在我的大多数项目中,我使用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
类是“ 聚合根”
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类匹配属性,即使它们具有私有设置程序也是如此。干净整洁!
当您具有需要加载的一对多对象时,一切都会变得棘手。
比方说,现在的用户类有属于用户只读车列表,以及一些方法,你可以用它来添加/删除车:
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;
}
}