Dapper.NET
Multimapping
Ricerca…
Sintassi
-
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)
Parametri
Parametro | Dettagli |
---|---|
cnn | La tua connessione al database, che deve essere già aperta. |
sql | Comando da eseguire. |
tipi | Matrice di tipi nel set di record. |
carta geografica | Func<> che gestisce la costruzione del risultato di ritorno. |
param | Oggetto da cui estrarre i parametri. |
transazione | Transazione di cui questa query fa parte, se esiste. |
tamponata | Indica se bufferizzare o meno i risultati della query. Questo è un parametro facoltativo con il valore predefinito true. Quando il buffer è vero, i risultati vengono memorizzati in un List<T> e quindi restituiti come IEnumerable<T> che è sicuro per l'enumerazione multipla. Quando il buffer è falso, la connessione sql viene mantenuta aperta fino al termine della lettura, consentendo di elaborare una singola riga alla volta in memoria. Più enumerazioni genereranno ulteriori connessioni al database. Mentre buffered false è altamente efficiente per ridurre l'utilizzo della memoria se si mantengono solo frammenti molto piccoli dei record restituiti, ha un overhead delle prestazioni considerevole rispetto alla materializzazione impaziente del set di risultati. Infine, se si dispone di numerose connessioni SQL simultanee non bufferizzate, è necessario considerare l'inattività del pool di connessioni che causa il blocco delle richieste fino a quando le connessioni diventano disponibili. |
dividersi | Il campo dovremmo dividere e leggere il secondo oggetto da (predefinito: id). Questo può essere un elenco delimitato da virgole quando più di 1 tipo è contenuto in un record. |
CommandTimeout | Numero di secondi prima del timeout dell'esecuzione del comando. |
CommandType | È un processo memorizzato o un batch? |
Semplice mappatura multi-tabella
Diciamo che abbiamo una query sui restanti cavalieri che devono compilare una classe Person.
Nome | Nato | Residenza |
---|---|---|
Daniel Dennett | 1942 | Stati Uniti d'America |
Sam Harris | 1967 | Stati Uniti d'America |
Richard Dawkins | 1941 | Regno Unito |
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; }
}
Possiamo popolare la classe persona e la proprietà Residenza con un'istanza di Paese utilizzando una Query<>
sovraccarico Query<>
che accetta un Func<>
che può essere utilizzato per comporre l'istanza restituita. Il Func<>
può richiedere fino a 7 tipi di input con l'argomento generico finale che è sempre il tipo restituito.
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");
Notare l'uso dello
splitOn: "Residence"
argomentosplitOn: "Residence"
che è la prima colonna del prossimo tipo di classe da compilare (in questo casoCountry
). Dapper cercherà automaticamente una colonna denominata Id da dividere, ma se non ne trova una esplitOn
non viene fornito unSystem.ArgumentException
verrà lanciato con un messaggio utile. Quindi, sebbene sia facoltativo, di solito devi fornire un valoresplitOn
.
Mappatura uno-a-molti
Diamo un'occhiata a un esempio più complesso che contiene una relazione uno-a-molti. La nostra query ora conterrà più righe contenenti dati duplicati e dovremo gestirli. Lo facciamo con una ricerca in una chiusura.
La query cambia leggermente come fanno le classi di esempio.
Id | Nome | Nato | CountryId | Nome del paese | BookID | BookName |
---|---|---|---|---|---|---|
1 | Daniel Dennett | 1942 | 1 | Stati Uniti d'America | 1 | scambi di idee |
1 | Daniel Dennett | 1942 | 1 | Stati Uniti d'America | 2 | Camera di gomito |
2 | Sam Harris | 1967 | 1 | Stati Uniti d'America | 3 | Il paesaggio morale |
2 | Sam Harris | 1967 | 1 | Stati Uniti d'America | 4 | Svegliarsi: una guida alla spiritualità senza religione |
3 | Richard Dawkins | 1941 | 2 | Regno Unito | 5 | La magia della realtà: come sappiamo che cosa è veramente vero |
3 | Richard Dawkins | 1941 | 2 | Regno Unito | 6 | An Appetite for Wonder: The Making of a Scientist |
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; }
}
Il dizionario remainingHorsemen
sarà popolato con istanze completamente materializzate degli oggetti persona. Per ogni riga del risultato della query vengono passati i valori mappati delle istanze dei tipi definiti negli argomenti lambda e sta a te decidere come gestirlo.
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");
Si noti come l'argomento
splitOn
è un elenco delimitato da virgole delle prime colonne del prossimo tipo.
Mappatura di oltre 7 tipi
A volte il numero di tipi che stai mappando supera il 7 fornito da Func <> che esegue la costruzione.
Invece di usare la Query<>
con gli input dell'argomento di tipo generico, forniremo i tipi da mappare come una matrice, seguita dalla funzione di mappatura. A parte l'impostazione manuale iniziale e la trasmissione dei valori, il resto della funzione non cambia.
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");
Mappature personalizzate
Se i nomi delle colonne delle query non corrispondono alle tue classi, puoi configurare i mapping per i tipi. Questo esempio dimostra la mappatura utilizzando System.Data.Linq.Mapping.ColumnAttribute
e una mappatura personalizzata.
Le mappature devono essere configurate solo una volta per tipo, quindi impostarle all'avvio dell'applicazione o da qualche altra parte in cui vengono inizializzate solo una volta.
Assumendo di nuovo la stessa query dell'esempio uno a molti e le classi refactored verso nomi migliori come:
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; }
}
Nota come
Book
non si basa suColumnAttribute
ma dovremmo mantenere la dichiarazioneif
Ora posiziona questo codice di mappatura da qualche parte nell'applicazione in cui viene eseguito solo una volta:
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);
Quindi la query viene eseguita utilizzando uno degli esempi precedenti Query<>
.
In questa risposta viene mostrato un modo più semplice per aggiungere i mapping.