Dapper.NET
Multimapping
Suche…
Syntax
-
public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>( this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
-
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
-
public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string sql, Type[] types, Func<object[], TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
Parameter
Parameter | Einzelheiten |
---|---|
cnn | Ihre Datenbankverbindung, die bereits geöffnet sein muss. |
sql | Befehl zum Ausführen |
Typen | Array von Typen im Datensatz. |
Karte | Func<> , die die Konstruktion des Rückgabeergebnisses übernimmt. |
param | Objekt, aus dem Parameter extrahiert werden sollen. |
Transaktion | Transaktion, zu der diese Abfrage gehört, sofern vorhanden. |
gepuffert | Ob das Lesen der Ergebnisse der Abfrage zwischengespeichert werden soll oder nicht. Dies ist ein optionaler Parameter mit der Standardeinstellung true. Wenn gepuffert wahr ist, werden die Ergebnisse in eine List<T> gepuffert und dann als IEnumerable<T> , das für mehrere Aufzählungen sicher ist. Wenn gepuffert "false" ist, bleibt die SQL-Verbindung offen, bis Sie mit dem Lesen fertig sind, sodass Sie eine einzelne Zeile zur Zeit im Speicher bearbeiten können. Durch mehrere Aufzählungen werden zusätzliche Verbindungen zur Datenbank hergestellt. Zwar ist gepuffertes false sehr effizient, um die Speicherauslastung zu reduzieren, wenn Sie nur sehr kleine Fragmente der zurückgegebenen Datensätze verwalten, was jedoch einen erheblichen Performance-Overhead im Vergleich zu eifrigem Ergebnis bewirkt. Wenn Sie über zahlreiche gleichzeitige ungepufferte SQL-Verbindungen verfügen, müssen Sie bedenken, dass der Verbindungspool verhungert, dass Anforderungen blockiert werden, bis Verbindungen verfügbar werden. |
aufgeteilt auf | Das Feld, aus dem wir das zweite Objekt teilen und lesen sollen (Standard: id). Dies kann eine durch Kommas getrennte Liste sein, wenn in einem Datensatz mehr als ein Typ enthalten ist. |
commandTimeout | Anzahl Sekunden vor der Befehlsausführung. |
Befehlstyp | Ist es eine gespeicherte Prozedur oder eine Charge? |
Einfaches Multi-Table-Mapping
Nehmen wir an, wir haben eine Abfrage der verbleibenden Reiter, die eine Person-Klasse füllen müssen.
Name | Geboren | Residenz |
---|---|---|
Daniel Dennett | 1942 | vereinigte Staaten von Amerika |
Sam Harris | 1967 | vereinigte Staaten von Amerika |
Richard Dawkins | 1941 | Großbritannien |
public class Person
{
public string Name { get; set; }
public int Born { get; set; }
public Country Residience { get; set; }
}
public class Country
{
public string Residence { get; set; }
}
Wir können sowohl die Personenklasse als auch die Residence-Eigenschaft mit einer Instanz von Country mit einer Überlast- Query<>
Func<>
, die eine Func<>
verwendet, die zum Func<>
der zurückgegebenen Instanz verwendet werden kann. Die Func<>
kann bis zu 7 Eingabetypen aufnehmen, wobei das generische Argument immer der Rückgabetyp ist.
var sql = @"SELECT 'Daniel Dennett' AS Name, 1942 AS Born, 'United States of America' AS Residence
UNION ALL SELECT 'Sam Harris' AS Name, 1967 AS Born, 'United States of America' AS Residence
UNION ALL SELECT 'Richard Dawkins' AS Name, 1941 AS Born, 'United Kingdom' AS Residence";
var result = connection.Query<Person, Country, Person>(sql, (person, country) => {
if(country == null)
{
country = new Country { Residence = "" };
}
person.Residience = country;
return person;
},
splitOn: "Residence");
Beachten Sie die Verwendung des Arguments
splitOn: "Residence"
, bei dem es sich um die 1. Spalte der nächsten zusplitOn: "Residence"
Klassentypen handelt (in diesem FallCountry
). Dapper sucht automatisch nach einer Spalte mit dem Namen " Id", nach der aufgeteilt werden soll. Wenn jedoch keinesplitOn
wird undsplitOn
nichtSystem.ArgumentException
wird, wird eineSystem.ArgumentException
mit einer hilfreichen Nachricht ausgelöst. Obwohl es optional ist, müssen Sie normalerweise einensplitOn
WertsplitOn
.
One-to-Many-Zuordnung
Schauen wir uns ein komplexeres Beispiel an, das eine Eins-zu-Viele-Beziehung enthält. Unsere Abfrage enthält jetzt mehrere Zeilen, die doppelte Daten enthalten, und wir müssen dies behandeln. Wir machen das mit einem Lookup in einem Abschluss.
Die Abfrage ändert sich geringfügig wie die Beispielklassen.
Ich würde | Name | Geboren | CountryId | Ländername | BookId | BookName |
---|---|---|---|---|---|---|
1 | Daniel Dennett | 1942 | 1 | vereinigte Staaten von Amerika | 1 | Brainstorms |
1 | Daniel Dennett | 1942 | 1 | vereinigte Staaten von Amerika | 2 | Spielraum |
2 | Sam Harris | 1967 | 1 | vereinigte Staaten von Amerika | 3 | Die moralische Landschaft |
2 | Sam Harris | 1967 | 1 | vereinigte Staaten von Amerika | 4 | Aufwachen: Ein Leitfaden für Spiritualität ohne Religion |
3 | Richard Dawkins | 1941 | 2 | Großbritannien | 5 | Die Magie der Realität: Wie wir wissen, was wirklich wahr ist |
3 | Richard Dawkins | 1941 | 2 | Großbritannien | 6 | Appetit for Wonder: Die Entstehung eines Wissenschaftlers |
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Born { get; set; }
public Country Residience { get; set; }
public ICollection<Book> Books { get; set; }
}
public class Country
{
public int CountryId { get; set; }
public string CountryName { get; set; }
}
public class Book
{
public int BookId { get; set; }
public string BookName { get; set; }
}
Das remainingHorsemen
WörterbuchHorsemen wird mit vollständig materialisierten Instanzen der Personenobjekte gefüllt. Für jede Zeile des Abfrageergebnisses werden die zugeordneten Werte von Instanzen der in den Lambda-Argumenten definierten Typen übergeben, und es liegt an Ihnen, wie Sie damit umgehen.
var sql = @"SELECT 1 AS Id, 'Daniel Dennett' AS Name, 1942 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 1 AS BookId, 'Brainstorms' AS BookName
UNION ALL SELECT 1 AS Id, 'Daniel Dennett' AS Name, 1942 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 2 AS BookId, 'Elbow Room' AS BookName
UNION ALL SELECT 2 AS Id, 'Sam Harris' AS Name, 1967 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 3 AS BookId, 'The Moral Landscape' AS BookName
UNION ALL SELECT 2 AS Id, 'Sam Harris' AS Name, 1967 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 4 AS BookId, 'Waking Up: A Guide to Spirituality Without Religion' AS BookName
UNION ALL SELECT 3 AS Id, 'Richard Dawkins' AS Name, 1941 AS Born, 2 AS CountryId, 'United Kingdom' AS CountryName, 5 AS BookId, 'The Magic of Reality: How We Know What`s Really True' AS BookName
UNION ALL SELECT 3 AS Id, 'Richard Dawkins' AS Name, 1941 AS Born, 2 AS CountryId, 'United Kingdom' AS CountryName, 6 AS BookId, 'An Appetite for Wonder: The Making of a Scientist' AS BookName";
var remainingHorsemen = new Dictionary<int, Person>();
connection.Query<Person, Country, Book, Person>(sql, (person, country, book) => {
//person
Person personEntity;
//trip
if (!remainingHorsemen.TryGetValue(person.Id, out personEntity))
{
remainingHorsemen.Add(person.Id, personEntity = person);
}
//country
if(personEntity.Residience == null)
{
if (country == null)
{
country = new Country { CountryName = "" };
}
personEntity.Residience = country;
}
//books
if(personEntity.Books == null)
{
personEntity.Books = new List<Book>();
}
if (book != null)
{
if (!personEntity.Books.Any(x => x.BookId == book.BookId))
{
personEntity.Books.Add(book);
}
}
return personEntity;
},
splitOn: "CountryId,BookId");
Beachten Sie, dass das
splitOn
Argument eine durch Kommas getrennte Liste der ersten Spalten des nächsten Typs ist.
Mapping von mehr als 7 Typen
Manchmal überschreitet die Anzahl der Typen, die Sie abbilden, die Zahl 7, die von der Funktion <> zur Verfügung gestellt wird, die die Konstruktion durchführt.
Anstelle der Query<>
mit den generischen Typargumenteingaben werden die Typen bereitgestellt, denen ein Array zugeordnet werden soll, gefolgt von der Mapping-Funktion. Abgesehen von der anfänglichen manuellen Einstellung und dem Umwandeln der Werte ändert sich der Rest der Funktion nicht.
var sql = @"SELECT 1 AS Id, 'Daniel Dennett' AS Name, 1942 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 1 AS BookId, 'Brainstorms' AS BookName
UNION ALL SELECT 1 AS Id, 'Daniel Dennett' AS Name, 1942 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 2 AS BookId, 'Elbow Room' AS BookName
UNION ALL SELECT 2 AS Id, 'Sam Harris' AS Name, 1967 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 3 AS BookId, 'The Moral Landscape' AS BookName
UNION ALL SELECT 2 AS Id, 'Sam Harris' AS Name, 1967 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 4 AS BookId, 'Waking Up: A Guide to Spirituality Without Religion' AS BookName
UNION ALL SELECT 3 AS Id, 'Richard Dawkins' AS Name, 1941 AS Born, 2 AS CountryId, 'United Kingdom' AS CountryName, 5 AS BookId, 'The Magic of Reality: How We Know What`s Really True' AS BookName
UNION ALL SELECT 3 AS Id, 'Richard Dawkins' AS Name, 1941 AS Born, 2 AS CountryId, 'United Kingdom' AS CountryName, 6 AS BookId, 'An Appetite for Wonder: The Making of a Scientist' AS BookName";
var remainingHorsemen = new Dictionary<int, Person>();
connection.Query<Person>(sql,
new[]
{
typeof(Person),
typeof(Country),
typeof(Book)
}
, obj => {
Person person = obj[0] as Person;
Country country = obj[1] as Country;
Book book = obj[2] as Book;
//person
Person personEntity;
//trip
if (!remainingHorsemen.TryGetValue(person.Id, out personEntity))
{
remainingHorsemen.Add(person.Id, personEntity = person);
}
//country
if(personEntity.Residience == null)
{
if (country == null)
{
country = new Country { CountryName = "" };
}
personEntity.Residience = country;
}
//books
if(personEntity.Books == null)
{
personEntity.Books = new List<Book>();
}
if (book != null)
{
if (!personEntity.Books.Any(x => x.BookId == book.BookId))
{
personEntity.Books.Add(book);
}
}
return personEntity;
},
splitOn: "CountryId,BookId");
Benutzerdefinierte Zuordnungen
Wenn die Abfragespaltennamen nicht mit Ihren Klassen übereinstimmen, können Sie Zuordnungen für Typen einrichten. In diesem Beispiel wird die Zuordnung mit System.Data.Linq.Mapping.ColumnAttribute
sowie eine benutzerdefinierte Zuordnung System.Data.Linq.Mapping.ColumnAttribute
.
Die Zuordnungen müssen nur einmal pro Typ eingerichtet werden. Setzen Sie sie also beim Start der Anwendung oder an anderer Stelle so, dass sie nur einmal initialisiert werden.
Nehmen wir wieder dieselbe Abfrage wie das One-to-Many-Beispiel an, und die Klassen werden wie folgt auf bessere Namen umgestaltet:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Born { get; set; }
public Country Residience { get; set; }
public ICollection<Book> Books { get; set; }
}
public class Country
{
[System.Data.Linq.Mapping.Column(Name = "CountryId")]
public int Id { get; set; }
[System.Data.Linq.Mapping.Column(Name = "CountryName")]
public string Name { get; set; }
}
public class Book
{
public int Id { get; set; }
public string Name { get; set; }
}
Beachten Sie, dass sich
Book
nicht aufColumnAttribute
, wir müssen jedoch dieif
ColumnAttribute
beibehalten
Platzieren Sie diesen Mapping-Code nun irgendwo in Ihrer Anwendung, wo er nur einmal ausgeführt wird:
Dapper.SqlMapper.SetTypeMap(
typeof(Country),
new CustomPropertyTypeMap(
typeof(Country),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<System.Data.Linq.Mapping.ColumnAttribute>()
.Any(attr => attr.Name == columnName)))
);
var bookMap = new CustomPropertyTypeMap(
typeof(Book),
(type, columnName) =>
{
if(columnName == "BookId")
{
return type.GetProperty("Id");
}
if (columnName == "BookName")
{
return type.GetProperty("Name");
}
throw new InvalidOperationException($"No matching mapping for {columnName}");
}
);
Dapper.SqlMapper.SetTypeMap(typeof(Book), bookMap);
Dann wird die Abfrage mit einem der vorherigen Beispiele Query<>
.
Eine einfachere Möglichkeit zum Hinzufügen der Zuordnungen wird in dieser Antwort gezeigt .