Các kho lưu trữ có nên trả về IQueryable không?


66

Tôi đã thấy rất nhiều dự án có kho lưu trữ trả về các thể hiện của IQueryable. Điều này cho phép các bộ lọc bổ sung và sắp xếp có thể được thực hiện trên IQueryablemã khác, dịch sang các SQL khác nhau được tạo. Tôi tò mò mô hình này đến từ đâu và liệu nó có phải là một ý tưởng tốt.

Mối quan tâm lớn nhất của tôi IQueryablelà một lời hứa sẽ tấn công cơ sở dữ liệu một thời gian sau đó, khi nó được liệt kê. Điều này có nghĩa là một lỗi sẽ được ném ra bên ngoài kho lưu trữ. Điều này có thể có nghĩa là một ngoại lệ Entity Framework được ném vào một lớp khác của ứng dụng.

Tôi cũng đã gặp phải các vấn đề với Nhiều bộ kết quả hoạt động (MARS) trong quá khứ (đặc biệt là khi sử dụng giao dịch) và cách tiếp cận này có vẻ như sẽ dẫn đến điều này xảy ra thường xuyên hơn.

Tôi đã luôn gọi AsEnumerablehoặc ToArrayở cuối mỗi biểu thức LINQ của mình để đảm bảo cơ sở dữ liệu được nhấn trước khi rời khỏi mã kho lưu trữ.

Tôi tự hỏi nếu trả về IQueryablecó thể hữu ích như một khối xây dựng cho một lớp dữ liệu. Tôi đã thấy một số mã khá xa hoa với một kho lưu trữ gọi một kho lưu trữ khác để xây dựng một kho thậm chí còn lớn hơn IQueryable.


Điều gì sẽ là sự khác biệt nếu db không có sẵn tại thời điểm thực hiện truy vấn bị trì hoãn so với thời điểm xây dựng truy vấn? Mã tiêu thụ sẽ phải đối phó với tình huống đó bất kể.
Steven Evers

Câu hỏi thú vị, nhưng có lẽ sẽ khó trả lời. Một tìm kiếm đơn giản cho thấy một cuộc tranh luận khá sôi nổi về câu hỏi của bạn: duckduckgo.com/?q=reposeective+return+iqueryable
Corbin

6
Tất cả tùy thuộc vào việc bạn có muốn cho phép khách hàng của mình thêm các mệnh đề Linq có thể hưởng lợi từ việc thực hiện hoãn lại hay không. Nếu họ thêm một wheremệnh đề vào hoãn lại IQueryable, bạn chỉ phải gửi dữ liệu đó qua dây chứ không phải toàn bộ tập kết quả.
Robert Harvey

Nếu bạn đang sử dụng mẫu kho lưu trữ - không, bạn không nên trả về IQueryable. Đây là một lý do tốt đẹp tại sao .
Arnis Lapsa

1
@SteveEvers Có một sự khác biệt rất lớn. Nếu một ngoại lệ được ném vào kho lưu trữ của tôi, tôi có thể bọc nó bằng một loại ngoại lệ dành riêng cho lớp dữ liệu. Nếu nó xảy ra sau này, tôi phải nắm bắt loại ngoại lệ ban đầu ở đó. Càng gần với nguồn gốc của ngoại lệ, tôi càng có nhiều khả năng tôi biết nguyên nhân gây ra nó. Điều này rất quan trọng để tạo thông báo lỗi có ý nghĩa. IMHO, cho phép một ngoại lệ dành riêng cho EF rời khỏi kho lưu trữ của tôi là vi phạm đóng gói.
Công viên Travis

Câu trả lời:


47

Trả lại IQueryable chắc chắn sẽ đủ khả năng linh hoạt hơn cho người tiêu dùng của kho lưu trữ. Nó đặt trách nhiệm thu hẹp kết quả cho khách hàng, điều này tự nhiên có thể vừa là lợi ích vừa là một cái nạng.

Về mặt tốt, bạn sẽ không cần phải tạo ra hàng tấn phương thức lưu trữ (ít nhất là trên lớp này) - GetAllActiveItems, GetAllNonActiveItems, v.v. - để có được dữ liệu bạn muốn. Tùy thuộc vào sở thích của bạn, một lần nữa, điều này có thể là tốt hoặc xấu. Bạn sẽ (/ nên) cần xác định các hợp đồng hành vi mà việc triển khai của bạn tuân thủ, nhưng điều đó tùy thuộc vào bạn.

Vì vậy, bạn có thể đặt logic truy xuất gritty bên ngoài kho lưu trữ và để nó được sử dụng theo ý muốn của người dùng. Vì vậy, việc phơi bày IQueryable mang lại sự linh hoạt nhất và cho phép truy vấn hiệu quả thay vì lọc trong bộ nhớ, v.v. và có thể giảm nhu cầu tạo ra hàng tấn phương thức tìm nạp dữ liệu cụ thể.

Mặt khác, bây giờ bạn đã cung cấp cho người dùng của mình một khẩu súng ngắn. Họ có thể thực hiện những việc mà bạn có thể không có ý định (lạm dụng .include (), thực hiện các truy vấn nặng và thực hiện lọc trong bộ nhớ trong các triển khai tương ứng của họ, v.v.), về cơ bản sẽ đẩy mạnh các điều khiển phân lớp và hành vi vì bạn đã đưa ra truy cập đầy đủ.

Vì vậy, tùy thuộc vào nhóm, kinh nghiệm của họ, kích thước của ứng dụng, phân lớp và kiến ​​trúc tổng thể, nó phụ thuộc vào: - \


Lưu ý rằng bạn có thể, nếu bạn muốn làm việc cho nó, trả lại IQueryable của bạn, cái mà lần lượt gọi DB, nhưng thêm các điều khiển phân lớp và hành vi.
psr

1
@psr Bạn có thể giải thích ý của bạn là gì không?
Công viên Travis

1
@TravisParks - IQueryable không cần phải được DB triển khai, bạn có thể tự viết các lớp IQueryable - nó khá khó. Vì vậy, bạn có thể viết một trình bao bọc IQueryable xung quanh cơ sở dữ liệu IQueryable và có quyền truy cập đầy đủ giới hạn IQueryable của bạn vào DB bên dưới. Nhưng điều này đủ khó để tôi không mong đợi nó sẽ được thực hiện rộng rãi.
psr

2
Lưu ý rằng ngay cả khi bạn không trả lại IQueryables, bạn vẫn có thể ngăn không phải thực hiện nhiều phương thức (GetAllActiveItems, GetAllNonActiveItems, v.v.) bằng cách chuyển một Expression<Func<Foo,bool>>phương thức của bạn. Các tham số này có thể được truyền Wheretrực tiếp đến phương thức, do đó cho phép người tiêu dùng chọn bộ lọc của họ mà không cần truy cập trực tiếp vào IQueryable <Foo>.
Flater

1
@hanzolo: Phụ thuộc vào ý nghĩa của bạn với tái cấu trúc. Đúng là việc thay đổi thiết kế (ví dụ: thêm các lĩnh vực mới hoặc thay đổi các mối quan hệ) sẽ gây khó khăn hơn khi bạn có một cơ sở mã hóa được ghép lớp và lỏng lẻo; nhưng tái cấu trúc sẽ ít gây đau đớn hơn khi bạn chỉ cần thay đổi một lớp. Tất cả phụ thuộc vào việc bạn dự kiến ​​thiết kế lại ứng dụng hay bạn chỉ đơn giản là tái cấu trúc việc thực hiện cùng một kiến ​​trúc cơ bản. Tôi vẫn sẽ ủng hộ việc ghép lỏng trong cả hai trường hợp nhưng bạn không sai khi thêm vào việc tái cấu trúc khi thay đổi các khái niệm miền cốt lõi.
Flater

29

Tiếp xúc IQueryable với các giao diện công cộng không phải là một cách thực hành tốt. Lý do là cơ bản: bạn không thể cung cấp nhận thức IQueryable như đã nói.

Giao diện công cộng là một hợp đồng giữa nhà cung cấp và khách hàng. Và trong hầu hết các trường hợp, có một kỳ vọng thực hiện đầy đủ. IQueryable là một mánh gian lận:

  1. IQueryable gần như không thể thực hiện. Và hiện tại không có giải pháp thích hợp. Ngay cả Entity Framework cũng có rất nhiều Ngoại lệ không được hỗ trợ .
  2. Ngày nay các nhà cung cấp truy vấn có sự phụ thuộc lớn vào cơ sở hạ tầng. Sharepoint có triển khai một phần riêng và nhà cung cấp SQL của tôi có triển khai một phần riêng biệt.

Mẫu lưu trữ cung cấp cho chúng tôi đại diện rõ ràng bằng cách xếp lớp, và, quan trọng nhất, tính độc lập của việc thực hiện. Vì vậy, chúng ta có thể thay đổi trong nguồn dữ liệu trong tương lai.

Câu hỏi là "Có nên thay thế nguồn dữ liệu không? ".

Nếu một phần dữ liệu ngày mai có thể được chuyển sang các dịch vụ SAP, cơ sở dữ liệu NoQuery hoặc đơn giản là các tệp văn bản, chúng ta có thể đảm bảo triển khai đúng giao diện IQueryable không?

( nhiều điểm tốt hơn về lý do tại sao điều này là thực hành xấu)


Câu trả lời này không tự đứng vững mà không tham khảo các liên kết bên ngoài dễ bay hơi. Xem xét tóm tắt ít nhất các điểm chính được tạo bởi tài nguyên bên ngoài và cố gắng tránh ý kiến ​​cá nhân ra khỏi câu trả lời của bạn ("ý tưởng tồi tệ nhất tôi từng nghe"). Cũng xem xét việc xóa đoạn mã dường như không liên quan đến bất kỳ điều gì IQueryable.
Lars Viklund 17/12/13

1
Cảm ơn bạn @LarsViklund, câu trả lời sẽ được cập nhật với các ví dụ và mô tả vấn đề
Artru

19

Trên thực tế, bạn đã có ba lựa chọn thay thế nếu bạn muốn thực hiện hoãn lại:

  • Làm theo cách này - phơi bày một IQueryable.
  • Thực hiện cấu trúc hiển thị các phương thức cụ thể cho các bộ lọc cụ thể hoặc "câu hỏi". ( GetCustomersInAlaskaWithChildren, bên dưới)
  • Thực hiện cấu trúc hiển thị API bộ lọc / sắp xếp / trang được gõ mạnh và xây dựng IQueryablenội bộ.

Tôi thích cái thứ ba (mặc dù tôi cũng đã giúp các đồng nghiệp thực hiện cái thứ hai), nhưng rõ ràng có một số thiết lập và bảo trì liên quan. (T4 để giải cứu!)

Chỉnh sửa : Để xóa bỏ sự nhầm lẫn xung quanh những gì tôi đang nói, hãy xem xét ví dụ sau bị đánh cắp từ IQueryable có thể giết chó của bạn, đánh cắp vợ, giết ý chí sống của bạn, v.v.

Trong kịch bản mà bạn sẽ phơi bày một cái gì đó như thế này:

public class CustomerRepo : IRepo 
{ 
     private DataContext ct; 
     public Customer GetCustomerById(int id) { ... } 
     public Customer[] GetCustomersInAlaskaWithChildren() { ... } 
} 

API mà tôi đang nói sẽ cho phép bạn đưa ra một phương thức cho phép bạn thể hiện GetCustomersInAlaskaWithChildren(hoặc bất kỳ sự kết hợp tiêu chí nào khác) một cách linh hoạt và repo sẽ thực thi nó như một IQueryable và trả lại kết quả cho bạn. Nhưng điều quan trọng là nó chạy bên trong lớp kho lưu trữ, vì vậy nó vẫn tận dụng lợi thế của việc thực thi bị trì hoãn. Khi bạn nhận được kết quả trở lại, bạn vẫn có thể LINQ-to-object trên đó với nội dung trái tim của bạn.

Một ưu điểm khác của cách tiếp cận như thế này là bởi vì chúng là các lớp POCO, kho lưu trữ này có thể nằm sau một dịch vụ web hoặc WCF; nó có thể được tiếp xúc với AJAX hoặc những người gọi khác không biết điều đầu tiên về LINQ.


4
Vì vậy, bạn sẽ xây dựng API truy vấn để thay thế cho API truy vấn tích hợp của .NET?
Michael Brown

4
Nghe có vẻ khá vô nghĩa đối với tôi
ozz 26/03/13

7
@MikeBrown Điểm của câu hỏi này - và câu trả lời của tôi - là bạn muốn phơi bày hành vi hoặc lợi thế của việc IQueryable không phơi bày IQueryablechính nó . Làm thế nào bạn sẽ làm điều đó, với API tích hợp?
GalacticCowboy

2
Đó là một tautology rất tốt. Bạn đã không giải thích lý do tại sao bạn muốn tránh điều đó. Heck WCF Data Services hiển thị IQueryable trên toàn bộ dây. Tôi không cố gắng để chọn bạn, tôi chỉ không hiểu sự ác cảm này với những người IQueryable dường như có.
Michael Brown

2
Nếu bạn chỉ sử dụng một IQueryable, thì tại sao bạn thậm chí sẽ sử dụng một kho lưu trữ? Các kho lưu trữ được dự định để ẩn chi tiết thực hiện. (Ngoài ra, Dịch vụ dữ liệu WCF mới hơn một chút so với hầu hết các giải pháp tôi đã làm trong 4 năm qua đã sử dụng kho lưu trữ như thế này ... Vì vậy, không có khả năng chúng sẽ được cập nhật bất cứ lúc nào chỉ để thực hiện sử dụng một món đồ chơi mới sáng bóng.)
GalacticCowboy

8

Thực sự chỉ có một câu trả lời hợp pháp: nó phụ thuộc vào cách sử dụng kho lưu trữ.

Ở một thái cực, kho lưu trữ của bạn là một trình bao bọc rất mỏng xung quanh DBContext để bạn có thể tiêm veneer khả năng kiểm tra xung quanh một ứng dụng dựa trên cơ sở dữ liệu. Thực sự không có kỳ vọng trong thế giới thực rằng nó có thể được sử dụng theo cách bị ngắt kết nối mà không có DB thân thiện LINQ đằng sau vì ứng dụng CRUD của bạn sẽ không bao giờ cần điều đó. Vậy thì chắc chắn, tại sao không sử dụng IQueryable? Tôi có lẽ sẽ thích IEnumarable hơn khi bạn nhận được hầu hết các lợi ích [đọc: thực thi bị trì hoãn] và nó không cảm thấy bẩn.

Trừ khi bạn đang ngồi ở mức cực đoan đó, tôi sẽ cố gắng tận dụng tinh thần của mẫu kho lưu trữ và trả về các bộ sưu tập được vật chất hóa phù hợp không có hàm ý kết nối cơ sở dữ liệu cơ bản.


6
Về việc trở lại IEnumerable, điều quan trọng cần lưu ý ((IEnumerable<T>)query).LastOrDefault()là rất khác so với query.LastOrDefault()(giả sử querylà một IQueryable). Vì vậy, nó chỉ có ý nghĩa để trở lại IEnumerablenếu bạn sẽ lặp đi lặp lại qua tất cả các yếu tố.
Lou

7
 > Should Repositories return IQueryable?

KHÔNG nếu bạn muốn thực hiện phát triển / hủy bỏ thử nghiệm vì không có cách nào dễ dàng để tạo một kho lưu trữ giả để kiểm tra businesslogic (= repository-Consumer) của bạn tách biệt khỏi cơ sở dữ liệu hoặc nhà cung cấp IQueryable khác.


6

Từ quan điểm kiến ​​trúc thuần túy, IQueryable là một sự trừu tượng hóa bị rò rỉ. Nói chung, nó cung cấp cho người dùng quá nhiều sức mạnh. Điều đó đang được nói, có những nơi nó có ý nghĩa. Nếu bạn đang sử dụng OData, IQueryable giúp việc cung cấp một điểm cuối có thể dễ dàng lọc, sắp xếp, có thể nhóm được, v.v ... Tôi thực sự thích tạo DTO mà bản đồ thực thể của tôi thay vào đó và IQueryable trả về DTO. Bằng cách đó, tôi lọc trước dữ liệu của mình và thực sự chỉ sử dụng phương thức IQueryable để cho phép khách hàng tùy chỉnh kết quả.


6

Tôi cũng đã đối mặt với sự lựa chọn như vậy.

Vì vậy, hãy tóm tắt các mặt tích cực và tiêu cực:

Tích cực:

  • Uyển chuyển. Nó chắc chắn linh hoạt và thuận tiện để cho phép phía khách hàng xây dựng các truy vấn tùy chỉnh chỉ với một phương thức lưu trữ

Tiêu cực:

  • Không thể đo đếm được . (bạn sẽ thực sự kiểm tra việc triển khai IQueryable cơ bản, thường là ORM của bạn. Nhưng thay vào đó bạn nên kiểm tra logic của mình)
  • Làm mờ trách nhiệm . Không thể nói, phương pháp cụ thể của kho lưu trữ của bạn làm gì. Trên thực tế, đó là một phương thức thần, có thể trả về bất cứ thứ gì bạn thích trong toàn bộ cơ sở dữ liệu của bạn
  • Không an toàn . Không có hạn chế về những gì có thể được truy vấn bởi phương thức kho lưu trữ này, trong một số trường hợp, việc triển khai như vậy có thể không an toàn từ điểm bảo mật.
  • Các vấn đề bộ đệm (hoặc, nói chung, bất kỳ vấn đề trước hoặc sau xử lý). Nó thường không khôn ngoan, ví dụ, lưu trữ mọi thứ trong ứng dụng của bạn. Bạn sẽ cố gắng lưu trữ một cái gì đó, điều đó khiến cơ sở dữ liệu của bạn tải đáng kể. Nhưng làm thế nào để làm điều đó, nếu bạn chỉ có một phương thức lưu trữ, mà các máy khách đó sử dụng để đưa ra hầu như bất kỳ truy vấn nào đối với cơ sở dữ liệu?
  • Vấn đề đăng nhập . Ghi nhật ký một cái gì đó ở lớp kho lưu trữ sẽ khiến bạn kiểm tra IQueryable trước để kiểm tra xem cuộc gọi cụ thể này có được ghi lại hay không.

Tôi sẽ khuyên bạn không nên sử dụng phương pháp này. Thay vào đó, hãy xem xét việc tạo một số Thông số kỹ thuật và triển khai các phương thức kho lưu trữ, có thể truy vấn cơ sở dữ liệu bằng cách sử dụng các phương thức đó. Mẫu đặc tả là một cách tuyệt vời để đóng gói một tập hợp các điều kiện.


5

Tôi nghĩ rằng việc hiển thị IQueryable trên kho lưu trữ của bạn là hoàn toàn chấp nhận được trong các giai đoạn phát triển ban đầu. Có các công cụ UI có thể làm việc trực tiếp với IQueryable và xử lý việc sắp xếp, lọc, nhóm, v.v.

Điều đó đang được nói, tôi nghĩ rằng chỉ cần phơi bày crud trên kho lưu trữ và gọi nó là một ngày là vô trách nhiệm. Có các hoạt động hữu ích và trợ giúp truy vấn cho các truy vấn phổ biến cho phép bạn tập trung logic cho truy vấn đồng thời hiển thị bề mặt giao diện nhỏ hơn cho người tiêu dùng của kho lưu trữ.

Đối với một vài lần lặp đầu tiên của dự án, việc hiển thị IQueryable công khai trên kho lưu trữ cho phép tăng trưởng nhanh hơn. Trước khi phát hành đầy đủ đầu tiên, tôi khuyên bạn nên đặt hoạt động truy vấn ở chế độ riêng tư hoặc được bảo vệ và mang các truy vấn hoang dã dưới một mái nhà.


4

Những gì tôi đã thấy đã làm (và những gì tôi đang thực hiện bản thân) là như sau:

public interface IEntity<TKey>
{
    TKey Id { get; set; }
}

public interface IRepository<TEntity, in TKey> where TEntity : IEntity<TKey>
{
    void Create(TEntity entity);
    TEntity Get(TKey key);
    IEnumerable<TEntity> GetAll();
    IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> expression);
    void Update(TEntity entity);
    void Delete(TKey id);
}

Điều này cho phép bạn làm là có sự linh hoạt khi thực hiện IQueryable.Where(a => a.SomeFilter)truy vấn trên repo của mình bằng cách thực hiện concreteRepo.GetAll(a => a.SomeFilter)mà không để lộ bất kỳ doanh nghiệp LINQy nào.

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.