it-swarm.cn

在小巧玲珑中正确使用Multimapping

我正在尝试使用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; }
}

我的短小精悍的代码如下

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。我想念 - 了解多重映射功能的核心功能。我不想每次都要添加完整的列名列表。

93
Richard Forrest

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

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。如果有多个拆分点,则需要将它们添加到逗号分隔列表中。

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

 ProductID |产品名称| AccountOpened | CustomerId |顾客姓名 
 -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - -    -  - - -------------------- 

Dapper需要知道如何将此顺序中的列拆分为2个对象。粗略的外观显示客户从CustomerId列开始,因此splitOn: CustomerId

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

 ProductID |产品名称| AccountOpened |客户名称|顾客ID  
 -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - -    -  - - -------------------- 

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

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

158
Sam Saffron

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

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

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

我们所做的,多年来成千上万的查询都很有用,只是使用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.*

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

21
BlackjacketMack

还有一点需要注意。如果CustomerId字段为null(通常在具有左连接的查询中),则Dapper使用Customer = null创建ProductItem。在上面的例子中:

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会创建并填充相关对象(在本例中为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");  
2
Frantisek Bachan

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

一些缺点是:

  • 这假定您的外键属性是您的子对象的名称+“Id”,例如单元ID。
  • 我只将1个子对象映射到父对象。

代码:

    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;
    }
1
Dylan Hayes

假设以下sql查询结构(列名表示,值无关)

col_1 col_2 col_3 | col_n col_m | col_A col_B col_C | col_9 col_8

因此,在精巧的时候,您将使用以下Query(QueryAsync)定义

Query<TFirst, TSecond, TThird, TFourth, TResut> (
    sql : query,
    map: Func<TFirst, TSecond, TThird, TFourth, TResut> myFunc,
    parma: optional,
    splitOn: "col_3, col_n, col_A, col_9")

我们希望TFirst将第一部分映射到第二部分第二部分。

SplitOn表达式转换为:

将所有列映射到TFrist,直到找到名为或别名为“col_3”的列,同时将列包含在映射中。

然后映射到从col_n开始到结束的TSecond或找到新的分隔符(也包括它到映射col_n中)

然后映射到TThird,从col_A开始直到结束或找到新的分隔符(也将其包含在映射col_A中)

然后映射到TFourth,从col_9开始直到结束或找到新的分隔符(也将它包含在映射col_9中)

Sql查询的列和映射对象的props以1:1的关系(意味着它们应该被命名为相同),如果sql查询产生的列名不同,则将使用别名AS [Some_Alias_Name]

1
MCR