用Dapper dot net映射域实体的私有属性
本文关键字:属性 实体 Dapper dot net 映射 | 更新日期: 2023-09-27 18:03:13
在我的大多数项目中,我使用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?如果有,怎么做?
一种选择是创建一组单独的持久化类来与Dapper一起工作;例如:UserRecord和CarRecord。记录类将匹配数据库表,并封装在持久化模块中。Dapper查询将针对这些类运行,然后你可以有一个单独的持久化工厂,它将组装域实体并将它们返回给客户端。
小例子:
var carRecord = DbConnection.Query<CarRecord>("select * from cars where id = @id", new {id = 1});
var carEntity = CarFactory.Build(carRecord);
对于具有私有setter的基本类型属性,Dapper
足够智能地自动映射它们,只要它们的名称与您从数据库/查询中获得的名称匹配。
假设User
类是聚合根
public class User : AggregateRoot
{
public int Id { get; private set; }
public string Name { get; private set; }
...
}
你有GetById
方法从存储库重建User
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类匹配属性,即使它们具有私有设置。又漂亮又干净!
人际关系中的问题
当你需要加载一对多对象时,一切都变得棘手。
假设现在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类被构造时,如何加载汽车列表呢?
有人建议只运行多个查询,然后通过调用PurchaseCar
和SellCar
方法(或类中可用的任何方法)构建用户后构建汽车列表来添加/删除汽车:
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(user, cars);
}
return user;
}
}