Dapper.NET
Multimapping
Recherche…
Syntaxe
-
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)
Paramètres
Paramètre | Détails |
---|---|
CNN | Votre connexion à la base de données, qui doit déjà être ouverte. |
sql | Commande à exécuter. |
les types | Tableau de types dans le jeu d'enregistrements. |
carte | Func<> qui gère la construction du résultat de retour. |
param | Objet pour extraire les paramètres de. |
transaction | Transaction dont cette requête fait partie, le cas échéant. |
tamponné | S'il faut ou non mettre en mémoire tampon les résultats de la requête. Ceci est un paramètre facultatif avec la valeur par défaut étant true. Lorsque la mise en mémoire tampon est vraie, les résultats sont mis en mémoire tampon dans une List<T> , puis renvoyés sous la forme d'un IEnumerable<T> sûr pour une énumération multiple. Lorsque la mise en mémoire tampon est fausse, la connexion SQL est maintenue ouverte jusqu'à ce que vous ayez fini de lire, ce qui vous permet de traiter une seule ligne à la fois en mémoire. Plusieurs énumérations engendreront des connexions supplémentaires à la base de données. Bien que false mis en mémoire tampon soit très efficace pour réduire l’utilisation de la mémoire si vous ne gérez que de très petits fragments d’enregistrements renvoyés, il se caractérise par une surcharge de performances considérable par rapport à la matérialisation rapide du jeu de résultats. Enfin, si vous avez de nombreuses connexions SQL non tamponnées simultanées, vous devez tenir compte de la famine du pool de connexions, ce qui entraîne le blocage des requêtes jusqu'à ce que les connexions soient disponibles. |
splitOn | Le champ que nous devons diviser et lire le second objet (par défaut: id). Cela peut être une liste délimitée par des virgules lorsque plus d'un type est contenu dans un enregistrement. |
commandeTimeout | Nombre de secondes avant l'expiration du délai d'exécution de la commande. |
type de commande | Est-ce un processus stocké ou un lot? |
Mappage multi-tables simple
Disons que nous avons une interrogation des cavaliers restants qui doivent remplir une classe de personnes.
prénom | Née | Résidence |
---|---|---|
Daniel Dennett | 1942 | les États-Unis d'Amérique |
Sam Harris | 1967 | les États-Unis d'Amérique |
Richard dawkins | 1941 | Royaume-Uni |
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; }
}
Nous pouvons remplir la classe de personne ainsi que la propriété Residence avec une instance de Country à l'aide d'une Query<>
surcharge Query<>
qui prend un Func<>
pouvant être utilisé pour composer l'instance renvoyée. Le Func<>
peut prendre jusqu'à 7 types d'entrées, l'argument générique final étant toujours le type de retour.
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");
Notez l'utilisation de l'
splitOn: "Residence"
qui est la 1ère colonne du prochain type de classe à renseigner (dans ce cas,Country
). Dapper recherchera automatiquement une colonne appelée Id à diviser mais si elle n'en trouve pas et quesplitOn
n'est pas fourni, uneSystem.ArgumentException
sera lancée avec un message utile. Donc, bien que ce soit facultatif, vous devrez généralement fournir une valeursplitOn
.
Cartographie un à plusieurs
Regardons un exemple plus complexe qui contient une relation un-à-plusieurs. Notre requête contiendra désormais plusieurs lignes contenant des données en double et nous devrons gérer cela. Nous faisons cela avec une recherche dans une fermeture.
La requête change légèrement comme le font les exemples de classes.
Id | prénom | Née | CountryId | Nom du pays | BookId | Nom du livre |
---|---|---|---|---|---|---|
1 | Daniel Dennett | 1942 | 1 | les États-Unis d'Amérique | 1 | Brainstorms |
1 | Daniel Dennett | 1942 | 1 | les États-Unis d'Amérique | 2 | Espace vital |
2 | Sam Harris | 1967 | 1 | les États-Unis d'Amérique | 3 | Le paysage moral |
2 | Sam Harris | 1967 | 1 | les États-Unis d'Amérique | 4 | Se réveiller: Guide de spiritualité sans religion |
3 | Richard dawkins | 1941 | 2 | Royaume-Uni | 5 | La magie de la réalité: comment nous savons ce qui est vraiment vrai |
3 | Richard dawkins | 1941 | 2 | Royaume-Uni | 6 | Un appétit pour merveille: la fabrication d'un scientifique |
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; }
}
Les dictionnaires remainingHorsemen
seront remplis d'instances entièrement matérialisées des objets de la personne. Pour chaque ligne du résultat de la requête, les valeurs mappées des instances des types définis dans les arguments lambda sont transmises et il vous appartient de gérer cela.
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");
Notez comment l'argument
splitOn
est une liste délimitée par des virgules des premières colonnes du type suivant.
Cartographie de plus de 7 types
Parfois, le nombre de types que vous mappez dépasse les 7 fournis par le Func <> qui effectue la construction.
Au lieu d'utiliser la Query<>
avec les entrées d'argument de type générique, nous allons fournir les types à mapper en tant que tableau, suivis de la fonction de mappage. Outre le réglage manuel initial et la conversion des valeurs, le reste de la fonction ne change pas.
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");
Mappages personnalisés
Si les noms des colonnes de requête ne correspondent pas à vos classes, vous pouvez configurer des mappages pour les types. Cet exemple illustre le mappage à l'aide de System.Data.Linq.Mapping.ColumnAttribute
ainsi qu'un mappage personnalisé.
Les mappages ne doivent être configurés qu'une seule fois par type, donc définissez-les au démarrage de l'application ou ailleurs pour qu'ils ne soient initialisés qu'une seule fois.
En supposant la même requête que l'exemple One-to-many et les classes remaniées vers de meilleurs noms comme ceux-ci:
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; }
}
Notez que
Book
ne repose pas surColumnAttribute
mais que nous devons conserver l'instructionif
Placez maintenant ce code de mappage quelque part dans votre application où il n'est exécuté qu'une seule fois:
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);
La requête est ensuite exécutée à l'aide de l'un des exemples de Query<>
précédents Query<>
.
Une manière plus simple d'ajouter les mappages est montrée dans cette réponse .