使用Dapper dot net映射域实体的私有属性

问题描述 投票:5回答:2

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

c# orm domain-driven-design dapper fluent-nhibernate-mapping
2个回答
9
投票

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

小例子:

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

这创造了良好的分离并提供了灵活性。


1
投票

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

让我们说你的User类是Aggregate Root

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返回Id和Name列,它们就会自动映射到User类匹配属性,即使它们具有私有setter也是如此。干净整洁!

The problem comes with you have relationships

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

现在假设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对象。

Reflection!

唯一对我有用的是使用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;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.