KHÔNG sử dụng mẫu kho lưu trữ, hãy sử dụng ORM nguyên trạng (EF)


95

Tôi luôn sử dụng Repository pattern nhưng đối với dự án mới nhất của mình, tôi muốn xem liệu tôi có thể hoàn thiện việc sử dụng nó và triển khai “Unit Of Work” hay không. Càng bắt đầu đào sâu, tôi bắt đầu tự đặt câu hỏi: "Tôi có thực sự cần nó không?"

Bây giờ tất cả bắt đầu bằng một vài nhận xét trên Stackoverflow với dấu vết từ bài đăng của Ayende Rahien trên blog của anh ấy, với 2 điều cụ thể,

Điều này có thể được nói về mãi mãi và nó phụ thuộc vào các ứng dụng khác nhau. Tôi muốn biết điều gì,

  1. liệu cách tiếp cận này có phù hợp với một dự án Khung thực thể không?
  2. sử dụng cách tiếp cận này là logic nghiệp vụ vẫn diễn ra trong lớp dịch vụ hay các phương thức mở rộng (như được giải thích bên dưới, tôi biết, phương pháp mở rộng đang sử dụng phiên NHib)?

Điều đó dễ dàng thực hiện bằng cách sử dụng các phương pháp mở rộng. Sạch sẽ, đơn giản và có thể tái sử dụng.

public static IEnumerable GetAll(
    this ISession instance, Expression<Func<T, bool>> where) where T : class
{
    return instance.QueryOver().Where(where).List();
}

Sử dụng cách tiếp cận này và Ninjectvới tư cách là DI, tôi có cần tạo Contextgiao diện và đưa giao diện đó vào bộ điều khiển của mình không?

Câu trả lời:


103

Tôi đã đi xuống nhiều con đường và tạo ra nhiều triển khai kho lưu trữ trên các dự án khác nhau và ... Tôi đã ném khăn vào và từ bỏ nó, đây là lý do tại sao.

Mã hóa cho ngoại lệ

Bạn có viết mã cho 1% khả năng cơ sở dữ liệu của bạn sẽ thay đổi từ công nghệ này sang công nghệ khác không? Nếu bạn đang nghĩ về trạng thái tương lai của doanh nghiệp mình và nói có thì đó là một khả năng thì a) họ phải có nhiều tiền để thực hiện chuyển đổi sang công nghệ DB khác hoặc b) bạn đang chọn công nghệ DB cho vui hoặc c ) đã xảy ra sự cố khủng khiếp với công nghệ đầu tiên bạn quyết định sử dụng.

Tại sao lại vứt bỏ cú pháp LINQ phong phú?

LINQ và EF đã được phát triển để bạn có thể thực hiện những công việc gọn gàng với nó để đọc và duyệt qua đồ thị đối tượng. Tạo và duy trì một kho lưu trữ có thể cung cấp cho bạn sự linh hoạt tương tự để thực hiện điều đó là một nhiệm vụ quái dị. Theo kinh nghiệm của tôi bất cứ khi nào tôi tạo một kho lưu trữ, tôi LUÔN LUÔN bị rò rỉ logic nghiệp vụ vào lớp kho lưu trữ để làm cho các truy vấn hoạt động hiệu quả hơn và / hoặc giảm số lần truy cập vào cơ sở dữ liệu.

Tôi không muốn tạo một phương thức cho mọi hoán vị của một truy vấn mà tôi phải viết. Tôi cũng có thể viết các thủ tục được lưu trữ. Tôi không muốn GetOrder, GetOrderWithOrderItem, GetOrderWithOrderItemWithOrderActivity, GetOrderByUserId, và vân vân ... Tôi chỉ muốn để có được những thực thể chính và traverse và bao gồm các đồ thị đối tượng như tôi rất vui lòng.

Hầu hết các ví dụ về kho lưu trữ là nhảm nhí

Trừ khi bạn đang phát triển một thứ gì đó THỰC TẾ như blog hoặc thứ gì đó thì các truy vấn của bạn sẽ không bao giờ đơn giản như 90% các ví dụ bạn tìm thấy trên internet xoay quanh mô hình kho lưu trữ. Tôi không thể nhấn mạnh điều này đủ! Đây là một cái gì đó mà người ta phải bò qua bùn để tìm ra. Sẽ luôn có một truy vấn phá vỡ kho lưu trữ / giải pháp bạn đã nghĩ ra một cách hoàn hảo mà bạn đã tạo ra và phải đến thời điểm đó bạn mới tự mình đoán ra lần thứ hai và nợ / xói mòn kỹ thuật bắt đầu.

Đừng đơn vị kiểm tra tôi anh bạn

Nhưng điều gì về kiểm thử đơn vị nếu tôi không có kho lưu trữ? Tôi sẽ chế nhạo như thế nào? Đơn giản, bạn không. Hãy nhìn nó từ cả hai góc độ:

Không có kho lưu trữ - Bạn có thể giả lập DbContextbằng cách sử dụng một IDbContexthoặc một số thủ thuật khác nhưng sau đó bạn thực sự đang kiểm tra đơn vị LINQ với Đối tượng chứ không phải LINQ với Đối tượng vì truy vấn được xác định trong thời gian chạy ... OK, vậy là không tốt! Vì vậy, bây giờ nó phụ thuộc vào kiểm tra tích hợp để bao gồm điều này.

Với kho lưu trữ - Bây giờ bạn có thể giả lập các kho lưu trữ của mình và kiểm tra đơn vị (các) lớp ở giữa. Tuyệt vời phải không? Cũng không hẳn ... Trong các trường hợp ở trên khi bạn phải rò rỉ logic vào lớp kho lưu trữ để làm cho các truy vấn hoạt động hiệu quả hơn và / hoặc ít lần truy cập hơn vào cơ sở dữ liệu, làm thế nào các bài kiểm tra đơn vị của bạn có thể che đậy điều đó? Bây giờ nó ở trong lớp repo và bạn không muốn kiểm tra IQueryable<T>đúng không? Thành thật mà nói, các bài kiểm tra đơn vị của bạn sẽ không bao gồm các truy vấn có .Where()mệnh đề 20 dòng và.Include()'một loạt các mối quan hệ và truy cập lại vào cơ sở dữ liệu để thực hiện tất cả những thứ này khác, blah, blah, blah bất kỳ vì truy vấn được tạo trong thời gian chạy. Ngoài ra, vì bạn đã tạo một kho lưu trữ để giữ cho các lớp trên không liên tục, nếu bây giờ bạn muốn thay đổi công nghệ cơ sở dữ liệu của mình, xin lỗi các bài kiểm tra đơn vị của bạn chắc chắn sẽ không đảm bảo kết quả tương tự trong thời gian chạy, quay lại các bài kiểm tra tích hợp. Vì vậy, toàn bộ điểm của kho lưu trữ có vẻ kỳ lạ ..

2 xu

Chúng tôi đã mất rất nhiều chức năng và cú pháp khi sử dụng EF qua các thủ tục được lưu trữ đơn giản (chèn hàng loạt, xóa hàng loạt, CTE, v.v.) nhưng tôi cũng viết mã bằng C # nên tôi không phải nhập nhị phân. Chúng tôi sử dụng EF để chúng tôi có thể sử dụng các nhà cung cấp khác nhau và làm việc với các biểu đồ đối tượng theo một cách liên quan tốt giữa nhiều thứ. Một số trừu tượng là hữu ích và một số thì không.


16
Bạn không tạo kho lưu trữ để có thể kiểm tra đơn vị chúng. Bạn tạo kho lưu trữ để có thể kiểm tra đơn vị logic nghiệp vụ . Để đảm bảo rằng các truy vấn hoạt động: việc viết các bài kiểm tra tích hợp cho các kho lưu trữ dễ dàng hơn nhiều vì chúng chỉ chứa logic chứ không phải bất kỳ nghiệp vụ nào.
jgauffin

16
Coding for the exception: Sử dụng kho lưu trữ không thể chuyển đổi cơ sở dữ liệu. Đó là về tách doanh nghiệp khỏi sự bền bỉ.
jgauffin

2
Đây là tất cả những điểm rất hợp lệ với rất nhiều sự thật đằng sau chúng. Tuy nhiên, điều còn thiếu là nhận ra rằng LINQ phân bố về một ứng dụng thay vì bị giới hạn ở một vị trí nhất quán tạo ra EF tương đương với các lệnh gọi SQL trong các trang sau mã. Mỗi truy vấn LINQ là một điểm bảo trì tiềm năng trong một ứng dụng và càng có nhiều (và chúng càng phổ biến) thì chi phí bảo trì và rủi ro càng cao. Hãy tưởng tượng thêm một 'xóa' cờ để một thực thể và cần phải xác định vị trí mỗi nơi duy nhất trong một ứng dụng lớn mà thực thể được truy vấn, cần phải sửa đổi từng ...
DVK

2
Tôi nghĩ rằng điều này là thiển cận và buồn tẻ. Tại sao bạn lại rò rỉ logic vào repo? Và nếu bạn đã làm vậy, tại sao nó lại quan trọng? Đó là một triển khai dữ liệu. Tất cả những gì chúng tôi đang làm là ngăn LINQ khỏi phần còn lại của mã bằng cách ẩn nó sau repo. Bạn nói rằng không kiểm tra nó, nhưng sau đó bạn sử dụng việc không thể kiểm tra nó như một lý lẽ chống lại việc làm đó. Vì vậy, hãy thực hiện repo, không để lộ IQueryable và không kiểm tra nó. Ít nhất bạn có thể kiểm tra mọi thứ khác một cách tách biệt với việc triển khai dữ liệu. Và 1% cơ hội thay đổi db vẫn là một con số rất lớn theo chiều kim $.
Sinaesthetic

5
+1 cho câu trả lời này. Tôi thấy rằng chúng tôi thực sự KHÔNG cần kho lưu trữ với Entity Framework Core. Đây DbSetkho lưu trữDbContextĐơn vị Công việc . Tại sao lại triển khai mẫu kho lưu trữ khi ORM đã làm điều đó cho chúng tôi! Để thử nghiệm, chỉ cần thay đổi nhà cung cấp thành InMemory. Và làm các bài kiểm tra của bạn! Nó cũng được ghi lại trong MSDN.
Mohammed Noureldin

49

Mô hình kho lưu trữ là một trừu tượng . Mục đích của nó là giảm độ phức tạp và làm cho phần còn lại của đoạn mã không thể hiểu được. Như một phần thưởng, nó cho phép bạn viết các bài kiểm tra đơn vị thay vì các bài kiểm tra tích hợp .

Vấn đề là nhiều nhà phát triển không hiểu được mục đích của các mẫu và tạo ra các kho lưu trữ làm rò rỉ thông tin cụ thể về độ bền cho người gọi (thường là bằng cách tiết lộ IQueryable<T>). Bằng cách đó, họ không nhận được lợi ích gì so với việc sử dụng OR / M trực tiếp.

Cập nhật để giải quyết một câu trả lời khác

Mã hóa cho ngoại lệ

Sử dụng kho lưu trữ không phải là có thể chuyển đổi công nghệ bền bỉ (tức là thay đổi cơ sở dữ liệu hoặc sử dụng dịch vụ web, v.v.). Đó là về việc tách logic nghiệp vụ khỏi tính bền bỉ để giảm độ phức tạp và khớp nối.

Kiểm tra đơn vị so với kiểm tra tích hợp

Bạn không viết các bài kiểm tra đơn vị cho các kho lưu trữ. giai đoạn = Stage.

Nhưng bằng cách giới thiệu các kho lưu trữ (hoặc bất kỳ lớp trừu tượng nào khác giữa tính bền vững và nghiệp vụ), bạn có thể viết các bài kiểm tra đơn vị cho logic nghiệp vụ. tức là bạn không phải lo lắng về việc các bài kiểm tra của bạn không thành công do cơ sở dữ liệu được cấu hình sai.

Đối với các truy vấn. Nếu bạn sử dụng LINQ, bạn cũng phải đảm bảo rằng các truy vấn của bạn hoạt động, giống như bạn phải làm với kho lưu trữ. và điều đó được thực hiện bằng cách sử dụng các bài kiểm tra tích hợp.

Sự khác biệt là nếu bạn không kết hợp doanh nghiệp của mình với các câu lệnh LINQ, bạn có thể chắc chắn 100% rằng đó là mã bền vững của bạn đang thất bại chứ không phải thứ gì khác.

Nếu bạn phân tích các bài kiểm tra của mình, bạn cũng sẽ thấy rằng chúng sạch hơn nhiều nếu bạn không có mối quan tâm hỗn hợp (tức là LINQ + Logic kinh doanh)

Ví dụ về kho lưu trữ

Hầu hết các ví dụ là nhảm nhí. điều đó rất đúng. Tuy nhiên, nếu bạn google bất kỳ mẫu thiết kế nào, bạn sẽ tìm thấy rất nhiều ví dụ khó hiểu. Đó không phải là lý do để tránh sử dụng một khuôn mẫu.

Việc xây dựng một triển khai kho lưu trữ chính xác là rất dễ dàng. Trên thực tế, bạn chỉ phải tuân theo một quy tắc duy nhất:

Không thêm bất cứ thứ gì vào lớp kho lưu trữ cho đến thời điểm bạn cần nó

Rất nhiều lập trình viên lười biếng và cố gắng tạo một kho lưu trữ chung và sử dụng một lớp cơ sở với nhiều phương thức mà họ có thể cần. YAGNI. Bạn viết lớp kho lưu trữ một lần và giữ nó miễn là ứng dụng tồn tại (có thể là năm). Sao lại lười biếng. Giữ nó sạch sẽ mà không có bất kỳ kế thừa lớp cơ sở nào. Nó sẽ giúp bạn dễ dàng đọc và bảo trì hơn nhiều.

(Tuyên bố trên là một hướng dẫn chứ không phải luật. Một lớp cơ sở rất có thể được thúc đẩy. Chỉ cần suy nghĩ trước khi bạn thêm nó, để bạn thêm nó vào những lý do phù hợp)

Đồ cũ

Phần kết luận:

Nếu bạn không phiền khi có các câu lệnh LINQ trong mã doanh nghiệp của mình cũng như không quan tâm đến các bài kiểm tra đơn vị, tôi thấy không có lý do gì để không sử dụng Entity Framework trực tiếp.

Cập nhật

Tôi đã viết blog cả về mô hình kho lưu trữ và "trừu tượng" thực sự có nghĩa là gì: http://blog.gauffin.org/2013/01/repository-pattern-done-right/

Cập nhật 2

Đối với một loại thực thể có hơn 20 trường, bạn sẽ thiết kế phương pháp truy vấn như thế nào để hỗ trợ bất kỳ tổ hợp hoán vị nào? Bạn không muốn giới hạn tìm kiếm chỉ theo tên, còn tìm kiếm với thuộc tính điều hướng thì sao, hãy liệt kê tất cả các đơn đặt hàng với mặt hàng có mã giá cụ thể, 3 cấp độ tìm kiếm thuộc tính điều hướng. Toàn bộ lý do IQueryableđược phát minh là để có thể soạn bất kỳ kết hợp tìm kiếm nào dựa trên cơ sở dữ liệu. Về lý thuyết thì mọi thứ đều tuyệt vời, nhưng nhu cầu của người dùng lại vượt lên trên lý thuyết.

Một lần nữa: Một thực thể có hơn 20 trường được lập mô hình không chính xác. Đó là một thực thể GOD. Phá vỡ nó.

Tôi không tranh luận rằng nó IQueryablekhông được tạo ra để phân tích. Tôi đang nói rằng nó không phù hợp với một lớp trừu tượng như mẫu Kho lưu trữ vì nó bị rò rỉ. Không có nhà cung cấp LINQ To Sql nào hoàn chỉnh 100% (như EF).

Tất cả chúng đều có những thứ cụ thể về triển khai như cách sử dụng tải háo hức / lười biếng hoặc cách thực hiện câu lệnh SQL "IN". Việc hiển thị IQueryabletrong kho bắt buộc người dùng phải biết tất cả những điều đó. Do đó, toàn bộ nỗ lực trừu tượng hóa nguồn dữ liệu là một thất bại hoàn toàn. Bạn chỉ thêm phức tạp mà không nhận được bất kỳ lợi ích nào khi sử dụng OR / M trực tiếp.

Triển khai mẫu Kho lưu trữ một cách chính xác hoặc hoàn toàn không sử dụng nó.

(Nếu bạn thực sự muốn xử lý các thực thể lớn, bạn có thể kết hợp mẫu Kho lưu trữ với mẫu Đặc tả . Điều đó cung cấp cho bạn một bản tóm tắt hoàn chỉnh cũng có thể kiểm tra được.)


6
Không để lộ IQueryable dẫn đến việc tìm kiếm bị hạn chế và mọi người sẽ tạo ra nhiều phương thức Get hơn cho các loại truy vấn khác nhau và cuối cùng nó làm cho kho lưu trữ phức tạp hơn.
Akash Kava

3
bạn đã không giải quyết vấn đề cốt lõi nào cả: Việc hiển thị IQueryable thông qua một kho lưu trữ không phải là một sự trừu tượng hoàn toàn.
jgauffin

1
Có một đối tượng truy vấn chứa tất cả cơ sở hạ tầng cần thiết để được thực thi trong và của chính nó là cách để sử dụng imo. Bạn cung cấp cho nó các trường là cụm từ tìm kiếm và nó trả về một danh sách kết quả. Đối với QO, bạn có thể làm bất cứ điều gì bạn muốn. Và nó là một giao diện, rất dễ kiểm tra. Xem bài viết của tôi ở trên. Nó là tốt nhất.
h.alex

2
Cá nhân tôi nghĩ rằng việc triển khai giao diện IQueryable <T> trên một lớp Kho lưu trữ cũng có ý nghĩa hơn là để lộ tập cơ bản trong một trong các thành viên của nó.
dark_perfect

3
@yat: Một đại diện cho mỗi gốc tổng hợp. Nhưng imho nó không phải là gốc tổng hợp và tập hợp các bảng mà chỉ là tập hợp gốc và các tập hợp . Bộ nhớ thực tế có thể chỉ sử dụng một bảng hoặc nhiều bảng, tức là nó có thể không phải là ánh xạ một-một giữa mỗi tổng hợp và một bảng. Tôi sử dụng kho lưu trữ để giảm độ phức tạp và loại bỏ bất kỳ phụ thuộc nào của lưu trữ cơ bản.
jgauffin

27

IMO cả phần Repositorytrừu tượng và phần UnitOfWorktrừu tượng đều có một vị trí rất giá trị trong bất kỳ sự phát triển có ý nghĩa nào. Mọi người sẽ tranh cãi về chi tiết thực hiện, nhưng cũng giống như có nhiều cách để lột da mèo, có nhiều cách để thực hiện một cách trừu tượng.

Câu hỏi của bạn là sử dụng hay không sử dụng và tại sao.

Như bạn không nghi ngờ gì khi nhận ra rằng bạn đã có cả hai mẫu này được xây dựng trong Entity Framework, DbContextUnitOfWorkDbSetRepository. Nói chung, bạn không cần phải kiểm tra đơn vị UnitOfWorkhoặc Repositorychính chúng vì chúng chỉ đơn giản là tạo điều kiện giữa các lớp của bạn và các triển khai truy cập dữ liệu cơ bản. Những gì bạn sẽ thấy mình cần phải làm, lặp đi lặp lại, là mô phỏng hai điều trừu tượng này khi đơn vị kiểm tra tính logic của các dịch vụ của bạn.

Bạn có thể giả mạo, giả mạo hoặc bất cứ điều gì bằng các thư viện bên ngoài thêm các lớp mã phụ thuộc (mà bạn không kiểm soát) giữa logic đang thực hiện kiểm tra và logic đang được kiểm tra.

Vì vậy, một điểm nhỏ là có sự trừu tượng của riêng bạn UnitOfWorkRepositorycho phép bạn kiểm soát và linh hoạt tối đa khi bắt chước các bài kiểm tra đơn vị của bạn.

Tất cả đều rất tốt, nhưng đối với tôi, sức mạnh thực sự của những sự trừu tượng này là chúng cung cấp một cách đơn giản để áp dụng các kỹ thuật Lập trình hướng theo khía cạnh và tuân thủ các nguyên tắc SOLID .

Vì vậy, bạn có IRepository:

public interface IRepository<T>
    where T : class
{
    T Add(T entity);
    void Delete(T entity);
    IQueryable<T> AsQueryable();
}

Và việc thực hiện nó:

public class Repository<T> : IRepository<T>
    where T : class
{
    private readonly IDbSet<T> _dbSet;
    public Repository(PPContext context) 
    {
        _dbSet = context.Set<T>();
    }

    public T Add(T entity)
    { 
        return _dbSet.Add(entity); 
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity); 
    }

    public IQueryable<T> AsQueryable() 
    {
        return _dbSet.AsQueryable();
    }
}

Không có gì khác thường cho đến nay nhưng bây giờ chúng tôi muốn thêm một số ghi nhật ký - dễ dàng với Trình trang trí ghi nhật ký .

public class RepositoryLoggerDecorator<T> : IRepository<T>
    where T : class
{
    Logger logger = LogManager.GetCurrentClassLogger();
    private readonly IRepository<T> _decorated;
    public RepositoryLoggerDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
        T added = _decorated.Add(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        return added;
    }

    public void Delete(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        _decorated.Delete(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable();
    }
}

Tất cả được thực hiện và không có thay đổi đối với mã hiện có của chúng tôi . Có rất nhiều mối quan tâm xuyên suốt khác mà chúng tôi có thể thêm vào, chẳng hạn như xử lý ngoại lệ, bộ nhớ đệm dữ liệu, xác thực dữ liệu hoặc bất cứ điều gì và trong suốt quá trình thiết kế và xây dựng của chúng tôi, điều có giá trị nhất cho phép chúng tôi thêm các tính năng đơn giản mà không thay đổi bất kỳ mã hiện có nào của chúng tôi là sự IRepositorytrừu tượng của chúng ta .

Bây giờ, nhiều lần tôi đã thấy câu hỏi này trên StackOverflow - “làm cách nào để bạn làm cho Entity Framework hoạt động trong môi trường nhiều người thuê?”.

https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant

Nếu bạn có một sự Repositorytrừu tượng thì câu trả lời là "thật dễ dàng thêm một người trang trí"

public class RepositoryTennantFilterDecorator<T> : IRepository<T>
    where T : class
{
    //public for Unit Test example
    public readonly IRepository<T> _decorated;
    public RepositoryTennantFilterDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        return _decorated.Add(entity);
    }

    public void Delete(T entity)
    {
        _decorated.Delete(entity);
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable().Where(o => true);
    }
}

IMO, bạn nên luôn đặt một phần trừu tượng đơn giản lên bất kỳ thành phần nào của bên thứ 3 sẽ được tham chiếu ở nhiều nơi. Từ góc độ này, ORM là ứng cử viên hoàn hảo vì nó được tham chiếu trong rất nhiều mã của chúng tôi.

Câu trả lời thường xuất hiện trong đầu khi ai đó nói "tại sao tôi nên có một sự trừu tượng (ví dụ Repository) đối với thư viện này hoặc thư viện của bên thứ 3 đó" là "tại sao bạn lại không?"

PS Decorator cực kỳ đơn giản để áp dụng bằng IoC Container, chẳng hạn như SimpleInjector .

[TestFixture]
public class IRepositoryTesting
{
    [Test]
    public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
    {
        Container container = new Container();
        container.RegisterLifetimeScope<PPContext>();
        container.RegisterOpenGeneric(
            typeof(IRepository<>), 
            typeof(Repository<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryLoggerDecorator<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryTennantFilterDecorator<>));
        container.Verify();

        using (container.BeginLifetimeScope())
        {
            var result = container.GetInstance<IRepository<Image>>();

            Assert.That(
                result, 
                Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
            Assert.That(
                (result as RepositoryTennantFilterDecorator<Image>)._decorated,
                Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
        }
    }
}

11

Trước hết, theo gợi ý của một số câu trả lời, bản thân EF là một mẫu kho lưu trữ, không cần phải tạo thêm phần trừu tượng chỉ để đặt tên là kho lưu trữ.

Mockable Repository cho Unit Test, chúng ta có thực sự cần nó không?

Chúng tôi cho phép EF giao tiếp để kiểm tra DB trong các bài kiểm tra đơn vị để kiểm tra logic nghiệp vụ của chúng tôi trực tiếp với DB kiểm tra SQL. Tôi không thấy bất kỳ lợi ích nào của việc mô phỏng bất kỳ mẫu kho lưu trữ nào cả. Điều gì thực sự sai khi thực hiện các bài kiểm tra đơn vị dựa trên cơ sở dữ liệu kiểm tra? Vì đó là các hoạt động hàng loạt không thể thực hiện được và chúng tôi kết thúc bằng cách viết SQL thô. SQLite trong bộ nhớ là ứng cử viên hoàn hảo để thực hiện các bài kiểm tra đơn vị đối với cơ sở dữ liệu thực.

Tóm tắt không cần thiết

Bạn có muốn tạo kho lưu trữ để trong tương lai, bạn có thể dễ dàng thay thế EF bằng NHbibernate, v.v. hay bất kỳ thứ gì khác không? Kế hoạch nghe có vẻ tuyệt vời, nhưng nó có thực sự hiệu quả về chi phí không?

Linq giết các bài kiểm tra đơn vị?

Tôi sẽ muốn xem bất kỳ ví dụ nào về cách nó có thể giết chết.

Tiêm phụ thuộc, IoC

Chà, đây là những từ tuyệt vời, chắc chắn về mặt lý thuyết chúng trông rất tuyệt, nhưng đôi khi bạn phải đánh đổi giữa thiết kế tuyệt vời và giải pháp tuyệt vời. Chúng tôi đã sử dụng tất cả những thứ đó, và cuối cùng chúng tôi đã ném tất cả vào thùng rác và chọn cách tiếp cận khác. Kích thước vs Tốc độ (Kích thước mã và Tốc độ phát triển) quan trọng rất lớn trong cuộc sống thực. Người dùng cần sự linh hoạt, họ không quan tâm đến việc mã của bạn có thiết kế tuyệt vời về mặt DI hay IoC hay không.

Trừ khi bạn đang xây dựng Visual Studio

Tất cả những thiết kế tuyệt vời này là cần thiết nếu bạn đang xây dựng một chương trình phức tạp như Visual Studio hoặc Eclipse, chương trình này sẽ được nhiều người phát triển và nó cần có khả năng tùy biến cao. Tất cả các mô hình phát triển tuyệt vời đã được hình thành sau nhiều năm phát triển mà các IDE này đã trải qua, và chúng đã phát triển ở nơi mà tất cả các mô hình thiết kế tuyệt vời này có ý nghĩa rất lớn. Nhưng nếu bạn đang làm bảng lương dựa trên web đơn giản hoặc ứng dụng kinh doanh đơn giản, tốt hơn là bạn nên phát triển theo thời gian, thay vì dành thời gian để xây dựng nó cho hàng triệu người dùng trong đó nó sẽ chỉ được triển khai cho 100 người dùng.

Kho lưu trữ dưới dạng Chế độ xem được Lọc - ISecureRepository

Mặt khác, kho lưu trữ phải là một chế độ xem được lọc của EF bảo vệ quyền truy cập vào dữ liệu bằng cách áp dụng bộ đệm cần thiết dựa trên vai trò / người dùng hiện tại.

Nhưng làm như vậy sẽ làm phức tạp thêm kho lưu trữ vì nó kết thúc bằng cơ sở mã khổng lồ để duy trì. Mọi người kết thúc việc tạo các kho lưu trữ khác nhau cho các loại người dùng khác nhau hoặc kết hợp các loại thực thể. Không chỉ vậy, chúng tôi còn có rất nhiều DTO.

Câu trả lời sau đây là một ví dụ về triển khai Kho lưu trữ được lọc mà không cần tạo toàn bộ các lớp và phương thức. Nó có thể không trả lời câu hỏi trực tiếp nhưng nó có thể hữu ích trong việc tìm ra một câu hỏi.

Tuyên bố từ chối trách nhiệm: Tôi là tác giả của Entity REST SDK.

http://entityrestsdk.codeplex.com

Lưu ý ở trên, chúng tôi đã phát triển một SDK tạo kho lưu trữ chế độ xem được lọc dựa trên SecurityContext chứa các bộ lọc cho các hoạt động CRUD. Và chỉ có hai loại quy tắc đơn giản hóa mọi hoạt động phức tạp. Đầu tiên là quyền truy cập vào thực thể và thứ khác là quy tắc Đọc / Ghi cho thuộc tính.

Ưu điểm là bạn không viết lại logic nghiệp vụ hoặc kho lưu trữ cho các kiểu người dùng khác nhau, bạn chỉ cần chặn hoặc cấp quyền truy cập cho họ.

public class DefaultSecurityContext : BaseSecurityContext {

  public static DefaultSecurityContext Instance = new DefaultSecurityContext();

  // UserID for currently logged in User
  public static long UserID{
       get{
             return long.Parse( HttpContext.Current.User.Identity.Name );
       }
  }

  public DefaultSecurityContext(){
  }

  protected override void OnCreate(){

        // User can access his own Account only
        var acc = CreateRules<Account>();

        acc.SetRead( y => x=> x.AccountID == UserID ) ;
        acc.SetWrite( y => x=> x.AccountID == UserID );

        // User can only modify AccountName and EmailAddress fields
        acc.SetProperties( SecurityRules.ReadWrite, 
              x => x.AccountName,
              x => x.EmailAddress);

        // User can read AccountType field
        acc.SetProperties<Account>( SecurityRules.Read, 
              x => x.AccountType);

        // User can access his own Orders only
        var order = CreateRules<Order>();
        order.SetRead( y => x => x.CustomerID == UserID );

        // User can modify Order only if OrderStatus is not complete
        order.SetWrite( y => x => x.CustomerID == UserID 
            && x.OrderStatus != "Complete" );

        // User can only modify OrderNotes and OrderStatus
        order.SetProperties( SecurityRules.ReadWrite, 
              x => x.OrderNotes,
              x => x.OrderStatus );

        // User can not delete orders
        order.SetDelete(order.NotSupportedRule);
  }
}

Các Quy tắc LINQ này được đánh giá dựa trên Cơ sở dữ liệu trong phương pháp SaveChanges cho mọi hoạt động và các Quy tắc này hoạt động như Tường lửa phía trước Cơ sở dữ liệu.


3
Thử nghiệm đơn vị dựa trên DB có nghĩa là bạn có thêm yêu cầu bên ngoài cho các thử nghiệm của mình. Nếu DB đó bị lỗi, hoặc dữ liệu bị xóa, hoặc bất kỳ điều gì xảy ra với DB đó, các bài kiểm tra của bạn sẽ không thành công. Điều này không được mong muốn. Các kho lưu trữ hiển thị IQueryable mất khoảng 2 phút để thiết lập. Không lãng phí thời gian ở đây. Tại sao DI làm bạn mất nhiều thời gian? Tất cả điều này mất vài phút. Tôi sẽ nói rằng tất cả điều này đều hoạt động tốt cho đơn vị kiểm tra các truy vấn phức tạp của tôi trong lớp dịch vụ của tôi. Thật tuyệt khi không cần cơ sở dữ liệu để kết nối. Lấy một khuôn khổ chế nhạo từ nuget mất khoảng một phút. Công cụ này không mất bất kỳ thời gian.
user441521

@ user441521 Kho lưu trữ với IQueryable 2 phút để thiết lập? bạn đang sống ở thế giới nào, mọi yêu cầu của asp.net trên trang trực tiếp của chúng tôi sẽ được phục vụ trong vòng mili giây. Chế giễu và làm giả, v.v ... làm cho mã phức tạp hơn, gây lãng phí thời gian. Kiểm thử đơn vị là vô ích khi đơn vị không được định nghĩa là đơn vị logic nghiệp vụ.
Akash Kava

7

Có rất nhiều cuộc tranh luận về phương pháp nào là đúng, vì vậy tôi xem xét nó như cả hai đều có thể chấp nhận được vì vậy tôi sử dụng phương pháp nào tôi thích nhất (Không có kho lưu trữ, UoW).

Trong EF UoW được thực hiện thông qua DbContext và DbSets là kho lưu trữ.

Về cách làm việc với lớp dữ liệu tôi chỉ làm trực tiếp trên đối tượng DbContext, đối với những truy vấn phức tạp tôi sẽ thực hiện các phương thức mở rộng để truy vấn có thể sử dụng lại.

Tôi tin rằng Ayende cũng có một số bài đăng về cách trừu tượng hóa các hoạt động CUD là xấu.

Tôi luôn tạo một giao diện và có ngữ cảnh kế thừa từ nó để tôi có thể sử dụng vùng chứa IoC cho DI.


Vậy các phương pháp mở rộng, chúng được mở rộng như thế nào? Giả sử tôi cần có trạng thái của một thực thể khác trong tiện ích mở rộng của mình? Đó là mối quan tâm lớn nhất của tôi lúc này. Bạn có phiền hiển thị một số ví dụ về các phương pháp mở rộng không?
Dejan.S

ayende.com/blog/153473/…ayende.com/blog/153569/… . (Đây là những đánh giá về một kiến ​​trúc (Framework?) Được gọi là s # arp lite. Hầu hết là tốt nhưng anh ta không đồng ý với kho lưu trữ và các bản tóm tắt CUD).
Josh

Dựa trên NHibernate của nó. Bạn không có bất kỳ ví dụ nào sử dụng EF? Và một lần nữa khi tôi cần gọi một thực thể khác, làm thế nào mà điều đó được thực hiện tốt nhất trong phương thức mở rộng tĩnh?
Dejan.S

3
Điều này là tốt và tốt cho đến khi một thuộc tính của đối tượng miền của bạn cần được cung cấp bởi dữ liệu không được lưu trữ trong cơ sở dữ liệu của bạn; hoặc bạn cần phải chuyển sang một công nghệ hiệu quả hơn ORM cồng kềnh của bạn. GIÁO SƯ! ORM đơn giản KHÔNG phải là một thay thế cho một kho lưu trữ, nó là một chi tiết triển khai của một.
cdaq

2

Điều áp dụng nhiều nhất trên EF không phải là Mẫu kho lưu trữ. Nó là một mẫu Mặt tiền (trừu tượng hóa các lệnh gọi đến các phương thức EF thành các phiên bản đơn giản hơn, dễ sử dụng hơn).

EF là đơn vị áp dụng Mẫu kho lưu trữ (và cả mẫu Đơn vị công việc). Nghĩa là, EF là lớp trừu tượng hóa lớp truy cập dữ liệu để người dùng không biết họ đang xử lý SQLServer.

Và tại đó, hầu hết các "kho lưu trữ" trên EF thậm chí không phải là Mặt trận tốt vì chúng chỉ ánh xạ, khá đơn giản, đến các phương thức đơn lẻ trong EF, thậm chí đến mức có các chữ ký giống nhau.

Do đó, hai lý do để áp dụng cái gọi là mẫu "Kho lưu trữ" này trên EF là để cho phép thử nghiệm dễ dàng hơn và thiết lập một tập hợp con các lệnh gọi "đóng hộp" cho nó. Bản thân chúng không tệ, nhưng rõ ràng không phải là một Kho lưu trữ.


1

Linq là một 'Kho lưu trữ' ngày nay.

ISession + Linq đã là kho lưu trữ và bạn không cần GetXByYphương pháp cũng như QueryData(Query q)tổng quát hóa. Có một chút hoang tưởng với việc sử dụng DAL, tôi vẫn thích giao diện kho lưu trữ hơn. (Từ quan điểm về khả năng bảo trì, chúng ta vẫn phải có một số mặt đối với các giao diện truy cập dữ liệu cụ thể).

Đây là kho lưu trữ mà chúng tôi sử dụng - nó loại bỏ chúng tôi khỏi việc sử dụng trực tiếp nhibernate, nhưng cung cấp giao diện linq (như quyền truy cập ISession trong các trường hợp ngoại lệ, cuối cùng tùy thuộc vào trình tái cấu trúc).

class Repo
{
    ISession _session; //via ioc
    IQueryable<T> Query()
    {
        return _session.Query<T>();
    }
}

Bạn làm gì cho lớp dịch vụ?
Dejan.S

Bộ điều khiển truy vấn repo cho dữ liệu chỉ đọc, tại sao lại thêm một số lớp bổ sung? Khả năng khác là sử dụng "ContentService" mà ngày càng có xu hướng trở thành kho lưu trữ cấp dịch vụ: GetXByY, v.v. vv. Đối với hoạt động sửa đổi - ứng dụng dịch vụ chỉ là trừu tượng hơn trường hợp sử dụng - họ sử dụng BL và repo tự do ..
mikalai

Tôi đã từng làm lớp dịch vụ cho logic nghiệp vụ. Tôi không thực sự chắc chắn rằng tôi làm theo những gì bạn với ContentService, vui lòng giải thích thêm. Nó có phải là một thực tiễn xấu nếu thực hiện các lớp trợ giúp là "lớp dịch vụ"?
Dejan.S

Theo 'lớp dịch vụ', ý tôi là 'các dịch vụ ứng dụng'. Họ có thể sử dụng kho lưu trữ và bất kỳ phần công khai nào khác của lớp miền. "Lớp dịch vụ" không phải là một phương pháp tồi, nhưng tôi tránh tạo lớp XService chỉ để cung cấp kết quả Danh sách <X>. Trường nhận xét dường như quá ngắn để mô tả chi tiết các dịch vụ, xin lỗi.
mikalai

Điều gì sẽ xảy ra nếu, giả sử một phép tính giỏ hàng và bạn cần lấy các thông số cài đặt ứng dụng và các thông số khách hàng cụ thể để thực hiện tính toán và điều này được sử dụng lại trên một số nơi trong ứng dụng. Bạn xử lý tình huống đó như thế nào? lớp trợ giúp hoặc dịch vụ ứng dụng?
Dejan.S

1

Các Repository (hoặc tùy ý một lựa chọn để gọi nó) vào thời điểm này đối với tôi là chủ yếu về abstracting đi lớp kiên trì.

Tôi sử dụng nó cùng với các đối tượng truy vấn vì vậy tôi không có sự kết hợp với bất kỳ công nghệ cụ thể nào trong các ứng dụng của mình. Và nó cũng giúp giảm bớt thử nghiệm rất nhiều.

Vì vậy, tôi có xu hướng

public interface IRepository : IDisposable
{
    void Save<TEntity>(TEntity entity);
    void SaveList<TEntity>(IEnumerable<TEntity> entities);

    void Delete<TEntity>(TEntity entity);
    void DeleteList<TEntity>(IEnumerable<TEntity> entities);

    IList<TEntity> GetAll<TEntity>() where TEntity : class;
    int GetCount<TEntity>() where TEntity : class;

    void StartConversation();
    void EndConversation();

    //if query objects can be self sustaining (i.e. not need additional configuration - think session), there is no need to include this method in the repository.
    TResult ExecuteQuery<TResult>(IQueryObject<TResult> query);
}

Có thể thêm các phương thức không đồng bộ với các lệnh gọi lại làm đại biểu. Nói chung , repo rất dễ triển khai , vì vậy tôi không thể chạm vào đường dây triển khai từ ứng dụng này sang ứng dụng khác. Chà, điều này đúng ít nhất khi sử dụng NH, tôi cũng đã làm điều đó với EF, nhưng khiến tôi ghét EF. 4. Cuộc trò chuyện là sự khởi đầu của một giao dịch. Rất tuyệt nếu một vài lớp chia sẻ cá thể kho lưu trữ. Ngoài ra, đối với NH, một repo trong quá trình thực hiện của tôi tương đương với một phiên được mở theo yêu cầu đầu tiên.

Sau đó, các đối tượng truy vấn

public interface IQueryObject<TResult>
{
    /// <summary>Provides configuration options.</summary>
    /// <remarks>
    /// If the query object is used through a repository this method might or might not be called depending on the particular implementation of a repository.
    /// If not used through a repository, it can be useful as a configuration option.
    /// </remarks>
    void Configure(object parameter);

    /// <summary>Implementation of the query.</summary>
    TResult GetResult();
}

Đối với cấu hình tôi sử dụng trong NH chỉ để vượt qua trong ISession. Trong EF không có ý nghĩa nhiều hơn hoặc ít hơn.

Một truy vấn ví dụ sẽ là .. (NH)

public class GetAll<TEntity> : AbstractQueryObject<IList<TEntity>>
    where TEntity : class
{
    public override IList<TEntity> GetResult()
    {
        return this.Session.CreateCriteria<TEntity>().List<TEntity>();
    }
}

Để thực hiện một truy vấn EF, bạn sẽ phải có ngữ cảnh trong cơ sở Tóm tắt, không phải phiên. Nhưng tất nhiên ifc sẽ giống nhau.

Bằng cách này, các truy vấn được đóng gói và dễ dàng kiểm tra. Hơn hết, mã của tôi chỉ dựa vào các giao diện. Mọi thứ đều rất sạch sẽ. Các đối tượng miền (doanh nghiệp) chỉ là vậy, ví dụ: không có sự trộn lẫn trách nhiệm như khi sử dụng mẫu bản ghi hoạt động khó có thể kiểm tra được và trộn mã truy cập dữ liệu (truy vấn) trong đối tượng miền và làm như vậy là trộn các mối quan tâm (đối tượng tìm nạp chinh no??). Mọi người vẫn có thể tự do tạo POCO để truyền dữ liệu.

Nói chung, việc sử dụng lại nhiều mã và sự đơn giản được cung cấp với cách tiếp cận này mà tôi không thể tưởng tượng được. Bất kỳ ý tưởng?

Và cảm ơn Ayende rất nhiều vì những bài viết tuyệt vời và sự cống hiến không ngừng của anh ấy. Ý tưởng của anh ấy ở đây (đối tượng truy vấn), không phải của tôi.


1
Các thực thể lâu dài (POCO của bạn) KHÔNG phải là các thực thể kinh doanh / miền. Và mục đích của kho lưu trữ là tách lớp nghiệp vụ (bất kỳ) khỏi lớp tồn tại.
MikeSW

Tôi không nhìn thấy khớp nối. Hơi đồng ý về phần POCO, nhưng không quan tâm. Không có gì ngăn cản bạn có POCO 'chính hãng' và vẫn sử dụng phương pháp này.
h.alex

1
Các thực thể không cần phải có POCO ngu ngốc. Trên thực tế, mô hình hóa logic nghiệp vụ thành các Thực thể là điều mà đám đông DDD luôn làm. Phong cách phát triển này kết hợp rất tốt với NH hoặc EF.
chris

1

Đối với tôi, đó là một quyết định đơn giản, với tương đối ít yếu tố. Các yếu tố là:

  1. Kho lưu trữ dành cho các lớp miền.
  2. Trong một số ứng dụng của tôi, các lớp miền giống với các lớp kiên trì (DAL) của tôi, ở những lớp khác thì không.
  3. Khi chúng giống nhau, EF đang cung cấp cho tôi Kho lưu trữ rồi.
  4. EF cung cấp tính năng tải chậm và IQueryable. Tôi thích những cái này.
  5. Kho lưu trữ trừu tượng / 'facading' / tái triển khai qua EF thường có nghĩa là mất tính lười biếng và IQueryable

Vì vậy, nếu ứng dụng của tôi không thể phù hợp với # 2, mô hình miền và dữ liệu riêng biệt, thì tôi thường sẽ không bận tâm đến # 5.

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.