10

在我的大多数项目中,我使用 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 映射我的域实体?如果是这样,怎么做?

4

2 回答 2

17

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

小例子:

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

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

于 2013-03-08T22:08:42.140 回答
10

对于具有私有设置器的原始类型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类匹配属性,即使它们具有私有 setter。又好又干净!

人际关系问题

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

假设现在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类时如何加载汽车列表?

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

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;
    }
}
于 2019-04-11T21:13:05.163 回答