【问题标题】:Dapper - Handling custom mapping for ddd entity with read-only fields via constructorDapper - 通过构造函数处理具有只读字段的 ddd 实体的自定义映射
【发布时间】:2017-08-12 20:25:46
【问题描述】:

我有以下课程,我正在尝试补水:

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 名称、System.String 描述、System.UInt64 IsArchived) 是 Domain.Model.Products.Product 所必需的 物化'

我研究过使用自定义的SqlMapper.TypeHandler&lt;Product&gt;,但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 阅读器列(和类型)

这相当于VendorShortname 用于VendorId(string vendorShortname)NameDescriptionisArchived 用于公共Product(Sku sku, string name, string description, bool isArchived)...根据我在以下链接,Dapper 手动映射等价物会很棒MongoDB Composite Key: InvalidOperationException: {document}.Identity is not supported

【问题讨论】:

  • but i would rather (must) have this concern in the mapping code rather than in my domain model. 你能告诉我们为什么你必须这样吗?
  • 对于其他存储库 (MongoDB),我的域模型中没有任何持久性问题,我希望尽可能使用 SQL/Dapper,因为它使事情变得更简单。将更新我的帖子,了解需要做什么,可以自己动手,但不想重新发明轮子,也不能像 Dapper 那样编写代码

标签: c# orm dapper


【解决方案1】:

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)
            );
}

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

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

【讨论】:

    【解决方案2】:

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

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

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

    一些缺点:更多的代码,对于小型项目来说太过分了

    【讨论】:

    • 谢谢,是的,对 POCO 记录和使用自动映射器做了类似的事情,但双课可能会很痛苦。当然是要指出的一个有效选项。
    猜你喜欢
    • 1970-01-01
    • 2012-03-29
    • 1970-01-01
    • 2012-03-20
    • 1970-01-01
    • 2013-05-15
    • 1970-01-01
    • 1970-01-01
    • 2013-01-14
    相关资源
    最近更新 更多