Dapper 中多重映射的正确使用
我正在尝试使用 Dapper 的多重映射功能返回 ProductItems 和关联客户的列表。
[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; }
}
我的 Dapper 代码:
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。我是否误解了多重映射功能的核心功能?我不想每次都添加完整的列名称列表。
I'm trying to use the multimapping feature of Dapper to return a list of ProductItems and associated Customers.
[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; }
}
My Dapper code:
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"
);
This works fine, but I seem to have to add the complete column list to the "splitOn" parameter to return all the customers' properties. If I don't add "CustomerName", it returns null. Am I misunderstanding the core functionality of the multimapping feature? I don't want to have to add a complete list of column names each time.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
我刚刚运行了一个运行良好的测试:
需要将 splitOn 参数指定为分割点,它默认为 Id。如果有多个分割点,您需要将它们添加到逗号分隔的列表中。
假设您的记录集如下所示:
Dapper 需要知道如何按此顺序将列拆分为 2 个对象。粗略一看,客户从
CustomerId
列开始,因此splitOn: CustomerId
。这里有一个大警告,如果基础表中的列顺序由于某种原因翻转:
splitOn: CustomerId
将导致空客户名称。如果您指定
CustomerId,CustomerName
作为分割点,dapper 会假设您尝试将结果集分割为 3 个对象。第一个从头开始,第二个从CustomerId
开始,第三个从CustomerName
开始。I just ran a test that works fine:
The splitOn param needs to be specified as the split point, it defaults to Id. If there are multiple split points, you will need to add them in a comma delimited list.
Say your recordset looks like this:
Dapper needs to know how to split the columns in this order into 2 objects. A cursory look shows that the Customer starts at the column
CustomerId
, hencesplitOn: CustomerId
.There is a big caveat here, if the column ordering in the underlying table is flipped for some reason:
splitOn: CustomerId
will result in a null customer name.If you specify
CustomerId,CustomerName
as split points, dapper assumes you are trying to split up the result set into 3 objects. First starts at the beginning, second starts atCustomerId
, third atCustomerName
.我们的表的命名与您的表类似,其中类似“CustomerID”的内容可能会使用“select *”操作返回两次。因此,Dapper 正在完成其工作,但分割得太早(可能),因为列将是:
这使得 splitOn: 参数不太有用,特别是当您不确定返回列的顺序时。可以手动指定列...但现在是 2017 年了,我们很少再为基本对象获取这样做了。
我们所做的就是简单地使用 Id 的别名,并且从不指定 splitOn(使用 Dapper 的默认“Id”),而且多年来它对数千个查询都非常有效。
...瞧!默认情况下,Dapper 只会根据 Id 进行拆分,并且该 Id 出现在所有 Customer 列之前。当然,它会向您的返回结果集中添加一个额外的列,但是对于准确了解哪些列属于哪个对象的附加实用程序来说,这是极小的开销。您可以轻松扩展它。需要地址和国家信息?
最重要的是,您可以用最少量的 SQL 清楚地显示哪些列与哪个对象关联。剩下的由 Dapper 完成。
Our tables are named similarly to yours, where something like "CustomerID" might be returned twice using a 'select *' operation. Therefore, Dapper is doing its job but just splitting too early (possibly), because the columns would be:
This makes the splitOn: parameter not so useful, especially when you're not sure what order the columns are returned in. Of course you could manually specify columns... but it's 2017 and we just rarely do that anymore for basic object gets.
What we do, and it's worked great for thousands of queries for many many years, is simply use an alias for Id, and never specify splitOn (using Dapper's default 'Id').
...voila! Dapper will only split on Id by default, and that Id occurs before all the Customer columns. Of course it will add an extra column to your return resultset, but that is extremely minimal overhead for the added utility of knowing exactly which columns belong to what object. And you can easily expand this. Need address and country information?
Best of all, you're clearly showing in a minimal amount of SQL which columns are associated with which object. Dapper does the rest.
假设以下结构,其中“|”是分裂点,Ts 是应应用映射的实体。
以下是您必须编写的 Dapper 查询。
因此,我们希望 TFirst 映射到 col_1 col_2 col_3,TSecond 映射到 col_n col_m ...
splitOn 表达式转换为:
开始将所有列映射到 TFirst 直到找到名为或别名为“col_3”的列,并且还包括“ col_3' 进入映射结果。
然后开始映射到 TSecond 从“col_n”开始的所有列,并继续映射直到找到新的分隔符(在本例中为“col_A”),并标记 TThird 映射的开始,依此类推。
SQL 查询的列和映射对象的 props 是 1:1 关系(意味着它们应该命名相同)。如果 SQL 查询生成的列名不同,您可以使用“AS [Some_Alias_Name]”表达式为它们设置别名。
Assuming the following structure where '|' is the point of splitting and Ts are the entities to which the mapping should be applied.
Following is the Dapper query that you will have to write.
So we want for TFirst to map to col_1 col_2 col_3, for TSecond to col_n col_m ...
The splitOn expression translates to:
Start mapping of all columns into TFirst till you find a column named or aliased as 'col_3', and also include 'col_3' into the mapping result.
Then start mapping into TSecond all columns starting from 'col_n' and continue mapping till new separator is found, which in this case is 'col_A', and mark the start of TThird mapping and so on.
The columns of the SQL query and the props of the mapping object are in a 1:1 relation (meaning that they should be named the same). If the column names resulting from the SQL query are different, you can alias them using the 'AS [Some_Alias_Name]' expression.
如果您需要映射一个大型实体,那么编写每个字段一定是一项艰巨的任务。
我尝试了 @BlackjacketMack 答案,但我的一个表有一个 Id 列,其他表没有(我知道这是数据库设计问题,但是......)然后这会在 dapper 上插入一个额外的分割,这就是为什么
对我不起作用。然后我对此进行了一些更改,只需插入一个名称与表上的任何字段都不匹配的分割点,在可能的情况下将
as Id
更改为as _SplitPoint_,最终的sql脚本如下所示:
然后在dapper中添加一个splitOn,如下所示
If you need to map a large entity write each field must be a hard task.
I tried @BlackjacketMack answer, but one of my tables has an Id Column other ones not (I know it's a DB design problem, but ...) then this insert an extra split on dapper, that's why
Doesn't work for me. Then I ended with a little change to this, just insert an split point with a name that doesn't match with any field on tables, In may case changed
as Id
byas _SplitPoint_
, the final sql script looks like this:Then in dapper add just one splitOn as this
还有一个警告。如果 CustomerId 字段为 null(通常在具有左连接的查询中),Dapper 将创建 Customer = null 的 ProductItem。在上面的例子中:
甚至还有一个警告/陷阱。如果您没有映射 splitOn 中指定的字段,并且该字段包含 null,Dapper 将创建并填充相关对象(在本例中为 Customer)。为了演示如何将此类与之前的 sql 一起使用:
There is one more caveat. If CustomerId field is null (typically in queries with left join) Dapper creates ProductItem with Customer = null. In the example above:
And even one more caveat/trap. If you don't map the field specified in splitOn and that field contains null Dapper creates and fills the related object (Customer in this case). To demonstrate use this class with previous sql:
我通常在我的存储库中执行此操作,对我的用例很有用。我想我会分享。也许有人会进一步扩展这一点。
一些缺点是:
代码:
I do this generically in my repo, works good for my use case. I thought I'd share. Maybe someone will extend this further.
Some drawbacks are:
The code:
我想指出一个非常重要的方面:实体中的属性名称必须与选择语句匹配。
splitOn
的另一个方面是默认情况下它如何查找 Id,因此您不必指定它,除非您的命名类似于CustomerId
,而不是Id
。让我们看看这 2 种方法:方法 1
Entity Customer : Id Name
您的查询应该类似于:
然后您的映射了解实体和表之间的关系。
方法 2
实体客户:CustomerId、FancyName
选择c.Id作为nameof{Customer.CustomerId},c.WeirdAssName作为nameof{Customer.FancyName}
在映射的最后,您必须使用
SplitOn
指定 Id 为 CustomerId。我遇到了一个问题,即使映射在技术上是正确的,但由于与 SQL 语句不匹配,我无法获取值。
I would like to note a very important aspect: the property name within the Entity must match the select statement. Another aspect of
splitOn
is how it looks for Id by default, so you don't have to specify it unless your naming is something likeCustomerId
, instead ofId
. Let's look at these 2 approaches:Approach 1
Entity Customer : Id Name
Your query should be something like:
Then your mapping understands the relationship between the Entity and the table.
Approach 2
Entity Customer: CustomerId, FancyName
Select c.Id as nameof{Customer.CustomerId}, c.WeirdAssName As nameof{Customer.FancyName}
and at the end of the mapping, you have to specify that the Id is the CustomerId by using the
SplitOn
.I had an issue where I was not getting my values even though the mapping was correct technically because of a mismatch with the SQL statement.