为每个对象创建通用存储库与特定存储库的优势?

Bee*_*eep 131 architecture asp.net asp.net-mvc

我们正在开发ASP.NET MVC应用程序,现在正在构建存储库/服务类.我想知道创建一个所有存储库实现的通用IRepository接口是否有任何重大优势,而每个存储库都有自己独特的接口和方法集.

例如:一个通用的IRepository接口可能看起来像(取自这个答案):

public interface IRepository : IDisposable
{
    T[] GetAll<T>();
    T[] GetAll<T>(Expression<Func<T, bool>> filter);
    T GetSingle<T>(Expression<Func<T, bool>> filter);
    T GetSingle<T>(Expression<Func<T, bool>> filter, List<Expression<Func<T, object>>> subSelectors);
    void Delete<T>(T entity);
    void Add<T>(T entity);
    int SaveChanges();
    DbTransaction BeginTransaction();
}
Run Code Online (Sandbox Code Playgroud)

每个Repository都会实现此接口,例如:

  • CustomerRepository:IRepository
  • ProductRepository:IRepository
  • 等等

我们在之前的项目中遵循的备选方案是:

public interface IInvoiceRepository : IDisposable
{
    EntityCollection<InvoiceEntity> GetAllInvoices(int accountId);
    EntityCollection<InvoiceEntity> GetAllInvoices(DateTime theDate);
    InvoiceEntity GetSingleInvoice(int id, bool doFetchRelated);
    InvoiceEntity GetSingleInvoice(DateTime invoiceDate, int accountId); //unique
    InvoiceEntity CreateInvoice();
    InvoiceLineEntity CreateInvoiceLine();
    void SaveChanges(InvoiceEntity); //handles inserts or updates
    void DeleteInvoice(InvoiceEntity);
    void DeleteInvoiceLine(InvoiceLineEntity);
}
Run Code Online (Sandbox Code Playgroud)

在第二种情况下,表达式(LINQ或其他)将完全包含在Repository实现中,无论谁实现该服务,只需要知道要调用哪个存储库函数.

我想我没有看到在服务类中编写所有表达式语法并传递给存储库的优势.在许多情况下,这是否意味着容易混乱的LINQ代码?

例如,在我们的旧发票系统中,我们打电话

InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)
Run Code Online (Sandbox Code Playgroud)

来自几个不同的服务(客户,发票,帐户等).这似乎比在多个地方写下以下内容更清晰:

rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);
Run Code Online (Sandbox Code Playgroud)

我看到使用特定方法的唯一缺点是我们最终可能会得到许多Get*函数的排列,但这仍然似乎比将表达式逻辑推送到Service类更好.

我错过了什么?

Bry*_*tts 165

这是一个与Repository模式本身一样古老的问题.最近引入的LINQ IQueryable是查询的统一表示,引发了很多关于这个主题的讨论.

在努力构建通用存储库框架之后,我自己更喜欢特定的存储库.无论我尝试过哪种聪明的机制,我总是遇到同样的问题:存储库是被建模的域的一部分,并且该域不是通用的.并非每个实体都可以删除,不是每个实体都可以添加,并非每个实体都有一个存储库.查询变化很大; 存储库API变得与实体本身一样唯一.

我经常使用的模式是具有特定的存储库接口,但是实现的基类.例如,使用LINQ to SQL,您可以:

public abstract class Repository<TEntity>
{
    private DataContext _dataContext;

    protected Repository(DataContext dataContext)
    {
        _dataContext = dataContext;
    }

    protected IQueryable<TEntity> Query
    {
        get { return _dataContext.GetTable<TEntity>(); }
    }

    protected void InsertOnCommit(TEntity entity)
    {
        _dataContext.GetTable<TEntity>().InsertOnCommit(entity);
    }

    protected void DeleteOnCommit(TEntity entity)
    {
        _dataContext.GetTable<TEntity>().DeleteOnCommit(entity);
    }
}
Run Code Online (Sandbox Code Playgroud)

替换DataContext为您选择的工作单元.示例实现可能是:

public interface IUserRepository
{
    User GetById(int id);

    IQueryable<User> GetLockedOutUsers();

    void Insert(User user);
}

public class UserRepository : Repository<User>, IUserRepository
{
    public UserRepository(DataContext dataContext) : base(dataContext)
    {}

    public User GetById(int id)
    {
        return Query.Where(user => user.Id == id).SingleOrDefault();
    }

    public IQueryable<User> GetLockedOutUsers()
    {
        return Query.Where(user => user.IsLockedOut);
    }

    public void Insert(User user)
    {
        InsertOnCommit(user);
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,存储库的公共API不允许删除用户.此外,暴露IQueryable是另一种蠕虫 - 在该主题上有与肚脐一样多的意见.

  • "存储库是被建模的域的一部分,并且该域不是通用的.不是每个实体都可以删除,不是每个实体都可以添加,不是每个实体都有一个存储库"完美! (35认同)
  • 说真的,很好的答案.谢谢! (9认同)
  • 那么你将如何使用IoC/DI呢?(我是IoC的新手)关于你的模式的完整问题:http://stackoverflow.com/questions/4312388/how-should-i-use-ioc-di-with-this-repository-pattern (5认同)

Pau*_*aul 26

我实际上与布莱恩的帖子略有不同意见.我认为他是对的,最终一切都非常独特等等.但与此同时,大部分内容都是在你设计的时候出现的,而且我发现在开发我的模型的同时获得一个通用的存储库并使用它,我可以很快得到一个应用程序,然后重新调整到更高的特异性,因为我发现需要这样做.

因此,在这种情况下,我经常创建一个具有完整CRUD堆栈的通用IRepository,这使我能够快速使用API​​并让人们玩UI并同时进行集成和用户验收测试.然后,当我发现我需要对repo等进行特定查询时,如果需要,我会从那里开始替换那个特定的依赖关系.一个潜在的影响.很容易创建和使用(可能挂钩到内存中的数据库或静态对象或模拟对象或其他).

那就是说,我最近开始做的就是打破这种行为.因此,如果您为IDataFetcher,IDataUpdater,IDataInserter和IDataDeleter(例如)执行接口,您可以通过接口混合并匹配来定义您的需求,然后实现处理其中的部分或全部,并且我可以我正在构建应用程序时仍然注入使用它的所有实现.

保罗

  • 此外,通用查询机制是ORM的责任.您的存储库应该通过*使用*通用查询机制来实现项目实体的特定查询.不应强制存储库的使用者编写自己的查询,除非这是您的问题域的一部分,例如报告. (9认同)
  • 感谢@Paul的回复.我实际上也尝试过这种方法.我无法弄清楚如何一般地表达我尝试的第一种方法,`GetById()`.我应该使用`IRepository <T,TId>`,`GetById(object id)`,还是做出假设并使用`GetById(int id)`?复合键如何工作?我想知道ID的通用选择是否值得抽象.如果没有,那么通用存储库还会被强制表达什么呢?这是抽象*实现*背后的推理线,而不是*interface*. (4认同)
  • @Bryan - 关于你的GetById().我使用FindById <T,TId>(TId id); 因此导致类似repository.FindById <Invoice,int>(435); (2认同)

Arn*_*psa 13

我更喜欢使用可覆盖的方法签名从通用存储库(或通用存储库列表中指定确切行为)派生的特定存储库.

  • @JesseWebb Naah ...随着丰富的域模型查询逻辑显着简化.例如,如果我想搜索已购买东西的用户,我只搜索users.Where(u => u.HasPurchasedAnything)而不是users.Join(x => Orders,东西,我不知道linq).Where( order => order.Status == 1).Join(x => x.products).Where(x .... etc etc etc .... blah blah blah blah (3认同)

小智 5

拥有由特定存储库包装的通用存储库.这样您就可以控制公共接口,但仍然具有代码重用的优势,而代码重用来自于具有通用存储库.