Lợi thế của việc tạo một kho lưu trữ chung so với kho lưu trữ cụ thể cho từng đối tượng?


132

Chúng tôi đang phát triển một ứng dụng ASP.NET MVC và hiện đang xây dựng các lớp kho lưu trữ / dịch vụ. Tôi tự hỏi liệu có bất kỳ lợi thế lớn nào để tạo giao diện IRep repository chung mà tất cả các kho lưu trữ đều thực hiện, so với mỗi Kho lưu trữ có giao diện và bộ phương thức riêng.

Ví dụ: giao diện IRep repository chung có thể trông giống như (lấy từ câu trả lời này ):

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();
}

Mỗi Kho lưu trữ sẽ triển khai giao diện này, ví dụ:

  • Kho lưu trữ khách hàng: Kho lưu trữ
  • ProductRep repository: IRep repository
  • Vân vân.

Sự thay thế mà chúng tôi đã theo dõi trong các dự án trước sẽ là:

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);
}

Trong trường hợp thứ hai, các biểu thức (LINQ hay nói cách khác) sẽ hoàn toàn được chứa trong triển khai Kho lưu trữ, bất cứ ai đang triển khai dịch vụ chỉ cần biết chức năng kho lưu trữ nào sẽ gọi.

Tôi đoán tôi không thấy lợi thế của việc viết tất cả cú pháp biểu thức trong lớp dịch vụ và chuyển đến kho lưu trữ. Điều này có nghĩa là mã LINQ dễ gây rối sẽ bị trùng lặp trong nhiều trường hợp?

Ví dụ: trong hệ thống lập hóa đơn cũ, chúng tôi gọi

InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)

từ một vài dịch vụ khác nhau (Khách hàng, Hóa đơn, Tài khoản, v.v.). Điều đó có vẻ sạch sẽ hơn nhiều so với viết sau đây ở nhiều nơi:

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

Nhược điểm duy nhất tôi thấy khi sử dụng cách tiếp cận cụ thể là chúng ta có thể kết thúc với nhiều hoán vị của các hàm Get *, nhưng điều này có vẻ vẫn thích hợp hơn để đẩy logic biểu thức lên các lớp Dịch vụ.

Tôi đang thiếu gì?


Sử dụng kho lưu trữ chung với ORM đầy đủ có vẻ vô dụng. Tôi đã thảo luận chi tiết ở đây .
Amit Joshi

Câu trả lời:


169

Đây là một vấn đề cũ như chính mẫu Kho lưu trữ. Sự giới thiệu gần đây của LINQ IQueryable, một đại diện thống nhất của một truy vấn, đã gây ra nhiều cuộc thảo luận về chính chủ đề này.

Tôi thích các kho lưu trữ cụ thể hơn, sau khi đã làm việc rất chăm chỉ để xây dựng một khung kho lưu trữ chung. Bất kể cơ chế thông minh nào tôi đã thử, tôi luôn gặp phải cùng một vấn đề: kho lưu trữ là một phần của miền được mô hình hóa và miền đó không chung chung. Không phải mọi thực thể đều có thể bị xóa, không phải mọi thực thể đều có thể được thêm vào, không phải mọi thực thể đều có một kho lưu trữ. Truy vấn khác nhau rất nhiều; API kho lưu trữ trở nên độc đáo như chính thực thể.

Một mẫu tôi thường sử dụng là có các giao diện kho lưu trữ cụ thể, nhưng một lớp cơ sở cho việc triển khai. Ví dụ: sử dụng LINQ to SQL, bạn có thể làm:

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);
    }
}

Thay thế DataContextvới đơn vị công việc của bạn lựa chọn. Một ví dụ thực hiện có thể là:

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);
    }
}

Lưu ý API công khai của kho lưu trữ không cho phép người dùng bị xóa. Ngoài ra, phơi bày IQueryablelà một loạt sâu khác - có nhiều ý kiến ​​như rốn về chủ đề đó.


9
Nghiêm túc, câu trả lời tuyệt vời. Cảm ơn!
bíp bíp

5
Vậy bạn sẽ sử dụng IoC / DI như thế nào với điều này? (Tôi là người mới làm việc tại IoC) Câu hỏi của tôi liên quan đến mẫu của bạn đầy đủ: stackoverflow.com/questions/4312388/ chủ
dan

36
"kho lưu trữ là một phần của tên miền đang được mô hình hóa và tên miền đó không chung chung. Không phải mọi thực thể đều có thể bị xóa, không phải mọi thực thể đều có thể được thêm vào, không phải mọi thực thể đều có kho lưu trữ" hoàn hảo!
adamwtiko

Tôi biết đây là một câu trả lời cũ, nhưng tôi tò mò nếu bỏ qua một phương thức Cập nhật từ lớp Kho lưu trữ có chủ ý. Tôi gặp khó khăn khi tìm một cách sạch sẽ để làm điều này.
rtf

1
Oldie nhưng là một người tốt. Bài đăng này là vô cùng khôn ngoan và nên được đọc và đọc lại nhưng tất cả các nhà phát triển tốt để làm. Cảm ơn @BryanWatts. Việc triển khai của tôi thường dựa trên dapper, nhưng tiền đề là như nhau. Kho lưu trữ cơ sở, với các kho lưu trữ cụ thể để thể hiện tên miền chọn tham gia các tính năng.
pimbrouwers

27

Tôi thực sự không đồng ý với bài viết của Bryan. Tôi nghĩ anh ấy đúng, cuối cùng mọi thứ đều rất độc đáo và cứ thế. Nhưng đồng thời, hầu hết trong số đó xuất hiện khi bạn thiết kế và tôi thấy rằng việc có một kho lưu trữ chung và sử dụng nó trong khi phát triển mô hình của mình, tôi có thể tải ứng dụng lên rất nhanh, sau đó tái cấu trúc để cụ thể hơn khi tôi tìm thấy cần phải làm như vậy

Vì vậy, trong những trường hợp như vậy, tôi thường tạo một Kho lưu trữ chung có ngăn xếp CRUD đầy đủ và cho phép tôi nhanh chóng chơi với API và cho phép mọi người chơi w / UI và kiểm tra sự chấp nhận của người dùng và tích hợp. Sau đó, khi tôi thấy tôi cần các truy vấn cụ thể trên repo, v.v., tôi bắt đầu thay thế sự phụ thuộc đó với phụ thuộc cụ thể nếu cần và đi từ đó. Một ẩn ý ngầm. rất dễ tạo và sử dụng (và có thể móc vào db trong bộ nhớ hoặc các đối tượng tĩnh hoặc các đối tượng bị chế giễu hoặc bất cứ thứ gì).

Điều đó nói rằng, những gì tôi đã bắt đầu làm gần đây là phá vỡ hành vi. Vì vậy, nếu bạn thực hiện các giao diện cho IDataFetcher, IDataUpdater, IDataInserter và IDataDeleter (ví dụ), bạn có thể trộn và khớp để xác định các yêu cầu của mình thông qua giao diện và sau đó có các triển khai chăm sóc một số hoặc tất cả chúng, và tôi có thể vẫn sử dụng triển khai thực hiện tất cả để sử dụng trong khi tôi đang xây dựng ứng dụng.

paul


4
Cảm ơn đã trả lời @Paul. Tôi thực sự đã thử cách tiếp cận đó là tốt. Tôi không thể tìm ra cách diễn đạt một cách khái quát phương pháp đầu tiên tôi đã thử , GetById(). Tôi nên sử dụng IRepository<T, TId>, GetById(object id)hoặc đưa ra các giả định và sử dụng GetById(int id)? Làm thế nào các phím tổng hợp hoạt động? Tôi tự hỏi nếu một lựa chọn chung bằng ID là một sự trừu tượng đáng giá. Nếu không, các kho lưu trữ chung khác sẽ bị buộc phải thể hiện bất thường? Đó là dòng lý luận đằng sau trừu tượng hóa việc thực hiện , không phải giao diện .
Bryan Watts

10
Ngoài ra, một cơ chế truy vấn chung là trách nhiệm của ORM. Các kho lưu trữ của bạn nên triển khai các truy vấn cụ thể cho các thực thể của dự án bằng cách sử dụng cơ chế truy vấn chung. Người tiêu dùng của kho lưu trữ của bạn không nên bị buộc phải viết các truy vấn của riêng họ trừ khi đó là một phần của miền vấn đề của bạn, chẳng hạn như với báo cáo.
Bryan Watts

2
@Bryan - Liên quan đến GetById () của bạn. Tôi sử dụng FindById <T, TId> (id TId); Do đó, kết quả là một cái gì đó như repository.FindById <Invoice, int> (435);
Joshua Hayes

Tôi thường không đặt các phương thức truy vấn cấp trường trên các giao diện chung. Như bạn đã chỉ ra, không phải tất cả các mô hình đều được truy vấn bởi một khóa duy nhất và trong một số trường hợp, ứng dụng của bạn sẽ không bao giờ lấy một cái gì đó bằng ID (ví dụ: nếu bạn đang sử dụng các khóa chính do DB tạo và bạn chỉ tìm nạp theo một khóa tự nhiên, ví dụ tên đăng nhập). Các phương thức truy vấn phát triển trên các giao diện cụ thể mà tôi xây dựng như là một phần của tái cấu trúc.
Paul

13

Tôi thích các kho lưu trữ cụ thể xuất phát từ kho lưu trữ chung (hoặc danh sách các kho lưu trữ chung để chỉ định hành vi chính xác) với các chữ ký phương thức có thể ghi đè.


Bạn có thể vui lòng cung cấp một ví dụ nhỏ về đoạn trích không?
Johann Gerell

@Johann Gerell không, vì tôi không sử dụng kho lưu trữ nữa.
Arnis Lapsa

Những gì bạn sử dụng bây giờ mà bạn tránh xa các kho lưu trữ?
Chris

@Chris tôi tập trung rất nhiều vào việc có mô hình miền phong phú. phần đầu vào của ứng dụng là những gì quan trọng. nếu tất cả các thay đổi trạng thái được theo dõi cẩn thận, thì việc bạn đọc dữ liệu bao nhiêu cũng không thành vấn đề. vì vậy tôi chỉ cần sử dụng NHibernate ISession trực tiếp. không có sự trừu tượng của lớp kho lưu trữ, việc chỉ định các công cụ như tải háo hức, đa truy vấn, v.v ... sẽ dễ dàng hơn nhiều và nếu bạn thực sự cần, cũng không khó để chế nhạo ISession.
Arnis Lapsa

3
@JesseWebb Naah ... Với logic truy vấn mô hình miền phong phú được đơn giản hóa đáng kể. Ví dụ: nếu tôi muốn tìm kiếm người dùng đã mua thứ gì đó, tôi chỉ tìm kiếm người dùng. Ở đâu (u => u.HasPurchasingAnything) thay vì người dùng.Join (x => Đơn đặt hàng, một cái gì đó, tôi không biết linq) .Where ( order => order.Status == 1) .Join (x => x.products) .Where (x .... vv vv vv .... blah blah blah
Arnis Lapsa

5

Có một kho lưu trữ chung được bao bọc bởi một kho lưu trữ cụ thể. Bằng cách đó, bạn có thể kiểm soát giao diện công cộng nhưng vẫn có lợi thế của việc sử dụng lại mã xuất phát từ việc có một kho lưu trữ chung.


2

lớp công khai UserRep repository: Kho lưu trữ, IUserRep repository

Bạn không nên tiêm IUserRep repository để tránh lộ giao diện. Như mọi người đã nói, bạn có thể không cần ngăn xếp CRUD đầy đủ, v.v.


2
Điều này trông giống như một bình luận hơn là câu trả lời.
Amit Joshi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.