[英]How do I write one to many query in Dapper.Net?
我已经编写了这段代码来计划一对多关系,但是它不起作用:
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);
}
}
有人可以发现错误吗?
编辑:
这些是我的实体:
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>();
}
}
编辑:
我将查询更改为:
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");
我摆脱了例外! 但是,员工完全没有映射。 我仍然不确定在第一次查询中IEnumerable<Employee>
有什么问题。
这篇文章展示了如何查询高度标准化的SQL数据库 ,以及如何将结果映射到一组高度嵌套的C#POCO对象中。
配料:
使我能够解决此问题的见解是将MicroORM
与mapping the result back to the POCO Entities
分开。 因此,我们使用两个单独的库:
本质上,我们使用Dapper查询数据库,然后使用Slapper.Automapper将结果直接映射到我们的POCO中。
List<MyClass1>
,而List<MyClass1>
依次包含List<MySubClass2>
等), Automapper也需要处理所有抛出的问题。 inner joins
创建扁平化查询以返回扁平化结果要比创建多个select语句(在客户端进行拼接)要容易得多。 inner join
联接(它会带回重复项)将其弄平,而应该使用多个select
语句并将所有内容缝起来一起在客户端(请参阅此页面上的其他答案)。 在我的测试中, Slapper.Automapper给Dapper返回的结果增加了很小的开销,这意味着它仍然比Entity Framework快10倍,并且组合仍然相当接近SQL + C#能够达到的理论最大速度 。
在大多数实际情况下,大部分开销将出现在非最佳SQL查询中,而不是在C#端对结果进行一些映射。
迭代总数:1000
Dapper by itself
:每个查询1.889毫秒,使用3 lines of code to return the dynamic
。 Dapper + Slapper.Automapper
:每次查询2.463毫秒,使用额外的3 lines of code for the query + mapping from dynamic to POCO Entities
。 在此示例中,我们具有Contacts
列表,并且每个Contact
可以具有一个或多个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
请注意,此表有一个外键ContactID
,它引用了TestContact
表(与上面POCO中的List<TestPhone>
相对应)。
在我们的SQL查询中,我们使用了尽可能多的JOIN
语句来获取所需的所有数据(以扁平化,非规范化的形式) 。 是的,这可能会在输出中产生重复项,但是当我们使用Slapper.Automapper将查询结果直接映射到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);
}
}
}
}
在Visual Studio中查看,我们可以看到Slapper.Automapper已正确填充了POCO实体,即,我们有一个List<TestContact>
,每个TestContact
都有一个List<TestPhone>
。
Dapper和Slapper.Automapper都在内部缓存所有内容以提高速度。 如果遇到内存问题(极不可能),请确保偶尔清除两者的缓存。
确保使用下划线( _
)表示法来命名返回的列,从而为Slapper.Automapper提供有关如何将结果映射到POCO实体的线索。
确保为每个POCO实体的主键提供Slapper.Automapper线索(请参见Slapper.AutoMapper.Configuration.AddIdentifiers
行)。 您也可以为此在POCO上使用Attributes
。 如果跳过此步骤,则理论上可能会出错,因为Slapper.Automapper将不知道如何正确执行映射。
成功地将此技术应用于具有40多个标准化表的庞大生产数据库。 它完美地映射了一个高级SQL查询,该查询具有16个以上的inner join
联接和left join
联接到正确的POCO层次结构(具有4个嵌套级别)。 查询的速度非常快,几乎与在ADO.NET中手动编码的速度一样快(查询通常为52毫秒,从平面结果到POCO层次结构的映射通常为50毫秒)。 这确实没有什么革命性的,但是它确实在速度和易用性方面优于Entity Framework,特别是如果我们正在做的是运行查询。
代码在生产中完美运行了9个月。 Slapper.Automapper
的最新版本具有我为解决与SQL查询中返回的null相关的问题而应用的所有更改。
代码已经在生产中完美运行了21个月,并且已经处理了FTSE 250公司中数百名用户的连续查询。
Slapper.Automapper
也非常适合将.csv文件直接映射到POCO列表中。 将.csv文件读取到IDictionary列表中,然后将其直接映射到POCO的目标列表中。 唯一的技巧是您必须添加一个属性int Id {get; set}
int Id {get; set}
,并确保每一行都是唯一的(否则自动映射器将无法区分行)。
较小的更新以添加更多代码注释。
参见: https : //github.com/SlapperAutoMapper/Slapper.AutoMapper
我想让它尽可能简单,我的解决方案:
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();
}
我仍然对数据库进行一次调用,现在我执行2个查询而不是一个查询,而第二个查询使用的是INNER联接,而不是不太理想的LEFT联接。
对Andrew答案的略微修改,它利用Func选择了父键而不是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;
}
用法示例
conn.QueryParentChild<Product, Store, int>("sql here", prod => prod.Id, prod => prod.Stores)
根据此答案 ,Dapper.Net中没有内置的一对多映射支持。 查询将始终为每个数据库行返回一个对象。 不过,还有一个替代解决方案。
这是一个粗略的解决方法
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;
}
它绝不是最有效的方法,但是它将帮助您启动并运行。 如果有机会,我会尝试并对此进行优化。
像这样使用它:
conn.Query<Product, Store>("sql here", prod => prod.Stores);
请记住,您的对象需要实现GetHashCode
,也许像这样:
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
这是另一种方法:
订单(一个)-OrderDetail(许多)
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();
}
来源 : http : //dapper-tutorial.net/result-multi-mapping#example---query-multi-mapping-one-to-many
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.