在我的大多数项目中,我使用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。记录类将匹配db表,并将封装在持久性模块中。 Dapper查询将针对这些类运行,然后您可以拥有一个单独的持久性工厂,它将组装域实体并将它们返回给客户端。
小例子:
var carRecord = DbConnection.Query<CarRecord>("select * from cars where id = @id", new {id = 1});
var carEntity = CarFactory.Build(carRecord);
这创造了良好的分离并提供了灵活性。
对于具有私有设置器的属性,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也是如此。干净整洁!
当你需要加载一对多对象时,一切都变得棘手。
现在假设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(car, cars);
}
return user;
}
}