2010-02-27 67 views
14

Tôi đang trong quá trình thiết kế ứng dụng ASP.NET MVC của mình và tôi đã gặp một vài suy nghĩ thú vị.Thiết kế lớp trừu tượng cơ sở dữ liệu - Sử dụng IRepository đúng cách?

Nhiều mẫu tôi đã thấy mô tả và sử dụng mẫu Kho lưu trữ (IRepository) vì vậy đây là cách tôi đã làm trong khi tôi đang học MVC.

Bây giờ tôi biết tất cả những gì đang làm, tôi bắt đầu nhìn vào thiết kế hiện tại của mình và tự hỏi nếu đó là cách tốt nhất để đi.

Hiện nay tôi có một căn bản IUserRepository, trong đó xác định các phương pháp như FindById(), SaveChanges() vv

Hiện nay, bất cứ khi nào tôi muốn tải/truy vấn bảng người dùng trong DB, tôi làm điều gì đó dọc theo dòng của sau:

private IUserRepository Repository; 

    public UserController() 
     : this(new UserRepository()) 
    { } 

    [RequiresAuthentication] 
    [AcceptVerbs(HttpVerbs.Get)] 
    public ActionResult Edit(string ReturnUrl, string FirstRun) 
    { 
     var user = Repository.FindById(User.Identity.Name); 

     var viewModel = Mapper.Map<User, UserEditViewModel>(user); 
     viewModel.FirstRun = FirstRun == "1" ? true : false; 

     return View("Edit", viewModel); 
    } 

    [AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken(Salt = "SaltAndPepper")] 
    public ActionResult Edit(UserEditViewModel viewModel, string ReturnUrl) 
    { 
     //Map the ViewModel to the Model 
     var user = Repository.FindById(User.Identity.Name); 

     //Map changes to the user 
     Mapper.Map<UserEditViewModel, User>(viewModel, user); 

     //Save the DB changes 
     Repository.SaveChanges(); 

     if (!string.IsNullOrEmpty(ReturnUrl)) 
      return Redirect(ReturnUrl); 
     else 
      return RedirectToAction("Index", "User"); 
    } 

Bây giờ tôi không hoàn toàn hiểu được thế nào MVC làm việc liên quan đến việc tạo ra một bộ điều khiển khi người dùng tạo ra một liên kết (không chắc chắn nếu có 1 điều khiển cho mỗi người dùng hoặc 1 bộ điều khiển cho mỗi ứng dụng), vì vậy tôi 'không tích cực trong quá trình hành động tốt nhất.

Tôi đã tìm thấy câu hỏi hay về việc sử dụng giao diện kho chung chung IRepository<T>here và cũng có ý tưởng về một số tĩnh RepositoryFactory trên một số blog. Về cơ bản chỉ có 1 trường hợp của kho được giữ lại và nó được lấy thông qua nhà máy này

Vì vậy, câu hỏi của tôi xoay quanh cách mọi người làm điều đó trong ứng dụng đó, và những gì được coi là thực hành tốt.

Mọi người có kho lưu trữ riêng lẻ dựa trên mỗi bảng (IUserRepository) không?
Họ có sử dụng chung IRepository<T> không?
Họ có sử dụng nhà máy kho lưu trữ tĩnh không?
Hoặc cái gì khác hoàn toàn?

EDIT: Tôi chỉ nhận ra tôi có lẽ nên hỏi cũng như:

Là có một tin IRepository trên mỗi bộ điều khiển một cách tốt để đi đâu? hoặc tôi có nên khởi tạo IRepository mới mỗi lần tôi muốn sử dụng không?

BOUNTY EDIT: Tôi đang bắt đầu một tiền thưởng để có thêm một số quan điểm (không phải là Tim không hữu ích).

Tôi tò mò muốn biết những gì mọi người làm trong ứng dụng MVC của họ hoặc những gì họ nghĩ là một ý tưởng hay.

+1

Kho lưu trữ chung chung! YAY! SingletonSquared, chỉ là những gì chúng ta cần! ;-) –

+0

@Sky, người ta không bao giờ có đủ. Cá nhân tôi đang nghĩ đến kho lưu trữ chung một phần tĩnh. –

Trả lời

24

Một số vấn đề rất rõ ràng với ý tưởng của một generic IRepository<T>:

  • Nó giả định rằng tất cả các thực thể sử dụng cùng một loại chìa khóa, đó là không đúng sự thật trong hầu hết các hệ thống không tầm thường. Một số thực thể sẽ sử dụng GUID, những người khác có thể có một số loại khóa tự nhiên và/hoặc tổng hợp. NHibernate có thể hỗ trợ điều này khá tốt nhưng LINQ to SQL là khá xấu ở đó - bạn phải viết một thỏa thuận tốt về mã hackish để làm ánh xạ khóa tự động.

  • Điều này có nghĩa là mỗi kho lưu trữ chỉ có thể xử lý chính xác một loại thực thể và chỉ hỗ trợ các hoạt động tầm thường nhất. Khi một kho lưu trữ được chuyển xuống một trình bao bọc CRUD đơn giản, nó có rất ít sử dụng. Bạn cũng có thể giao khách hàng theo số IQueryable<T> hoặc Table<T>.

  • Giả định rằng bạn thực hiện chính xác các thao tác giống nhau trên mọi thực thể. Trong thực tế, điều này sẽ rất xa sự thật. Chắc chắn, có thể là bạn muốn nhận được rằng Order theo ID của nó, nhưng nhiều khả năng bạn muốn nhận được danh sách các đối tượng Order cho một khách hàng cụ thể và trong một số phạm vi ngày. Khái niệm về một hoàn toàn chung chung IRepository<T> không cho phép thực tế rằng bạn gần như chắc chắn muốn thực hiện các loại truy vấn khác nhau trên các loại thực thể khác nhau.

Toàn bộ vấn đề của mô hình kho là để tạo ra một trừu tượng trên mô hình truy cập dữ liệu thông thường. Tôi nghĩ một số lập trình viên cảm thấy buồn chán với việc tạo kho để họ nói "Này, tôi biết, tôi sẽ tạo một kho lưu trữ über có thể xử lý bất kỳ loại thực thể nào!" Mà là tuyệt vời ngoại trừ việc kho lưu trữ là khá nhiều useless cho 80% những gì bạn đang cố gắng để làm. Nó là tốt như là một lớp cơ sở/giao diện, nhưng nếu đó là mức độ đầy đủ của công việc mà bạn làm thì bạn chỉ là lười biếng (và đảm bảo đau đầu trong tương lai).


Lý tưởng nhất là tôi có thể bắt đầu với một kho lưu trữ chung mà trông giống như sau:

public interface IRepository<TKey, TEntity> 
{ 
    TEntity Get(TKey id); 
    void Save(TEntity entity); 
} 

Bạn sẽ nhận thấy rằng này không có một List hoặc GetAll chức năng - đó là bởi vì nó vô lý để nghĩ rằng có thể chấp nhận dữ liệu từ toàn bộ bảng cùng một lúc ở bất kỳ đâu trong mã. Đây là thời điểm bạn cần bắt đầu đi vào các kho lưu trữ cụ thể:

public interface IOrderRepository : IRepository<int, Order> 
{ 
    IEnumerable<Order> GetOrdersByCustomer(Guid customerID); 
    IPager<Order> GetOrdersByDate(DateTime fromDate, DateTime toDate); 
    IPager<Order> GetOrdersByProduct(int productID); 
} 

Và cứ như vậy - bạn sẽ có ý tưởng. Bằng cách này, chúng ta có kho lưu trữ "chung chung" nếu chúng ta thực sự cần các ngữ nghĩa truy xuất đơn giản vô cùng đơn giản, nhưng nói chung chúng ta không bao giờ thực sự truyền nó, chắc chắn không phải là một lớp điều khiển.


Còn với bộ điều khiển, bạn phải làm điều này đúng, nếu không bạn đã phủ nhận tất cả công việc bạn vừa làm khi gộp tất cả các kho.

Bộ điều khiển cần phải lấy kho lưu trữ từ thế giới bên ngoài. Lý do bạn tạo các kho lưu trữ này là do đó bạn có thể thực hiện một số loại Inversion of Control. Mục tiêu cuối cùng của bạn ở đây là có thể hoán đổi một kho lưu trữ cho một kho lưu trữ khác - ví dụ, để kiểm tra đơn vị, hoặc nếu bạn quyết định chuyển từ Linq sang SQL sang Entity Framework tại một thời điểm nào đó trong tương lai.

Một ví dụ về nguyên tắc này là:

public class OrderController : Controller 
{ 
    public OrderController(IOrderRepository orderRepository) 
    { 
     if (orderRepository == null) 
      throw new ArgumentNullException("orderRepository"); 
     this.OrderRepository = orderRepository; 
    } 

    public ActionResult List(DateTime fromDate, DateTime toDate) { ... } 
    // More actions 

    public IOrderRepository OrderRepository { get; set; } 
} 

Nói cách khác Controller có không biết làm thế nào để tạo ra một kho lưu trữ, và cũng không nên nó. Nếu bạn có bất kỳ kho lưu trữ-xây dựng đang diễn ra trong đó, nó tạo ra khớp nối mà bạn thực sự không muốn. Lý do mà các bộ điều khiển mẫu ASP.NET MVC có các hàm tạo tham số tạo ra các kho lưu trữ cụ thể là các trang web cần có khả năng biên dịch và chạy mà không buộc bạn phải thiết lập toàn bộ khuôn khổ Dependency Injection. Tuy nhiên, trong một trang web sản xuất, nếu bạn không đi qua sự phụ thuộc kho thông qua một nhà xây dựng hoặc tài sản công cộng, thì bạn lãng phí thời gian của bạn có kho, vì các bộ điều khiển vẫn được kết hợp chặt chẽ với tầng cơ sở dữ liệu . Bạn cần để có thể viết mã kiểm tra như thế này:

[TestMethod] 
public void Can_add_order() 
{ 
    OrderController controller = new OrderController(); 
    FakeOrderRepository fakeRepository = new FakeOrderRepository(); 
    controller.OrderRepository = fakeRepository; //<-- Important! 
    controller.SubmitOrder(...); 
    Assert.That(fakeRepository.ContainsOrder(...)); 
} 

Bạn không thể làm điều này nếu bạn OrderController đang xảy ra và tạo kho lưu trữ riêng của mình. Phương pháp kiểm tra này không được phép thực hiện bất kỳ truy cập dữ liệu nào, nó chỉ đảm bảo rằng trình điều khiển đang gọi phương thức kho lưu trữ đúng dựa trên hành động.


Đây không phải là DI, nhưng bạn hãy nhớ rằng đây chỉ là giả mạo/chế nhạo. Trường hợp DI đi vào hình ảnh là khi bạn quyết định rằng LINQ to SQL không làm đủ cho bạn và bạn thực sự muốn HQL trong NHibernate, nhưng nó sẽ đưa bạn 3 tháng để cổng tất cả mọi thứ trên, và bạn muốn để có thể làm một kho lưu trữ này tại một thời điểm. Vì vậy, ví dụ, sử dụng một khung DI như Ninject, tất cả các bạn phải làm là thay đổi điều này:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>(); 
Bind<IOrderRepository>().To<LinqToSqlOrderRepository>(); 
Bind<IProductRepository>().To<LinqToSqlProductRepository>(); 

Để:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>(); 
Bind<IOrderRepository>().To<NHibernateOrderRepository>(); 
Bind<IProductRepository>().To<NHibernateProductRepository>(); 

Và có bạn, bây giờ tất cả mọi thứ mà phụ thuộc vào IOrderRepository đang sử dụng phiên bản NHibernate, bạn chỉ phải thay đổi một dòng mã như trái ngược với hàng trăm dòng. Và chúng tôi đang chạy các phiên bản LINQ to SQL và NHibernate cạnh nhau, chức năng chuyển đổi từng mảnh một mà không bao giờ phá vỡ bất cứ điều gì ở giữa.


Vì vậy, để tóm tắt tất cả những điểm tôi đã thực hiện:

  1. Đừng dựa chặt chẽ trên một giao diện chung IRepository<T>. Hầu hết chức năng bạn muốn từ một kho lưu trữ là cụ thể, không phải chung. Nếu bạn muốn bao gồm một số IRepository<T> ở cấp trên của phân cấp lớp/giao diện, điều đó là tốt, nhưng bộ điều khiển phải phụ thuộc vào kho lưu trữ cụ thể để bạn không phải thay đổi mã của mình ở 5 địa điểm khác nhau khi bạn tìm thấy kho lưu trữ chung thiếu các phương thức quan trọng.

  2. Bộ điều khiển nên chấp nhận kho lưu trữ từ bên ngoài, không được tạo kho của riêng mình. Đây là một bước quan trọng trong việc loại bỏ khả năng kiểm tra ghép nối và cải thiện.

  3. Thông thường bạn sẽ muốn kết nối bộ điều khiển bằng cách sử dụng khuôn khổ Dependency Injection, và nhiều trong số chúng có thể được tích hợp liền mạch với ASP.NET MVC.Nếu đó là quá nhiều cho bạn để có trong, sau đó ít nhất bạn nên sử dụng một số loại nhà cung cấp dịch vụ tĩnh để bạn có thể tập trung tất cả các logic tạo kho lưu trữ. (Về lâu dài, có thể bạn sẽ dễ dàng tìm hiểu và sử dụng khung DI).

+0

đó là một câu trả lời tuyệt vời, và thực sự xứng đáng với tiền thưởng. Cảm ơn tất cả các mẹo và suy nghĩ. –

+1

Tôi biết đây là một câu hỏi cũ, nhưng đây là một câu trả lời tuyệt vời. Nó chỉ trả lời rất nhiều câu hỏi tôi có về việc sử dụng giao diện IRepository tiêu chuẩn. Cảm ơn bạn! –

+0

Câu trả lời tuyệt vời bất kể nó bao nhiêu tuổi. Đó là lời khuyên tuyệt vời. – slimflem

3

Mọi người có kho lưu trữ riêng lẻ dựa trên mỗi bảng (IUserRepository) không? Tôi có xu hướng có một kho lưu trữ cho mỗi tổng hợp, không phải mỗi bảng.

Họ có sử dụng IRepository chung không? nếu có thể, có

Họ có sử dụng kho lưu trữ tĩnh không? Tôi thích tiêm một thể hiện Kho lưu trữ thông qua một thùng chứa IOC

+3

Tôi tò mò về ý nghĩa của bạn bằng tổng hợp. –

+2

Tôi nghĩ tham chiếu đến tổng hợp là liên quan đến Mô hình miền - Tôi đã đọc về 'tổng hợp' trong ngữ cảnh Thiết kế điều khiển miền (DDD), nhưng tôi đoán rằng nên áp dụng cho tất cả các miền. Eric Evans định nghĩa một tập hợp là 'một nhóm đối tượng thuộc về nhau (một nhóm các đối tượng riêng lẻ đại diện cho một đơn vị)' – Ahmad

+0

thực sự là những gì tôi định nói –

1

Đây là cách tôi đang sử dụng nó.Tôi đang sử dụng IRepository cho tất cả các hoạt động phổ biến cho tất cả các kho của tôi.

public interface IRepository<T> where T : PersistentObject 
{ 
    T GetById(object id); 
    T[] GetAll(); 
    void Save(T entity); 
} 

và tôi cũng sử dụng ITRepository dành riêng cho từng hoạt động khác biệt với kho lưu trữ này. Ví dụ đối với người sử dụng tôi sẽ sử dụng IUserRepository để thêm phương pháp đó là khác biệt để UserRepository:

public interface IUserRepository : IRepository<User> 
{ 
    User GetByUserName(string username); 
} 

Việc thực hiện sẽ trông như thế này:

public class UserRepository : RepositoryBase<User>, IUserRepository 
{ 
    public User GetByUserName(string username) 
    { 
     ISession session = GetSession(); 
     IQuery query = session.CreateQuery("from User u where u.Username = :username"); 
     query.SetString("username", username); 

     var matchingUser = query.UniqueResult<User>(); 

     return matchingUser; 
    } 
} 


public class RepositoryBase<T> : IRepository<T> where T : PersistentObject 
{ 
    public virtual T GetById(object id) 
    { 
     ISession session = GetSession(); 
     return session.Get<T>(id); 
    } 

    public virtual T[] GetAll() 
    { 
     ISession session = GetSession(); 
     ICriteria criteria = session.CreateCriteria(typeof (T)); 
     return criteria.List<T>().ToArray(); 
    } 

    protected ISession GetSession() 
    { 
     return new SessionBuilder().GetSession(); 
    } 

    public virtual void Save(T entity) 
    { 
     GetSession().SaveOrUpdate(entity); 
    } 
} 

Thần trong UserController sẽ trông như:

public class UserController : ConventionController 
{ 
    private readonly IUserRepository _repository; 
    private readonly ISecurityContext _securityContext; 
    private readonly IUserSession _userSession; 

    public UserController(IUserRepository repository, ISecurityContext securityContext, IUserSession userSession) 
    { 
     _repository = repository; 
     _securityContext = securityContext; 
     _userSession = userSession; 
    } 
} 

Hơn kho lưu trữ được khởi tạo bằng cách sử dụng mẫu chèn phụ thuộc bằng cách sử dụng nhà máy bộ điều khiển tùy chỉnh. Tôi đang sử dụng StructureMap làm lớp tiêm phụ thuộc của tôi.

Lớp cơ sở dữ liệu là NHibernate. ISession là cổng vào cơ sở dữ liệu trong phiên này.

Tôi đề nghị bạn xem xét cấu trúc CodeCampServer, bạn có thể học được rất nhiều từ nó.

Một dự án khác mà bạn có thể tìm hiểu nó là Who Can Help Me. Mà tôi chưa đào đủ.

+0

Whats is this ISession? –

+0

Tôi quên đề cập rằng nó sử dụng NHibernate như là lớp kiên trì. Giao diện ISession cung cấp cho bạn những gì bạn cần để giao tiếp với máy chủ cho phiên này. –

+0

Ahhh, bây giờ nó có ý nghĩa :) –

0

Mọi người có kho lưu trữ riêng lẻ dựa trên mỗi bảng (IUserRepository) không?

Vâng, đây là lựa chọn tốt hơn cho 2 lý do:

  • Dal của tôi là dựa trên LINQ-to-SQL (nhưng DTOs của tôi là giao diện dựa trên các thực thể LTS)
  • các hoạt động biểu diễn là nguyên tử (thêm là một hoạt động nguyên tử, tiết kiệm là một số khác vv)

Họ có sử dụng IRepository chung không?

Vâng, độc quyền, lấy cảm hứng trên chương trình/Giá trị DDD Entity Tôi tạo ra IRepositoryEntity/IRepositoryValue và IRepository chung cho các công cụ khác.

Họ có sử dụng kho lưu trữ tĩnh không?

Có và không: Tôi sử dụng Thùng IOC được gọi thông qua lớp tĩnh. Vâng ... chúng ta có thể nói đó là một loại nhà máy.

Lưu ý: Tôi tự thiết kế kiến ​​trúc này và đồng nghiệp của tôi thấy nó tuyệt vời đến mức chúng tôi đang tạo toàn bộ khuôn khổ công ty của mình trên mô hình này (có đó là một công ty trẻ). Đây chắc chắn là một thứ đáng để thử, ngay cả khi tôi cảm thấy rằng những khuôn khổ đó sẽ được các diễn viên chính phát hành.

0

Bạn có thể tìm thấy một tuyệt vời Generic Repopsitory thư viện mà đã được viết để cho phép nó được sử dụng như một asp WebForms: ObjectDataSource trên CodePlex: MultiTierLinqToSql

Mỗi bộ điều khiển của tôi có kho riêng cho những hành động họ cần hỗ trợ.

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