Dieser Beitrag zeigt, wie Sie eine stark normalisierte SQL-Datenbank abfragen und das Ergebnis einer Reihe hoch verschachtelter C # POCO-Objekte zuordnen.
Zutaten:
- 8 Zeilen C #.
- Einige ziemlich einfache SQL, die einige Joins verwendet.
- Zwei großartige Bibliotheken.
Die Einsicht, die es mir ermöglichte, dieses Problem zu lösen, besteht darin, das MicroORM
von zu trennen mapping the result back to the POCO Entities
. Daher verwenden wir zwei separate Bibliotheken:
Im Wesentlichen verwenden wir Dapper , um die Datenbank abzufragen, und dann Slapper.Automapper , um das Ergebnis direkt in unsere POCOs abzubilden.
Vorteile
- Einfachheit . Es sind weniger als 8 Codezeilen. Ich finde das viel einfacher zu verstehen, zu debuggen und zu ändern.
- Weniger Code . Ein paar Codezeilen sind alles Slapper.Automapper muss alles verarbeiten, was Sie darauf werfen, selbst wenn wir einen komplexen verschachtelten POCO haben (dh POCO enthält,
List<MyClass1>
der wiederum enthält List<MySubClass2>
, usw.).
- Geschwindigkeit . Beide Bibliotheken verfügen über ein außerordentliches Maß an Optimierung und Caching, sodass sie fast so schnell ausgeführt werden wie handgestimmte ADO.NET-Abfragen.
- Trennung von Bedenken . Wir können das MicroORM gegen ein anderes austauschen, und das Mapping funktioniert immer noch und umgekehrt.
- Flexibilität . Slapper.Automapper verarbeitet beliebig verschachtelte Hierarchien und ist nicht auf einige Verschachtelungsebenen beschränkt. Wir können leicht schnelle Änderungen vornehmen, und alles wird noch funktionieren.
- Debuggen . Wir können zuerst sehen, dass die SQL-Abfrage ordnungsgemäß funktioniert, und dann überprüfen, ob das Ergebnis der SQL-Abfrage ordnungsgemäß den POCO-Zielentitäten zugeordnet ist.
- Einfache Entwicklung in SQL . Ich finde, dass das Erstellen abgeflachter Abfragen mit
inner joins
, um flache Ergebnisse zurückzugeben, viel einfacher ist als das Erstellen mehrerer select-Anweisungen mit Stitching auf der Clientseite.
- Optimierte Abfragen in SQL . In einer stark normalisierten Datenbank ermöglicht das Erstellen einer flachen Abfrage der SQL-Engine, erweiterte Optimierungen auf das Ganze anzuwenden, die normalerweise nicht möglich wären, wenn viele kleine Einzelabfragen erstellt und ausgeführt würden.
- Vertrauen . Dapper ist das Backend für StackOverflow, und Randy Burden ist ein bisschen ein Superstar. Muss ich noch mehr sagen?
- Entwicklungsgeschwindigkeit. Ich war in der Lage, einige außerordentlich komplexe Abfragen mit vielen Verschachtelungsebenen durchzuführen, und die Entwicklungszeit war ziemlich gering.
- Weniger Bugs. Ich habe es einmal geschrieben, es hat einfach funktioniert, und diese Technik hilft jetzt, ein FTSE-Unternehmen mit Strom zu versorgen. Es gab so wenig Code, dass es kein unerwartetes Verhalten gab.
Nachteile
- Skalierung über 1.000.000 Zeilen zurückgegeben. Funktioniert gut, wenn <100.000 Zeilen zurückgegeben werden. Wenn wir jedoch mehr als 1.000.000 Zeilen zurückbringen, sollten wir den Datenverkehr zwischen uns und dem SQL Server nicht reduzieren
inner join
(wodurch Duplikate zurückgebracht werden), sondern stattdessen mehrere select
Anweisungen verwenden und alles wieder zusammenfügen Client-Seite (siehe die anderen Antworten auf dieser Seite).
- Diese Technik ist abfrageorientiert . Ich habe diese Technik nicht zum Schreiben in die Datenbank verwendet, aber ich bin sicher, dass Dapper dies mit etwas mehr Arbeit mehr als tun kann, da StackOverflow selbst Dapper als Data Access Layer (DAL) verwendet.
Leistungstest
In meinen Tests hat Slapper.Automapper den von Dapper zurückgegebenen Ergebnissen einen kleinen Overhead hinzugefügt, was bedeutete, dass es immer noch 10x schneller als Entity Framework war und die Kombination immer noch ziemlich nahe an der theoretischen Höchstgeschwindigkeit liegt, zu der SQL + C # fähig ist .
In den meisten praktischen Fällen würde der größte Teil des Overheads in einer nicht optimalen SQL-Abfrage liegen und nicht mit einer gewissen Zuordnung der Ergebnisse auf der C # -Seite.
Leistungstestergebnisse
Gesamtzahl der Iterationen: 1000
Dapper by itself
: 1,889 Millisekunden pro Abfrage mit 3 lines of code to return the dynamic
.
Dapper + Slapper.Automapper
: 2,463 Millisekunden pro Abfrage unter Verwendung einer zusätzlichen 3 lines of code for the query + mapping from dynamic to POCO Entities
.
Gearbeitetes Beispiel
In diesem Beispiel haben wir eine Liste von Contacts
und jeder Contact
kann eine oder mehrere haben phone numbers
.
POCO-Einheiten
public class TestContact
{
public int ContactID { get; set; }
public string ContactName { get; set; }
public List<TestPhone> TestPhones { get; set; }
}
public class TestPhone
{
public int PhoneId { get; set; }
public int ContactID { get; set; }
public string Number { get; set; }
}
SQL-Tabelle TestContact
SQL-Tabelle TestPhone
Beachten Sie, dass diese Tabelle einen Fremdschlüssel hat, ContactID
der sich auf die TestContact
Tabelle bezieht (dies entspricht dem List<TestPhone>
im obigen POCO).
SQL, das ein flaches Ergebnis erzeugt
In unserer SQL-Abfrage verwenden wir so viele JOIN
Anweisungen, wie wir benötigen, um alle benötigten Daten in einer flachen, denormalisierten Form abzurufen . Ja, dies kann zu Duplikaten in der Ausgabe führen, aber diese Duplikate werden automatisch entfernt, wenn wir Slapper.Automapper verwenden , um das Ergebnis dieser Abfrage automatisch direkt in unsere POCO-Objektzuordnung abzubilden.
USE [MyDatabase];
SELECT tc.[ContactID] as ContactID
,tc.[ContactName] as ContactName
,tp.[PhoneId] AS TestPhones_PhoneId
,tp.[ContactId] AS TestPhones_ContactId
,tp.[Number] AS TestPhones_Number
FROM TestContact tc
INNER JOIN TestPhone tp ON tc.ContactId = tp.ContactId
C # -Code
const string sql = @"SELECT tc.[ContactID] as ContactID
,tc.[ContactName] as ContactName
,tp.[PhoneId] AS TestPhones_PhoneId
,tp.[ContactId] AS TestPhones_ContactId
,tp.[Number] AS TestPhones_Number
FROM TestContact tc
INNER JOIN TestPhone tp ON tc.ContactId = tp.ContactId";
string connectionString =
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
{
dynamic test = conn.Query<dynamic>(sql);
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(TestContact), new List<string> { "ContactID" });
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(TestPhone), new List<string> { "PhoneID" });
var testContact = (Slapper.AutoMapper.MapDynamic<TestContact>(test) as IEnumerable<TestContact>).ToList();
foreach (var c in testContact)
{
foreach (var p in c.TestPhones)
{
Console.Write("ContactName: {0}: Phone: {1}\n", c.ContactName, p.Number);
}
}
}
}
Ausgabe
POCO-Entitätshierarchie
In Visual Studio können wir sehen, dass Slapper.Automapper unsere POCO-Entitäten ordnungsgemäß ausgefüllt hat, dh wir haben eine List<TestContact>
und jede TestContact
hat eine List<TestPhone>
.
Anmerkungen
Sowohl Dapper als auch Slapper.Automapper speichern alles intern, um die Geschwindigkeit zu erhöhen. Wenn Sie auf Speicherprobleme stoßen (sehr unwahrscheinlich), stellen Sie sicher, dass Sie gelegentlich den Cache für beide leeren.
Stellen Sie sicher, dass Sie die zurückkommenden Spalten mit der Unterstrich- _
Notation ( ) benennen, um Slapper.Automapper Hinweise zu geben, wie das Ergebnis den POCO-Entitäten zugeordnet werden kann.
Stellen Sie sicher, dass Sie Slapper.Automapper-Hinweise zum Primärschlüssel für jede POCO-Entität geben (siehe Zeilen Slapper.AutoMapper.Configuration.AddIdentifiers
). Sie können dies auch Attributes
auf dem POCO verwenden. Wenn Sie diesen Schritt überspringen, kann dies (theoretisch) schief gehen, da Slapper.Automapper nicht weiß, wie das Mapping ordnungsgemäß ausgeführt wird.
Update 14.06.2015
Diese Technik wurde erfolgreich auf eine riesige Produktionsdatenbank mit über 40 normalisierten Tabellen angewendet. Es funktionierte perfekt eine erweiterte SQL - Abfrage mit mehr als 16 abzubilden inner join
und left join
in die richtige POCO Hierarchie (mit 4 Ebenen der Verschachtelung). Die Abfragen sind unglaublich schnell, fast so schnell wie die manuelle Codierung in ADO.NET (normalerweise waren es 52 Millisekunden für die Abfrage und 50 Millisekunden für die Zuordnung vom flachen Ergebnis zur POCO-Hierarchie). Dies ist wirklich nichts Revolutionäres, aber es übertrifft Entity Framework in Bezug auf Geschwindigkeit und Benutzerfreundlichkeit, insbesondere wenn wir nur Abfragen ausführen.
Update 19.02.2016
Code läuft seit 9 Monaten einwandfrei in der Produktion. Die neueste Version von Slapper.Automapper
enthält alle Änderungen, die ich vorgenommen habe, um das Problem zu beheben, das mit der Rückgabe von Nullen in der SQL-Abfrage zusammenhängt.
Update 2017-02-20
Code läuft seit 21 Monaten einwandfrei in der Produktion und hat fortlaufende Anfragen von Hunderten von Benutzern in einem FTSE 250-Unternehmen bearbeitet.
Slapper.Automapper
eignet sich auch hervorragend zum Zuordnen einer CSV-Datei direkt zu einer Liste von POCOs. Lesen Sie die CSV-Datei in eine IDictionary-Liste und ordnen Sie sie dann direkt der Zielliste der POCOs zu. Der einzige Trick besteht darin, dass Sie eine Eigenschaft hinzufügen int Id {get; set}
und sicherstellen müssen , dass sie für jede Zeile eindeutig ist (andernfalls kann der Automapper nicht zwischen den Zeilen unterscheiden).
Update 2019-01-29
Kleinere Aktualisierung, um weitere Codekommentare hinzuzufügen.
Siehe: https://github.com/SlapperAutoMapper/Slapper.AutoMapper