我正在努力将一些公共存储库基础结构合并到一些包含EF,L2SQL和WCF数据服务的应用程序中(尽管底层数据访问实现应该是任意的)。我已经完成了一些关于此事的阅读,但似乎找不到真正令人满意的例子。
我开始时:
public interface IRepository : IDisposable
{
IQueryable<T> Query<T>();
void Attach(object entity);
void ForDeletion(object entity);
void SaveChanges();
}
但我喜欢使用狭窄的域合同(http://codebetter.com/blogs/gregyoung/archive/2009/01/16/ddd-the-generic-repository.aspx)的存储库的想法。上面的内容让消费者知道存储库支持的所有实体类型。
我不会说这是不可能的,但我很难确信IQueryables本身不应该成为存储库合同的一部分。我不是锅炉板代码的粉丝,我坚信你拥有的越多,你引入的维护黑洞就越多。所以,我所说的是很难让我相信任何看起来像:
Public IEnumerable<Customer> GetCustomersWithFirstNameOf(string _Name) {
internalGenericRepository.FetchByQueryObject(new CustomerFirstNameOfQuery(_Name)); //could be hql or whatever
}
绝不是一个完全糟糕的想法。如果要搜索名字和姓氏,该怎么办?或名字或姓氏等。最终得到一个包含1000多个操作的存储库,其中一半重复相同的逻辑。注意:我不是调用代码应该负责应用所有过滤等等,而是有一个补充规范源对我有意义:
public static class CustomerSpecifications
{
public IQueryable<Customer> WithActiveSubscriptions(this IQueryable<Customer> customers, DateTime? start, DateTime? end)
{
// expression manipulation
return customers;
}
}
// bind some data source
repository.GetQueryable().WithActiveSubscriptions();
好的,所以继续前进,我认为让域模型显式存储库听起来像是一个好主意,采用以下格式:
public interface IRepository : IDisposable
{
void SaveChanges();
}
public interface IRepository<T>: IRepository
{
IQueryable<T> GetQueryable();
void Attach(T entity);
void ForDeletion(T entity);
}
然后
public class CustomerRepository:IRepository<Customer>
{
private ObjectContext _context;
// trivial implementation
}
但我的问题是它只允许我删除客户。我想删除客户地址的情况如何?也就是说,我使用存储库来查询Customer实体,但是然后想要删除myCustomer.CustomerAddresses [0]?我需要创建第二个存储库,只是为了附加和删除我想要的地址?
我想我的CustomerRepository可以是:
public class CustomerRepository:IRepository<Customer>, IRepository<CustomerAddress>
{
private ObjectContext _context;
// trivial implementation
}
这会让我重用存储库来删除CustomerAddresses,但是我不确定如何为图的每个部分继承IRepository<T>
我想要公开删除...
public class CustomerRepository:IRepository<Customer>, IRepository<CustomerAddress> /* this list may get pretty long, and then I really just have a masked EF ObjectContext, don't I? */
{
有人建议更好地实施吗?
答案 0 :(得分:2)
那里有很多问题,其中一些答案可能是主观的/选择性的,但我会用拳头打。但
IQueryable vs特定方法。
我实际上同意你的观点。我不喜欢有很多很多方法来定义我的存储库中的所有不同操作。我更喜欢允许调用代码提供规范。现在,我的调用代码不是我的演示文稿 - 它是域服务/ BLL。因此,它不像我们正在为UI提供全部功能,我们只是允许我们的存储库保持非常通用。 DDD纯粹主义者认为存储库是域模型的抽象,因此应该服务于特定的域操作。我会把它留给你决定。此外,如果您使用IQueryable
,那么您将强制使用“未知”LINQ提供程序。对我来说不是问题,因为我总是会使用LINQ(实体框架)之类的东西。但这是DDD纯粹主义者的另一个问题。
如果要搜索名字和姓氏
,该怎么办?
好吧,我使用Expression<Func<T,bool>>
作为我的域名服务,所以我可以这样做:
var customers = repository.FindAll<Customer>(x => x.FirstName == "Bob" && x.LastName == "Saget");
如果您不喜欢,可以使用规范模式来使用AND / OR类型代码。
删除客户地址
一般来说,每个聚合根应该有一个存储库。在不知道您的域模型的情况下,听起来“客户”是聚合根,而“CustomerAddress”是一个聚合,它总是与“客户”相关联(例如,没有客户)就不存在。
因此,您不需要CustomerAddressRepository。 CustomerAddress操作应该通过您的CustomerAddress存储库完成。
这就是为什么我想创建一个GenericRepository<T>
IRepository<T>
实现的实现,它在聚合(查找,添加,删除)上实现核心操作,然后甚至更具体的实现源自GenericRepository<T>
。
以下是我将如何处理客户存储库。创建GenericRepository<T>
,实现IRepository<T>
核心操作。然后为ICustomerRepository
创建另一个界面,该界面还实现IRepository<T>
。
现在,创建一个名为CustomerRepository
的实现,它继承了GenericRepository<T>
的核心存储库实现,并且还扩展了它以允许额外的操作(例如删除地址)。
public class CustomerRepository : GenericRepository<Customer>, ICustomerRepository
{
// inherits, for example:
// IQueryable<Customer> Query<Customer>();
public void Remove(Address address)
{
// get the customer (if you havent already got it)
var cust = ctx.Customers.SingleOrDefault(x => x.CustId == address.CustId);
cust.Addresses.Remove(address);
}
}
你的ICustomerRepository
看起来像这样:
public interface ICustomerRepository : GenericRepository<Customer>, IRepository<Customer>
{
// inherited from GenericRepository<Customer>
//IQueryable<T> Query<T>();
//void Attach(object entity);
//void ForDeletion(object entity);
//void SaveChanges();
void Remove(Address address);
}
知道我的意思吗?从真正的通用存储库开始,然后根据需要更具体。
您不需要CustomerAddressRepository。如上所示,通过CustomerRepository执行操作。
HTH。