关于asp.net:为每个对象创建通用存储库与特定存储库的优势?

Advantage of creating a generic repository vs. specific repository for each object?

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

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

1
2
3
4
5
6
7
8
9
10
11
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();
}

每个存储库都将实现此接口,例如:

  • 客户报告:报告
  • 产品存储库:iRepository
  • 等。

我们在之前的项目中采用的备选方案是:

1
2
3
4
5
6
7
8
9
10
11
12
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);
}

在第二种情况下,表达式(linq或其他形式)将完全包含在存储库实现中,实现服务的人只需要知道要调用哪个存储库函数。

我想我没有看到在服务类中编写所有表达式语法并传递到存储库的好处。这难道不意味着在许多情况下很容易弄乱LINQ代码吗?

例如,在我们的旧发票系统中,我们调用

1
InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)

从一些不同的服务(客户、发票、账户等)。这似乎比在多个地方写下以下内容要干净得多:

1
rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);

我看到的使用特定方法的唯一缺点是,我们最终可能得到许多get*函数的排列,但这似乎比将表达式逻辑推到服务类中更可取。

我错过了什么?


这是一个与存储库模式本身一样古老的问题。Linq的IQueryable是一个查询的统一表示,最近它的引入引起了很多关于这个主题的讨论。

在努力构建通用存储库框架之后,我自己更喜欢特定的存储库。不管我尝试了什么聪明的机制,我最终总是遇到同样的问题:存储库是被建模的域的一部分,而该域不是通用的。不是每个实体都可以删除,不是每个实体都可以添加,不是每个实体都有存储库。查询变化很大;存储库API和实体本身一样独特。

我经常使用的模式是具有特定的存储库接口,但是实现的基类。例如,使用Linq to SQL,可以执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
    }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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);
    }
}

请注意,存储库的公共API不允许删除用户。另外,暴露IQueryable是另一个完全不同的蠕虫罐头——在这个问题上,有和肚脐一样多的观点。


实际上我对布赖恩的职位有点不同意。我认为他是对的,最终一切都是独一无二的,等等。但是同时,大多数的设计都是在你设计的时候出现的,我发现在开发我的模型的时候,建立一个通用的存储库并使用它,我可以很快地得到一个应用程序,然后在我发现有必要的时候重构到更大的特定性。

因此,在这种情况下,我经常创建一个具有完整CRUD堆栈的通用IRepository,它让我能够快速地使用API,让人们使用UI,并并行进行集成和用户接受测试。然后,当我发现我需要对回购等进行特定的查询时,如果需要,我开始用特定的依赖项替换这个依赖项,然后从那里开始。一个潜在的IMPL。易于创建和使用(并且可能连接到内存中的数据库、静态对象、模拟对象或其他对象)。

也就是说,我最近开始做的就是打破这种行为。因此,如果您为IDataFetcher、IDataUpdater、IDataInserter和IDataDeleter(例如)做接口,您可以通过接口混合和匹配来定义您的需求,然后使用能够处理部分或全部需求的实现,并且我仍然可以在构建应用程序时注入要使用的Do-It-All实现。

保罗


我更喜欢从通用存储库(或通用存储库列表)派生的具有可重写方法签名的特定存储库。


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


公共类用户存储库:存储库,iuserrepository

您不应该注入iuserrepository以避免暴露接口吗?正如人们所说,你可能不需要完整的积垢堆等。