正确使用Dapper中的Multimapping

我正在尝试使用Dapper的Multimappingfunction来返回ProductItem和相关客户的列表。

[Table("Product")] public class ProductItem { public decimal ProductID { get; set; } public string ProductName { get; set; } public string AccountOpened { get; set; } public Customer Customer { get; set; } } public class Customer { public decimal CustomerId { get; set; } public string CustomerName { get; set; } } 

我的精巧代码如下

 var sql = @"select * from Product p inner join Customer c on p.CustomerId = c.CustomerId order by p.ProductName"; var data = con.Query<ProductItem, Customer, ProductItem>( sql, (productItem, customer) => { productItem.Customer = customer; return productItem; }, splitOn: "CustomerId,CustomerName" ); 

这工作正常,但我似乎必须将完整的列列表添加到splitOn参数来返回所有客户属性。 如果我不添加“CustomerName”,则返回null。 我想念 – 了解多图function的核心function。 我不想每次都添加一个完整的列名称列表。

我只是跑了一个testing,工作正常:

 var sql = "select cast(1 as decimal) ProductId, 'a' ProductName, 'x' AccountOpened, cast(1 as decimal) CustomerId, 'name' CustomerName"; var item = connection.Query<ProductItem, Customer, ProductItem>(sql, (p, c) => { p.Customer = c; return p; }, splitOn: "CustomerId").First(); item.Customer.CustomerId.IsEqualTo(1); 

splitOn参数需要被指定为分割点,它默认为Id。 如果有多个分割点,则需要将它们添加到以逗号分隔的列表中。

说你的logging集是这样的:

 ProductID |  ProductName |  AccountOpened |  CustomerId | 顾客姓名 
 --------------------------------------- ----------- --------------

Dapper需要知道如何将这个顺序拆分为2个对象。 粗略的一下表明客户从CustomerId列开始,因此splitOn: CustomerId

这里有一个很大的警告,如果底层表中的列顺序出于某种原因翻转了,

 ProductID |  ProductName |  AccountOpened | 客户名称| 客户ID  
 --------------------------------------- ----------- --------------

splitOn: CustomerId将导致一个空的客户名称。

如果将CustomerId,CustomerName指定为分割点,则dapper假定您正试图将结果集分成3个对象。 首先从头开始,第二次从CustomerId开始,第三次在CustomerName

我们的表的命名与您的类似,其中像“CustomerID”可能会使用“select *”操作返回两次。 因此,Dapper正在做的工作,但只是分裂得太早(可能),因为列将是:

 (select * might return): ProductID, ProductName, CustomerID, --first CustomerID AccountOpened, CustomerID, --second CustomerID, CustomerName. 

这使得spliton:参数不是很有用,特别是当你不确定列返回的顺序时。当然你可以手动指定列…但是到2017年,我们很less再为基本对象获取。

我们做了什么,而且对于成千上万的查询来说很好,很简单,就是使用Id的别名,而不用指定spliton(使用Dapper的默认“Id”)。

 select p.*, c.CustomerID AS Id, c.* 

……瞧! Dapper默认情况下只会在ID上分割,并且该Id在所有Customer列之前出现。 当然,它会为你的返回结果集增加一个额外的列,但是这对于确切地知道哪些列属于哪个对象的附加实用程序是非常小的开销。 而且你可以很容易地扩大这个。 需要地址和国家信息?

 select p.*, c.CustomerID AS Id, c.*, address.AddressID AS Id, address.*, country.CountryID AS Id, country.* 

最重要的是,你清楚地显示了哪些列与哪个对象相关联。 剩下的就是小巧。

还有一个警告。 如果CustomerId字段为空(通常在具有左连接的查询中),则Dapper使用Customer = null创buildProductItem。 在上面的例子中:

 var sql = "select cast(1 as decimal) ProductId, 'a' ProductName, 'x' AccountOpened, cast(null as decimal) CustomerId, 'n' CustomerName"; var item = connection.Query<ProductItem, Customer, ProductItem>(sql, (p, c) => { p.Customer = c; return p; }, splitOn: "CustomerId").First(); Debug.Assert(item.Customer == null); 

甚至还有一个警告/陷阱。 如果不映射在splitOn中指定的字段,并且该字段包含null,则Dapper将创build并填充相关对象(在此情况下为Customer)。 用以前的sql来演示如何使用这个类:

 public class Customer { //public decimal CustomerId { get; set; } public string CustomerName { get; set; } } ... Debug.Assert(item.Customer != null); Debug.Assert(item.Customer.CustomerName == "n"); 

我在回购中一般这样做,对我的用例很有帮助。 我以为我会分享。 也许有人会进一步扩大这一点。

一些缺点是:

  • 这假定你的外键属性是你的子对象的名字+“Id”,例如UnitId。
  • 我只有一个子对象映射到父对象。

代码:

  public IEnumerable<TParent> GetParentChild<TParent, TChild>() { var sql = string.Format(@"select * from {0} p inner join {1} c on p.{1}Id = c.Id", typeof(TParent).Name, typeof(TChild).Name); Debug.WriteLine(sql); var data = _con.Query<TParent, TChild, TParent>( sql, (p, c) => { p.GetType().GetProperty(typeof (TChild).Name).SetValue(p, c); return p; }, splitOn: typeof(TChild).Name + "Id"); return data; }