Dapper - 通过构造函数处理具有只读字段的ddd实体的自定义映射

时间:2017-08-12 20:25:46

标签: c# orm dapper

我有以下课程,我试图补充:

public class Product
{
    public readonly Sku Sku;
    public string Name { get; private set; }
    public string Description { get; private set; }
    public bool IsArchived { get; private set; }

    public Product(Sku sku, string name, string description, bool isArchived)
    {
        Sku = sku;
        Name = name;
        Description = description;
        IsArchived = isArchived;
    }
}

使用以下类来实现我的DDD实体域模型中的概念(删除不相关的代码以保持代码简短,设置为只读以在构造后使其成为不可变):

public class Sku
{
    public readonly VendorId VendorId;
    public readonly string SkuValue;

    public Sku(VendorId vendorId, string skuValue)
    {
        VendorId = vendorId;
        SkuValue = skuValue;
    }
}

public class VendorId
{
    public readonly string VendorShortname;

    public VendorId(string vendorShortname)
    {
        VendorShortname = vendorShortname;
    }
}

我尝试运行参数化查询,该查询将水合到Product对象:

using (connection)
{
    connection.Open();
    return connection.QueryFirst<Product>(ReadQuery, new { VendorId = sku.VendorId.VendorShortname, SkuValue = sku.SkuValue });
}

它抛出以下异常,因为它不知道如何处理构造函数中的Sku类型:

  

System.InvalidOperationException:'无参数的默认构造函数   或一个匹配的签名(System.String VendorId,System.String   SkuValue,System.String Name,System.String描述,System.UInt64   Domain.Model.Products.Product需要IsArchived   物化'

我研究了使用自定义SqlMapper.TypeHandler<Product>,但Parse(object value)只传递了来自VendorId数据库列的单个解析值(如果它传入了一个值数组,我可以自己做映射。)

有没有办法自定义对象的处理,以便我可以将所有参数传递给构造函数,如下所示:

using (connection)
{
    var command = connection.CreateCommand();
    command.CommandText = "SELECT VendorShortname, SkuValue, Name, Description, IsArchived FROM Products WHERE VendorShortname=@VendorShortname AND SkuValue=@SkuValue";
    command.Parameters.AddWithValue("@VendorShortname", sku.VendorId.VendorShortname);
    command.Parameters.AddWithValue("@SkuValue", sku.SkuValue);
    connection.Open();

    var reader = command.ExecuteReader();
    if (reader.HasRows==false)
        return null;

    reader.Read();

    return new Product(
        new Sku(new VendorId(reader.GetString("VendorId")),reader.GetString("SkuValue")),
        reader.GetString("Name"),
        reader.GetString("Description"),
        reader.GetBoolean("IsArchived"));
}

我想我可以用Product(string VendorShortname, string SkuValue, string Name, string Description, UInt64 IsArchived)创建一个特定的构造函数,但我宁愿(必须)在映射代码中而不是在我的域模型中有这个问题。

重复一些伪代码,我能做的就是滚动自己的ORM,但是想要通过Dapper做类似的事。

  1. 通过反射获取对象的所有构造函数
  2. 如果构造函数中的任何参数是类型,请获取其构造函数
  3. 对于每个构造函数(包括参数),将构造函数名称映射到SQL reader列(并键入)
  4. 这相当于用于VendorShortname的{​​{1}},以及用于公开VendorId(string vendorShortname)的{​​{1}},NameDescription ......同样由MongoDB根据我在以下链接中发布的答案完成,一个Dapper手动映射等效将是很棒的MongoDB Composite Key: InvalidOperationException: {document}.Identity is not supported

2 个答案:

答案 0 :(得分:3)

  

Execute a query and map it to a list of dynamic objects

public static IEnumerable<dynamic> Query (
    this IDbConnection cnn, 
    string sql, 
    object param = null, 
    SqlTransaction transaction = null, 
    bool buffered = true
)

然后,您将使用动态对象列表构建所需的模型。

因此,使用原始帖子中的示例,参数化查询将从...更改

using (connection)
{
    var command = connection.CreateCommand();
    command.CommandText = "SELECT VendorShortname, SkuValue, Name, Description, IsArchived FROM Products WHERE VendorShortname=@VendorShortname AND SkuValue=@SkuValue";
    command.Parameters.AddWithValue("@VendorShortname", sku.VendorId.VendorShortname);
    command.Parameters.AddWithValue("@SkuValue", sku.SkuValue);
    connection.Open();

    var reader = command.ExecuteReader();
    if (reader.HasRows==false)
        return null;

    reader.Read();

    return new Product(
        new Sku(new VendorId(reader.GetString("VendorId")),reader.GetString("SkuValue")),
        reader.GetString("Name"),
        reader.GetString("Description"),
        reader.GetBoolean("IsArchived"));
}

要...

var ReadQuery = "SELECT VendorShortname, SkuValue, Name, Description, IsArchived FROM Products WHERE VendorShortname=@VendorShortname AND SkuValue=@SkuValue";
using (connection) {
    connection.Open();
    return connection.Query(ReadQuery, new { VendorShortname = sku.VendorId.VendorShortname, SkuValue = sku.SkuValue })
            .Select(row => new Product(
                new Sku(new VendorId(row.VendorShortname), row.SkuValue),
                row.Name,
                row.Description,
                row.IsArchived)
            );
}

该框架的预期目的是什么。只需确保使用的属性直接映射到查询返回的字段。

这可能看起来很密集,但考虑到目标对象构造函数的复杂性,这是一个可行的解决方案。

答案 1 :(得分:1)

您还可以考虑将“域”模型与持久性分离并在其他地方构建它们的选项。例如:

  • 为每个数据库记录创建一个类:ProductRecord
  • 创建工厂:ProductFactory
  • 获取数据:var productRecords = connection.Query<ProductRecord>("select * from products").AsList();
  • 构建产品:factory.Build(productRecords)

一些优点:关注点分离,灵活性,适合大型项目

一些缺点:更多代码,小型项目的过度杀戮