J'ai écrit ce code pour projeter une relation un à plusieurs mais cela ne fonctionne pas:
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
IEnumerable<Store> stores = connection.Query<Store, IEnumerable<Employee>, Store>
(@"Select Stores.Id as StoreId, Stores.Name,
Employees.Id as EmployeeId, Employees.FirstName,
Employees.LastName, Employees.StoreId
from Store Stores
INNER JOIN Employee Employees ON Stores.Id = Employees.StoreId",
(a, s) => { a.Employees = s; return a; },
splitOn: "EmployeeId");
foreach (var store in stores)
{
Console.WriteLine(store.Name);
}
}
Quelqu'un peut-il repérer l'erreur?
EDIT:
Ce sont mes entités:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public IList<Store> Stores { get; set; }
public Product()
{
Stores = new List<Store>();
}
}
public class Store
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<Product> Products { get; set; }
public IEnumerable<Employee> Employees { get; set; }
public Store()
{
Products = new List<Product>();
Employees = new List<Employee>();
}
}
EDIT:
Je change la requête en:
IEnumerable<Store> stores = connection.Query<Store, List<Employee>, Store>
(@"Select Stores.Id as StoreId ,Stores.Name,Employees.Id as EmployeeId,Employees.FirstName,
Employees.LastName,Employees.StoreId from Store Stores INNER JOIN Employee Employees
ON Stores.Id = Employees.StoreId",
(a, s) => { a.Employees = s; return a; }, splitOn: "EmployeeId");
et je me débarrasse des exceptions! Cependant, les employés ne sont pas mappés du tout. Je ne suis toujours pas sûr du problème que cela a eu avec IEnumerable<Employee>
dans la première requête.
Cet article explique comment interroger une base de données SQL hautement normalisée et mapper le résultat dans un ensemble d'objets C # POCO hautement imbriqués.
Ingrédients:
L’idée qui m’a permis de résoudre ce problème est de séparer le MicroORM
de mapping the result back to the POCO Entities
. Ainsi, nous utilisons deux bibliothèques distinctes:
Essentiellement, nous utilisons Dapper pour interroger la base de données, puis Slapper.Automapper pour mapper le résultat directement dans nos POCO.
List<MyClass1>
qui contient lui-même List<MySubClass2>
, etc).inner joins
pour renvoyer des résultats à plat est beaucoup plus facile que la création de plusieurs instructions de sélection, avec la broderie côté client.inner join
(ce qui ramène les doublons), nous devrions plutôt utiliser plusieurs select
et assemblez le tout côté client (voir les autres réponses sur cette page).Dans mes tests, Slapper.Automapper ajoutait une petite surcharge aux résultats renvoyés par Dapper, ce qui signifiait qu'il était toujours 10 fois plus rapide qu'Entity Framework, et la combinaison est toujours assez sacrément proche de la vitesse maximale théorique que SQL + C # est capable de .
Dans la plupart des cas pratiques, la majeure partie de la surcharge serait due à une requête SQL non optimale, et non à un mappage des résultats du côté C #.
Nombre total d'itérations: 1000
Dapper by itself
: 1,889 millisecondes par requête, en utilisant 3 lines of code to return the dynamic
.Dapper + Slapper.Automapper
: 2,463 millisecondes par requête, en utilisant un 3 lines of code for the query + mapping from dynamic to POCO Entities
supplémentaire.Dans cet exemple, nous avons une liste de Contacts
, et chaque Contact
peut avoir un ou plusieurs phone numbers
.
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; } // foreign key
public string Number { get; set; }
}
TestContact
TestPhone
Notez que cette table a une clé étrangère ContactID
qui fait référence à la table TestContact
(elle correspond au List<TestPhone>
du POCO ci-dessus).
Dans notre requête SQL, nous utilisons autant d'instructions JOIN
que nous avons besoin pour obtenir toutes les données dont nous avons besoin, sous la forme forme plate, dénormalisée . Oui, cela pourrait produire des doublons dans la sortie, mais ces doublons seront automatiquement éliminés lorsque nous utiliserons Slapper.Automapper pour mapper automatiquement le résultat de cette requête directement dans notre carte d'objets POCO.
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
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 = // -- Insert SQL connection string here.
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
// Can set default database here with conn.ChangeDatabase(...)
{
// Step 1: Use Dapper to return the flat result as a Dynamic.
dynamic test = conn.Query<dynamic>(sql);
// Step 2: Use Slapper.Automapper for mapping to the POCO Entities.
// - IMPORTANT: Let Slapper.Automapper know how to do the mapping;
// let it know the primary key for each POCO.
// - Must also use underscore notation ("_") to name parameters in the SQL query;
// see Slapper.Automapper docs.
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);
}
}
}
}
En regardant dans Visual Studio, nous pouvons voir que Slapper.Automapper a correctement rempli nos entités POCO, c’est-à-dire que nous avons un List<TestContact>
et que chaque TestContact
a un List<TestPhone>
.
Dapper et Slapper.Automapper cachent tout en interne pour plus de rapidité. Si vous rencontrez des problèmes de mémoire (très peu probable), veillez à vider le cache occasionnellement pour les deux.
Assurez-vous de nommer les colonnes qui reviennent en utilisant le trait de soulignement (_
) pour donner à Slapper.Automapper des indices sur la manière de mapper le résultat dans les entités POCO.
Assurez-vous de donner des indices à Slapper.Automapper sur la clé primaire de chaque entité POCO (voir les lignes Slapper.AutoMapper.Configuration.AddIdentifiers
). Vous pouvez également utiliser Attributes
sur le POCO pour cela. Si vous ignorez cette étape, cela pourrait mal tourner (en théorie), car Slapper.Automapper ne saurait pas comment faire le mappage correctement.
Appliqué avec succès cette technique à une énorme base de données de production avec plus de 40 tables normalisées. Cela a parfaitement fonctionné de mapper une requête SQL avancée avec plus de 16 inner join
et left join
dans la hiérarchie POCO appropriée (avec 4 niveaux d'imbrication). Les requêtes sont incroyablement rapides, presque aussi rapides que le codage manuel dans ADO.NET (il fallait généralement 52 millisecondes pour la requête et 50 millisecondes pour le mappage du résultat à plat dans la hiérarchie POCO). Ce n’est vraiment rien de révolutionnaire, mais c’est certainement plus rapide que Entity Framework, en particulier si nous ne faisons que lancer des requêtes.
Code fonctionne parfaitement depuis 9 mois. La dernière version de Slapper.Automapper
contient toutes les modifications que j'ai appliquées pour résoudre le problème lié aux valeurs NULL renvoyées dans la requête SQL.
Code fonctionne parfaitement depuis 21 mois et a traité des requêtes continues de centaines d'utilisateurs dans une société FTSE 250.
Slapper.Automapper
est également idéal pour mapper un fichier .csv directement dans une liste de POCO. Lisez le fichier .csv dans une liste d'IDictionary, puis mappez-le directement dans la liste cible des POCO. Le seul truc, c'est que vous devez ajouter une propriété int Id {get; set}
et vous assurer qu'elle est unique pour chaque ligne (sinon, le mappeur automatique ne pourra pas distinguer les lignes).
Mise à jour mineure pour ajouter plus de commentaires de code.
Voir: https://github.com/SlapperAutoMapper/Slapper.AutoMapper
Je voulais que ce soit aussi simple que possible, ma solution:
public List<ForumMessage> GetForumMessagesByParentId(int parentId)
{
var sql = @"
select d.id_data as Id, d.cd_group As GroupId, d.cd_user as UserId, d.tx_login As Login,
d.tx_title As Title, d.tx_message As [Message], d.tx_signature As [Signature], d.nm_views As Views, d.nm_replies As Replies,
d.dt_created As CreatedDate, d.dt_lastreply As LastReplyDate, d.dt_edited As EditedDate, d.tx_key As [Key]
from
t_data d
where d.cd_data = @DataId order by id_data asc;
select d.id_data As DataId, di.id_data_image As DataImageId, di.cd_image As ImageId, i.fl_local As IsLocal
from
t_data d
inner join T_data_image di on d.id_data = di.cd_data
inner join T_image i on di.cd_image = i.id_image
where d.id_data = @DataId and di.fl_deleted = 0 order by d.id_data asc;";
var mapper = _conn.QueryMultiple(sql, new { DataId = parentId });
var messages = mapper.Read<ForumMessage>().ToDictionary(k => k.Id, v => v);
var images = mapper.Read<ForumMessageImage>().ToList();
foreach(var imageGroup in images.GroupBy(g => g.DataId))
{
messages[imageGroup.Key].Images = imageGroup.ToList();
}
return messages.Values.ToList();
}
Je fais toujours un appel à la base de données, et bien que j'exécute maintenant 2 requêtes au lieu d'une, la seconde requête utilise une jointure INNER au lieu d'une jointure gauche optimale.
Selon cette réponse , il n’existe pas de support de cartographie intégré à Dapper.Net. Les requêtes renverront toujours un objet par ligne de base de données. Il existe cependant une solution alternative.
Une légère modification de la réponse d'Andrew qui utilise un Func pour sélectionner la clé parente au lieu de GetHashCode
.
public static IEnumerable<TParent> QueryParentChild<TParent, TChild, TParentKey>(
this IDbConnection connection,
string sql,
Func<TParent, TParentKey> parentKeySelector,
Func<TParent, IList<TChild>> childSelector,
dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
Dictionary<TParentKey, TParent> cache = new Dictionary<TParentKey, TParent>();
connection.Query<TParent, TChild, TParent>(
sql,
(parent, child) =>
{
if (!cache.ContainsKey(parentKeySelector(parent)))
{
cache.Add(parentKeySelector(parent), parent);
}
TParent cachedParent = cache[parentKeySelector(parent)];
IList<TChild> children = childSelector(cachedParent);
children.Add(child);
return cachedParent;
},
param as object, transaction, buffered, splitOn, commandTimeout, commandType);
return cache.Values;
}
Exemple d'utilisation
conn.QueryParentChild<Product, Store, int>("sql here", prod => prod.Id, prod => prod.Stores)
Voici une solution brute
public static IEnumerable<TOne> Query<TOne, TMany>(this IDbConnection cnn, string sql, Func<TOne, IList<TMany>> property, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
var cache = new Dictionary<int, TOne>();
cnn.Query<TOne, TMany, TOne>(sql, (one, many) =>
{
if (!cache.ContainsKey(one.GetHashCode()))
cache.Add(one.GetHashCode(), one);
var localOne = cache[one.GetHashCode()];
var list = property(localOne);
list.Add(many);
return localOne;
}, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
return cache.Values;
}
ce n’est certes pas le moyen le plus efficace, mais il vous permettra de vous mettre en mouvement. Je vais essayer d'optimiser cela quand j'en aurai l'occasion.
utilisez-le comme ceci:
conn.Query<Product, Store>("sql here", prod => prod.Stores);
gardez à l'esprit que vos objets doivent implémenter GetHashCode
, peut-être comme ceci:
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
Voici une autre méthode:
Commande (une) - CommandeDétail (plusieurs)
using (var connection = new SqlCeConnection(connectionString))
{
var orderDictionary = new Dictionary<int, Order>();
var list = connection.Query<Order, OrderDetail, Order>(
sql,
(order, orderDetail) =>
{
Order orderEntry;
if (!orderDictionary.TryGetValue(order.OrderID, out orderEntry))
{
orderEntry = order;
orderEntry.OrderDetails = new List<OrderDetail>();
orderDictionary.Add(orderEntry.OrderID, orderEntry);
}
orderEntry.OrderDetails.Add(orderDetail);
return orderEntry;
},
splitOn: "OrderDetailID")
.Distinct()
.ToList();
}
Source : http://dapper-tutorial.net/result-multi-mapping#example---query-multi-mapping- un à plusieurs