2009-08-05 40 views
121

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 lưu trữ/dịch vụ. Tôi tự hỏi nếu có bất kỳ lợi thế lớn nào để tạo ra một giao diện IRepository chung mà tất cả các kho lưu trữ đều được thực hiện, so với mỗi Repository có giao diện và tập hợp các phương thức duy nhất của riêng nó.Lợi thế của việc tạo kho lưu trữ chung so với kho lưu trữ cụ thể cho từng đối tượng?

Ví dụ: một giao diện IRepository generic có thể trông giống như (lấy từ this answer):

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 Repository sẽ thực hiện giao diện này, ví dụ:

  • CustomerRepository: IRepository
  • ProductRepository : IRepository
  • vv

Các thay thế mà chúng tôi đã theo dõi 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 từ ngữ (LINQ hay cách khác) sẽ được hoàn toàn chứa trong việc thực hiện Repository, bất cứ ai đang triển khai các dịch vụ chỉ cần biết chức năng kho lưu trữ nào cần 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ễ bị lộn xộn đang được sao chép trong nhiều trường hợp?

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

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, vv). Điều đó có vẻ sạch hơn nhiều so với văn bản sau đây ở nhiều nơi:

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

Những bất lợi duy nhất tôi thấy việc sử dụng các phương pháp cụ thể là chúng ta có thể kết thúc với nhiều hoán vị của lệnh Get * chức năng, nhưng điều này vẫn có vẻ thích hợp hơn để đẩy biểu thức logic vào các lớp Service.

Tôi đang thiếu gì?

Trả lời

154

Đây là vấn đề cũ như bản mẫu Kho lưu trữ. Việc 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 rất nhiều cuộc thảo luận về chủ đề này rất.

Tôi thích 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 lưu trữ chung. Không có vấn đề gì cơ chế thông minh tôi đã cố gắng, tôi luôn luôn kết thúc tại cùng một vấn đề: một kho lưu trữ là một phần của tên miền được mô hình hóa, và tên miền đó không phải là 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 thay đổi rất nhiều; API kho lưu trữ trở thành duy nhất như chính thực thể đó.

Mẫu mà tôi thường sử dụng là có giao diện kho lưu trữ cụ thể, nhưng là 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 DataContext với bạn đơn vị-of-công việc của sự lựa chọn.Triển khai ví dụ 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 ý rằng API công khai của kho lưu trữ không cho phép người dùng xóa. Ngoài ra, phơi bày IQueryable là một toàn bộ các sâu khác - có nhiều ý kiến ​​như các nút bụng trên chủ đề đó.

+8

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

+5

Vì vậy, làm thế nào bạn sẽ sử dụng IoC/DI với điều này? (Tôi là một người mới ở IoC) Câu hỏi của tôi liên quan đến mẫu của bạn đầy đủ: http://stackoverflow.com/questions/4312388/how-should-i-use-ioc-di-with-this-repository-pattern – dan

+29

"một kho lưu trữ là một phần của miền được mô hình hoá và tên miền đó không phải là 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" hoàn hảo! – adamwtiko

11

Tôi thích 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 để xác định hành vi chính xác) với chữ ký phương pháp có thể ghi đè.

+0

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

+0

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

+0

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

26

Tôi thực sự không đồng ý một chút với bài đăng của Bryan. Tôi nghĩ anh ấy đúng, cuối cùng mọi thứ đều rất độc đáo và vân vân. Nhưng đồng thời, hầu hết điều đó xuất hiện khi bạn thiết kế và tôi thấy rằng việc nhận đượ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 trường hợp như vậy, tôi thường tạo một IRepository 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 với giao diện người dùng và tích hợp & sự chấp nhận của người dùng thử nghiệm song song. Sau đó, như tôi thấy tôi cần truy vấn cụ thể trên repo, vv, tôi bắt đầu thay thế phụ thuộc đó w/một cụ thể nếu cần thiết và đi từ đó. Một ẩn tiềm ẩn. rất dễ tạo và sử dụng (và có thể móc vào một bộ nhớ trong hoặc các đối tượng tĩnh hoặc các đối tượng giả hoặc bất kỳ 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ể kết hợp 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 thực hiện một số hoặc tất cả chúng. vẫn tiêm việc 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 bạn đã trả lời @Paul. Tôi thực sự đã thử cách tiếp cận đó Tôi không thể tìm ra cách thể hiện một cách tổng quát phương pháp đầu tiên mà tôi đã thử, 'GetById()'. Tôi có nên sử dụng 'IRepository ',' GetById (id đối tượng) 'hay giả định và sử dụng' GetById (int id) 'Làm thế nào các phím composite làm việc? Tôi tự hỏi nếu một lựa chọn chung của ID là một trừu tượng đáng giá.Nếu không, những gì khác sẽ kho chung chung bị buộc phải thể hiện akwardly? Đó là dòng lý do đằng sau trừu tượng * thực hiện * , không phải giao diện * * –

+9

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 được triển khai đưa ra các truy vấn cụ thể cho các thực thể của dự án của bạ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ắt 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. –

+2

@Bryan - Về GetById() của bạn. Tôi sử dụng FindById (id TId); Do đó dẫn đến một cái gì đó như repository.FindById (435); –

5

Có kho lưu trữ chung được gói 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 tái sử dụng mã đến từ một kho lưu trữ chung.

1

public class UserRepository: Repository, IUserRepository

nên không tiêm IUserRepository để tránh phơi bày 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.

+1

Điều này giống như một bình luận hơn là trả lời. –

Các vấn đề liên quan