在Dapper中正确使用Multimapping

Ric*_*est 98 dapper

我正在尝试使用dapper的Multimapping功能来返回ProductItems和相关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; }
}
Run Code Online (Sandbox Code Playgroud)

我的短小精悍的代码如下

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"
);
Run Code Online (Sandbox Code Playgroud)

这工作正常,但我似乎必须将完整的列列表添加到splitOn参数以返回所有客户属性.如果我不添加"CustomerName",则返回null.我想念 - 了解多重映射功能的核心功能.我不想每次都要添加完整的列名列表.

Sam*_*ron 167

我刚刚运行了一个工作正常的测试:

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);
Run Code Online (Sandbox Code Playgroud)

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

假设您的记录集如下所示:

ProductID | ProductName | AccountOpened | CustomerId | CustomerName 
---------------------------------------   -------------------------

Dapper需要知道如何将此顺序中的列拆分为2个对象.粗略地看一下显示,客户开始在列CustomerId,因此splitOn: CustomerId.

这里有一个很大的警告,如果基础表中的列排序由于某种原因被翻转:

ProductID | ProductName | AccountOpened | CustomerName | CustomerId  
---------------------------------------   -------------------------

splitOn: CustomerId 将导致客户名称为空.

如果指定CustomerId,CustomerName为分割点,则dapper假定您尝试将结果集拆分为3个对象.首先从开始,第二个开始CustomerId,第三个开始CustomerName.

  • 要记住的一件事是你不能在`spliton`中有空格,即`CustomerId,CustomerName`而不是'CustomerId,CustomerName`,因为Dapper没有"修剪"字符串拆分的结果.它只会抛出通用的spliton错误.有一天开车送我疯了. (14认同)
  • 对于任何想知道的人,如果您必须将查询拆分为 3 个对象:在名为“Id”的列和名为“somethingId”的列上,请确保在拆分子句中包含第一个“Id”。即使 Dapper 默认在“Id”上拆分,在这种情况下也必须明确设置。 (3认同)
  • 谢谢山姆。是的,您的权利是与CustomerName |问题有关的列的退货顺序。返回的CustomerId客户名返回空值。 (2认同)
  • @vaheeds你应该总是使用列名并且永远不要使用星号,这样可以减少sql的工作量,并且你不会遇到列顺序错误的情况,就像在这种情况下一样. (2认同)
  • @vaheeds - 关于id,Id,ID查看精巧的代码,它不区分大小写,它还修剪了splitOn的文本 - 这是dapper的v1.50.2.0. (2认同)

Bla*_*ack 25

我们的表的名称与您的表类似,其中"CustomerID"之类的内容可能会使用"select*"操作返回两次.因此,Dapper正在完成它的工作,但只是过早地(可能)分裂,因为列将是:

(select * might return):
ProductID,
ProductName,
CustomerID, --first CustomerID
AccountOpened,
CustomerID, --second CustomerID,
CustomerName.
Run Code Online (Sandbox Code Playgroud)

这使得spliton:参数不那么有用,特别是当你不确定返回列的顺序时.当然你可以手动指定列......但它是2017年,我们很少再这样做了基本对象获取.

我们所做的,多年来成千上万的查询都很有用,只是使用Id的别名,而不是指定spliton(使用Dapper的默认'Id').

select 
p.*,

c.CustomerID AS Id,
c.*
Run Code Online (Sandbox Code Playgroud)

......瞧!默认情况下,Dapper将仅在Id上拆分,并且Id在所有Customer列之前发生.当然,它会为返回的结果集添加一个额外的列,但这对于确切知道哪些列属于哪个对象的附加实用程序来说是非常小的开销.你可以轻松扩展它.需要地址和国家信息?

select
p.*,

c.CustomerID AS Id,
c.*,

address.AddressID AS Id,
address.*,

country.CountryID AS Id,
country.*
Run Code Online (Sandbox Code Playgroud)

最重要的是,你清楚地显示了最少量的sql哪些列与哪个对象相关联.Dapper完成其余的工作.


小智 11

假设以下结构,其中“|” 是分裂点,Ts 是应该应用映射的实体。

       TFirst         TSecond         TThird           TFourth
------------------+-------------+-------------------+------------
col_1 col_2 col_3 | col_n col_m | col_A col_B col_C | col_9 col_8
------------------+-------------+-------------------+------------
Run Code Online (Sandbox Code Playgroud)

以下是您必须编写的 Dapper 查询。

       TFirst         TSecond         TThird           TFourth
------------------+-------------+-------------------+------------
col_1 col_2 col_3 | col_n col_m | col_A col_B col_C | col_9 col_8
------------------+-------------+-------------------+------------
Run Code Online (Sandbox Code Playgroud)

所以我们希望 TFirst 映射到 col_1 col_2 col_3,TSecond 映射到 col_n col_m ...

splitOn 表达式转换为:

开始将所有列映射到 TFirst 中,直到找到名为“col_3”或别名为“col_3”的列,并将“col_3”包含在映射结果中。

然后开始映射到 TSecond 中所有从 'col_n' 开始的列并继续映射直到找到新的分隔符,在这种情况下是 'col_A',并标记 TThird 映射的开始,依此类推。

SQL 查询的列和映射对象的 props 是 1:1 的关系(意味着它们应该命名相同)。如果 SQL 查询产生的列名不同,您可以使用“AS [Some_Alias_Name]”表达式为它们取别名。

  • 这是本线程中关于此功能如何工作的最佳视觉解释。 (2认同)

Jua*_*mez 5

如果您需要映射一个大型实体,那么编写每个字段一定是一项艰巨的任务。

我尝试了 @BlackjacketMack 答案,但我的一个表有一个 Id 列,其他表则没有(我知道这是数据库设计问题,但是......)然后这会在 dapper 上插入一个额外的分割,这就是为什么

select
p.*,

c.CustomerID AS Id,
c.*,

address.AddressID AS Id,
address.*,

country.CountryID AS Id,
country.*
Run Code Online (Sandbox Code Playgroud)

对我不起作用。然后我对此进行了一些更改,只需插入一个名称与表上的任何字段都不匹配的分割点,在可能的情况下更改as Idas _SplitPoint_最终的sql脚本如下所示:

select
p.*,

c.CustomerID AS _SplitPoint_,
c.*,

address.AddressID AS _SplitPoint_,
address.*,

country.CountryID AS _SplitPoint_,
country.*
Run Code Online (Sandbox Code Playgroud)

然后在 dapper 中添加一个 splitOn ,如下所示

cmd =
    "SELECT Materials.*, " +
    "   Product.ItemtId as _SplitPoint_," +
    "   Product.*, " +
    "   MeasureUnit.IntIdUM as _SplitPoint_, " +
    "   MeasureUnit.* " +
    "FROM   Materials INNER JOIN " +
    "   Product ON Materials.ItemtId = Product.ItemtId INNER JOIN " +
    "   MeasureUnit ON Materials.IntIdUM = MeasureUnit.IntIdUM " +
List < Materials> fTecnica3 = (await dpCx.QueryAsync<Materials>(
        cmd,
        new[] { typeof(Materials), typeof(Product), typeof(MeasureUnit) },
        (objects) =>
        {
            Materials mat = (Materials)objects[0];
            mat.Product = (Product)objects[1];
            mat.MeasureUnit = (MeasureUnit)objects[2];
            return mat;
        },
        splitOn: "_SplitPoint_"
    )).ToList();
Run Code Online (Sandbox Code Playgroud)