通用存储库

时间:2010-11-28 21:40:48

标签: linq entity-framework repository

我正在努力将一些公共存储库基础结构合并到一些包含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? */
{

有人建议更好地实施吗?

1 个答案:

答案 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。