【问题标题】:Mapping private attributes of domain entities with Dapper dot net使用 Dapper dot net 映射域实体的私有属性
【发布时间】:2013-03-08 17:33:44
【问题描述】:

在我的大多数项目中,我使用 nHibernate + Fluent 映射,最近我开始尝试使用 Dapper,看看是否可以将读取操作移至它。

我遵循 DDD 方法,因此我的域实体没有任何公共设置器。例如:

public class User
{
    private int _id;
    private string _name;
    private IList<Car> _carList;

    protected User(){} // Fluent Mapping

    public User(string id, string name)
    {
        // validation 
        // ...
        _id = id;
        _name = name;
    }

    public int Id{ get {return _id;} }
    public string Name { get {return _name;} }
    public IList<Car> CarList { get {return _carList;}}         
}

public class Car
{
    private int _id;
    private string _brand;

    protected Car(){} // Fluent Mapping

    public Car(string id, string brand)
    {
        // validation 
        // ...
        _id = id;
        _brand= brand;
    }

    public int Id{ get {return _id;} }
    public string Brand { get {return _brand;} }
}

使用 Fluent nHibernate,我可以显示成员以进行映射:

Id(Reveal.Member<User>("_id")).Column("id");
Map(Reveal.Member<User>("_name")).Column("name");

有没有办法用 Dapper 映射我的域实体?如果有,怎么做?

【问题讨论】:

  • 请注意,由 User 类公开的 IList 是一种 DDD 气味,就像 setter 一样:您应该公开一个 IEnumerable,因为在 aggregate's 状态上的所有操作都应该处理通过commands发送给它。

标签: c# orm domain-driven-design dapper fluent-nhibernate-mapping


【解决方案1】:

一个选项是创建一组单独的持久性类来使用 Dapper;例如:用户记录和汽车记录。记录类将匹配数据库表并将被封装在持久性模块中。 Dapper 查询将针对此类运行,然后您可以拥有一个单独的持久性工厂,它将组装域实体并将它们返回给客户端。

小例子:

        var carRecord = DbConnection.Query<CarRecord>("select * from cars where id = @id", new {id = 1});
        var carEntity = CarFactory.Build(carRecord);

这创造了一个很好的分离并提供了灵活性。

【讨论】:

  • +1 我可以确认此解决方案对custom repositories 非常有效。
  • 我考虑过这个解决方案,但是因为这让我编写了更多代码,所以我想知道是否有任何替代方案。感谢您的 cmets。
  • @R2D2:我想知道,你是如何实现的?我知道这是一篇相当老的帖子,但我陷入了类似的境地,并在搜索时发现了这篇文章。
  • @CoderAbsolute - 按照给出的答案创建工厂。但是,它会产生很多额外的工作。目前,我正在做越来越多的 CQRS,其中我将 nHibernate 用于没有公共设置器的实体,将 Dapper.NET 用于 DTO 进行查询。
【解决方案2】:

对于具有私有设置器的原始类型属性,Dapper 足够智能,可以自动映射它们,只要它们的名称与您从数据库/查询中获得的名称匹配。

假设您的User 类是聚合根

public class User : AggregateRoot
{
    public int Id { get; private set; }
    public string Name { get; private set; }

    ...
}

你有来自存储库的GetById 方法来重建用户

public class UserRepository : Repository<User>, IUserRepository
{
    private UserRepository(IDbConnection dbConnection) : base(dbConnection) {}

    ...

    public User GetById(int id)
    {
        const string sql = @"
            SELECT *
            FROM [User]
            WHERE ID = @userId;
        ";

        return base.DbConnection.QuerySingleOrDefault<User>(
            sql: sql,
            param: new { userId = id }
        );
    }
}

只要 sql 返回 IdName 列,它们就会自动映射到您的 User 类匹配属性,即使它们有私有设置器。又好又干净!

人际关系问题

当您需要加载一对多的对象时,一切都会变得棘手。

现在假设 User 类有一个属于 User 的只读汽车列表,以及一些可用于添加/删除汽车的方法:

public class User : AggregateRoot
{
    public int Id { get; private set; }
    public string Name { get; private set; }

    private readonly IList<Car> _cars = new List<Car>();
    public IEnumerable<Car> Cars => _cars;

    public void PurchaseCar(Car car)
    {
        _cars.Add(car);
 
        AddEvent(new CarPurchasedByUser { ... });
    }

    public void SellCar(Car car)
    {
        _cars.Remove(car);

        AddEvent(new CarSoldByUser { ... });
    }
}

public class Car : Entity
{
    public int Id { get; private set; }
    public string Brand { get; private set; }
}

现在User类被构造时如何加载汽车列表?

有些人建议只运行多个查询,然后在构造用户后通过调用 PurchaseCarSellCar 方法(或类中可用的任何方法)添加/删除汽车来构造汽车列表:

public User GetById(int id)
{
    const string sql = @"
        SELECT *
        FROM [User]
        WHERE ID = @userId;

        SELECT *
        FROM [Car]
        WHERE UserID = @userId;
    ";

    using (var multi = base.DbConnection.QueryMultiple(sql, new { userId = id })
    {
        var user = multi.Read<User>()
            .FirstOrDefault();

        if (user != null)
        {
            var cars = multi.Read<Car>();

            foreach (var car in cars)
            {
                user.PurchaseCar(car);
            }
        }

        return user;
    }
}

但是如果你正在练习Domain-Driven Design,你真的不能这样做,因为这些方法通常会触发额外的事件,这些事件可能会被其他人订阅以触发其他命令。你只是想初始化你的 User 对象。

用反射解决它

唯一对我有用的是使用System.Reflection

public User GetById(int id)
{
    const string sql = @"
        SELECT *
        FROM [User]
        WHERE ID = @userId;

        SELECT *
        FROM [Car]
        WHERE UserID = @userId;
    ";

    using (var multi = base.DbConnection.QueryMultiple(sql, new { userId = id })
    {
        var user = multi.Read<User>()
            .FirstOrDefault();

        if (user != null)
        {
            var cars = multi.Read<Car>();

            // Load user car list using Reflection
            var privateCarListField = user.GetType()
                .GetField("_cars", BindingFlags.NonPublic | BindingFlags.Instance);

            privateCarListField.SetValue(car, cars);
        }

        return user;
    }
}

【讨论】:

  • 这是我采用的方法,因为我也不喜欢必须创建一大堆“记录”类的想法,这是公认的答案 - IMO 这应该是公认的答案这个问题。
猜你喜欢
  • 1970-01-01
  • 2021-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-20
相关资源
最近更新 更多