리포지토리가 IQueryable을 반환해야합니까?


66

의 인스턴스를 반환하는 리포지토리가있는 많은 프로젝트를보고 IQueryable있습니다. 이를 통해 IQueryable다른 코드에서 추가 필터 및 정렬을 수행 할 수 있으며 생성되는 다른 SQL로 변환됩니다. 이 패턴의 출처와 좋은 아이디어인지 궁금합니다.

저의 가장 큰 관심사는 IQueryable데이터베이스가 열거 될 때 언젠가 데이터베이스에 도달 할 것이라는 약속입니다. 이는 저장소 외부에서 오류가 발생했음을 의미합니다. 이는 Entity Framework 예외가 다른 애플리케이션 계층에서 발생 함을 의미 할 수 있습니다.

또한 과거에 (특히 트랜잭션을 사용할 때) MARS (Multiple Active Result Set)와 관련된 문제가 발생 했으며이 접근법이 더 자주 발생하는 것처럼 들립니다.

리포지토리 코드를 떠나기 전에 데이터베이스가 적중되었는지 확인하기 위해 항상 각 LINQ 표현식을 호출 AsEnumerable하거나 ToArray종료했습니다.

반환 IQueryable이 데이터 계층의 빌딩 블록으로 유용 할 수 있는지 궁금합니다 . 하나의 저장소가 다른 저장소를 호출하여 훨씬 더 큰 빌드를하는 매우 사치스러운 코드를 보았습니다 IQueryable.


지연된 쿼리 실행시와 쿼리 생성시 db를 사용할 수없는 경우 차이점은 무엇입니까? 소비 코드는 상황에 관계없이 처리해야합니다.
Steven Evers

흥미로운 질문이지만 대답하기 어려울 것입니다. 간단한 검색은 귀하의 질문에 대한 상당히 논쟁을 보여줍니다 : duckduckgo.com/?q=repository+return+iqueryable
Corbin March

6
이 모든 것은 클라이언트가 지연된 실행으로 이익을 얻을 수있는 Linq 절을 추가하도록 허용할지 여부에 달려 있습니다. 그들이 wheredeferred에 절을 추가 하면 전체 결과 세트가 아닌 유선을 통해 해당 데이터 IQueryable를 보내면 됩니다.
Robert Harvey

저장소 패턴을 사용하는 경우-아니오, IQueryable을 리턴하지 않아야합니다. 여기 좋은 이유가 있습니다.
Arnis Lapsa

1
@SteveEvers 큰 차이가 있습니다. 저장소에 예외가 발생하면 데이터 계층 별 예외 유형으로 래핑 할 수 있습니다. 나중에 발생하면 원래 예외 유형을 캡처해야합니다. 예외의 원인에 가까울수록 원인의 원인을 더 많이 알 수 있습니다. 의미있는 오류 메시지를 작성하는 데 중요합니다. IMHO, EF 관련 예외가 내 저장소를 떠나도록 허용하는 것은 캡슐화를 위반하는 것입니다.
트래비스 파크

답변:


47

IQueryable을 반환하면 저장소 소비자에게 더 많은 유연성이 제공됩니다. 그것은 결과를 좁히는 책임을 클라이언트에게 맡겨 주는데, 이는 당연히 이익이자 목발이 될 수 있습니다.

좋은면에서, 원하는 데이터를 얻기 위해 (최소한이 계층에서) GetAllActiveItems, GetAllNonActiveItems 등 수많은 저장소 메소드를 작성할 필요가 없습니다. 귀하의 선호도에 따라, 이것은 좋거나 나쁠 수 있습니다. 당신은 것입니다 (/ 함) 귀하의 구현을 준수 행동 계약을 정의 할 필요가 있지만,이 어디로 가는지 당신에게 달려 있습니다.

따라서 리포지토리 외부에하자면 검색 로직을 배치하고 사용자가 원하는대로 사용할 수 있습니다. 그래서 노출 된 IQueryable은 가장 유연성을 제공 등 메모리 필터링, 반대로 효율적인 쿼리를 허용하고, 방법을 가져 오는 특정 데이터의 톤을 만들기위한 필요성을 줄일 수 있습니다.

반면에 이제는 사용자에게 샷건을 제공했습니다. 의도하지 않았던 작업 (.include () 사용, 무거운 쿼리 수행 및 해당 구현 에서 인 메모리 필터링 수행 등)을 수행 할 수 있습니다. 전체 권한.

따라서 팀, 경험, 앱 크기, 전체 레이어 및 아키텍처에 따라 다릅니다.


작업하고 싶다면 IQueryable을 반환하면 DB를 호출하지만 계층화 및 동작 제어를 추가 할 수 있습니다.
psr

1
@psr 이것의 의미를 설명해 주시겠습니까?
트래비스 파크

1
@TravisParks-IQueryable은 DB에서 구현할 필요가 없으며 IQueryable 클래스를 직접 작성할 수 있습니다. 매우 어렵습니다. 따라서 데이터베이스 IQueryable 주위에 IQueryable 래퍼를 작성하고 IQueryable이 기본 DB에 대한 전체 액세스를 제한하도록 할 수 있습니다. 그러나 이것은 내가 열심히 할 것으로 기대하지 않을 정도로 충분하지 않습니다.
psr

2
IQueryables를 반환하지 않더라도 메서드에 단순히 전달하여 많은 메서드 (GetAllActiveItems, GetAllNonActiveItems 등)를 만들지 않아도됩니다 Expression<Func<Foo,bool>>. 이 매개 변수는 Where메소드에 직접 전달 될 수 있으므로 소비자는 IQueryable <Foo>에 직접 액세스 할 필요없이 필터를 선택할 수 있습니다.
Flater

1
@hanzolo : 리팩토링의 의미에 따라 다릅니다. 레이어 변경 및 느슨하게 결합 된 코드베이스가있는 경우 디자인 변경 (예 : 새 필드 추가 또는 관계 변경)이 더 어렵다는 것이 사실입니다. 하지만 리팩토링은 적은 당신은 단지 하나 개의 레이어를 변경해야 할 때 통증. 응용 프로그램을 다시 디자인 할 것인지 또는 동일한 기본 아키텍처의 구현을 단순히 리팩토링하는지에 따라 달라집니다. 나는 두 경우 모두 여전히 느슨한 결합을 옹호하지만 핵심 도메인 개념을 변경할 때 리팩토링에 추가하는 것은 잘못이 아닙니다.
Flater

29

공용 인터페이스에 IQueryable을 노출하는 것은 좋은 습관이 아닙니다. 그 이유는 근본적입니다. IQueryable 실현은 말 그대로 제공 할 수 없습니다.

공용 인터페이스는 공급자와 클라이언트 간의 계약입니다. 그리고 대부분의 경우 완전한 구현이 기대됩니다. IQueryable은 속임수입니다.

  1. IQueryable은 구현이 거의 불가능합니다. 그리고 현재 적절한 해결책이 없습니다. Entity Framework에도 많은 UnsupportedExceptions가 있습니다.
  2. 오늘날 쿼리 가능한 공급자는 인프라에 크게 의존합니다. Sharepoint에는 자체 부분 구현이 있으며 My SQL 공급자에는 고유 한 부분 구현이 있습니다.

리포지토리 패턴 은 계층화 및 가장 중요한 실현의 독립성을 통해 명확한 표현을 제공합니다. 향후 데이터 소스를 변경할 수 있습니다.

문제는 " 데이터 소스 대체 가능성이 있어야합니까? "입니다.

내일 데이터의 일부를 SAP 서비스, NoSQL 데이터베이스 또는 단순히 텍스트 파일로 옮길 수 있다면 IQueryable 인터페이스의 적절한 구현을 보장 할 수 있습니까?

( 이 나쁜 방법입니다 이유에 대한 좋은 점)


이 답변은 휘발성 외부 링크를 참조하지 않고 독자적으로 설 수 없습니다. 최소한 외부 자원의 핵심 요점을 요약하고 답변에서 개인적 의견을 피하도록 노력하십시오 ( "내가들은 것 중 최악의 생각"). 또한 IQueryable과 관련이없는 것으로 보이는 코드 스 니펫을 제거하십시오.
Lars Viklund

1
@LarsViklund에게 감사드립니다. 답변은 예제와 문제 설명으로 업데이트되었습니다
Artru

19

실제로 지연된 실행을 원할 경우 세 가지 대안이 있습니다.

  • 이 방법으로 노출하십시오 IQueryable.
  • 특정 필터 또는 "질문"에 대한 특정 방법을 제공하는 구조를 구현하십시오. ( GetCustomersInAlaskaWithChildren아래)
  • 강력한 형식의 필터 / 정렬 / 페이지 API를 노출하고 IQueryable내부적으로 빌드하는 구조를 구현하십시오 .

나는 세 번째를 선호하지만 (동료가 두 번째를 구현하도록 도왔지만) 분명히 설정 및 유지 관리가 필요합니다. (구조에 T4!)

편집 : 내가 말하고있는 것을 둘러싼 혼란을 없애려면 IQueryable Can Dog Your Dog, Kill Your Wife, Kill Will To Live 등에서 도난당한 다음 예를 고려하십시오 .

다음과 같은 것을 노출시키는 시나리오에서 :

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

내가 말하고있는 API GetCustomersInAlaskaWithChildren를 사용하면 유연한 방식으로 표현 (또는 다른 기준의 조합) 을 표현할 수있는 메소드를 공개 할 수 있으며 리포지토리는 IQueryable로 실행하고 결과를 반환합니다. 그러나 중요한 것은 리포지토리 계층 내에서 실행되므로 지연된 실행을 여전히 활용한다는 것입니다. 결과를 다시 가져와도 여전히 마음의 내용에 따라 LINQ-to-Objects를 수행 할 수 있습니다.

이와 같은 접근 방식의 또 다른 장점은 POCO 클래스이기 때문에이 저장소가 웹 또는 WCF 서비스 뒤에있을 수 있다는 것입니다. LINQ에 대해 가장 잘 모르는 AJAX 또는 다른 발신자에게 노출 될 수 있습니다.


4
.NET의 내장 쿼리 API를 대체 할 쿼리 API를 작성 하시겠습니까?
Michael Brown

4
나에게 무의미한 소리
ozz

7
@MikeBrown이 질문의 핵심은 제 자신을 드러내지 않으면 서의 행동 이나 장점IQueryable 드러내고 자한다는 IQueryable 입니다. 내장 API를 사용하여 어떻게 할 것입니까?
GalacticCowboy 11

2
그것은 매우 좋은 타우 톨 로지입니다. 왜 피하고 싶은지 설명하지 않았습니다. Heck WCF Data Services는 IQueryable을 무선으로 공개합니다. 나는 당신을 선택하려고하지 않고, 단지 IQueryable 사람들 에게이 혐오감을 이해하지 못하는 것 같습니다.
Michael Brown

2
IQueryable을 사용하려는 경우 왜 리포지토리를 사용합니까? 리포지토리는 구현 세부 정보를 숨기도록 고안되었습니다. (또한 WCF Data Services는 지난 4 년 동안 이와 같은 리포지토리를 사용했던 대부분의 솔루션보다 훨씬 새롭습니다. 따라서 언제라도 곧 업데이트 될 가능성은 없습니다 반짝이는 새 장난감 사용.)
GalacticCowboy

8

실제로 합법적 인 답변은 하나뿐입니다. 리포지토리 사용 방법에 따라 다릅니다.

극단적으로 저장소는 DBContext를 둘러싼 매우 얇은 래퍼이므로 데이터베이스 기반 앱 주위에 테스트 가능성을 더할 수 있습니다. CRUD 앱이 그다지 필요하지 않기 때문에 LINQ 친화적 인 DB가 없으면 연결이 끊어지지 않을 것이라는 실제 기대는 없습니다. 그렇다면 왜 IQueryable을 사용하지 않습니까? 아마 당신이 대부분의 이점 [읽기 : 지연된 실행]을 얻었을 때 IEnumarable을 선호 할 것입니다.

그 극단에 앉아 있지 않으면 저장소 패턴의 정신을 활용하고 기본 데이터베이스 연결에 영향을 미치지 않는 적절한 구체화 된 컬렉션을 반환하려고 노력합니다.


6
returning IEnumerable에 관해서 ((IEnumerable<T>)query).LastOrDefault()query.LastOrDefault()(와 같다고 가정 query) 이 매우 다르다는 점에 유의해야합니다 IQueryable. 따라서 IEnumerable어쨌든 모든 요소를 ​​반복하려는 경우 에만 반환 하는 것이 좋습니다.
Lou

7
 > Should Repositories return IQueryable?

데이터베이스 또는 다른 IQueryable 제공자와 분리하여 businesslogic (= 저장소 소비자)을 테스트하기 위해 모의 저장소를 작성하는 쉬운 방법이 없기 때문에 테스트 주도 개발 / 단위 테스트를 수행하려는 경우 아니오 .


6

순수한 아키텍처 관점에서 볼 때, IQueryable은 유출 된 추상화입니다. 일반적으로 사용자에게 너무 많은 힘을 제공합니다. 말하자면 말이되는 곳이 있습니다. OData를 사용하는 경우 IQueryable을 사용하면 쉽게 필터링하고, 정렬하고, 그룹화 할 수있는 끝점을 쉽게 제공 할 수 있습니다. 실제로 엔터티가 매핑되는 DTO를 만들고 IQueryable이 대신 DTO를 반환하는 것을 선호합니다. 그렇게하면 데이터를 사전 필터링하고 실제로 결과의 클라이언트 사용자 정의를 허용하기 위해 IQueryable 메소드 만 사용합니다.


6

나는 또한 그러한 선택에 직면했다.

따라서 긍정적 측면과 부정적인 측면을 요약 해 보겠습니다.

긍정적 :

  • 적응성. 클라이언트 측에서 단 하나의 리포지토리 방법으로 사용자 지정 쿼리를 작성할 수 있도록하는 것이 유연하고 편리합니다.

제외 어 :

  • 테스트 할 수 없습니다 . (실제로 ORM 인 기본 IQueryable 구현을 테스트하지만 대신 논리를 테스트해야합니다)
  • 책임을 흐리게합니다 . 저장소의 특정 방법이 무엇을하는지는 말할 수 없습니다. 사실, 그것은 전체 데이터베이스에서 원하는 것을 반환 할 수있는 신 방법입니다.
  • 안전하지 않습니다 . 이 리포지토리 방법으로 쿼리 할 수있는 항목에 대한 제한이 없으면 경우에 따라 이러한 구현이 보안 지점에서 안전하지 않을 수 있습니다.
  • 캐싱 문제 (또는 일반적인 전처리 또는 사후 처리 문제) 예를 들어 응용 프로그램의 모든 것을 캐시하는 것은 일반적으로 현명하지 않습니다. 데이터베이스에 상당한로드를 유발하는 무언가를 캐시하려고 시도합니다. 그러나 저장소 방법이 하나 뿐인 경우 클라이언트가 데이터베이스에 대해 거의 모든 쿼리를 실행하는 데 사용하는 방법은 무엇입니까?
  • 로깅 문제 . 리포지토리 계층에 무언가를 기록하면 IQueryable을 먼저 검사하여이 특정 호출이 기록되는지 확인합니다.

이 방법을 사용하지 않는 것이 좋습니다. 대신 여러 스펙을 작성 하고이를 사용하여 데이터베이스를 조회 할 수있는 저장소 메소드를 구현하십시오. 사양 패턴 은 일련의 조건을 캡슐화하는 좋은 방법입니다.


5

초기 개발 단계에서 저장소에 IQueryable을 노출하는 것이 완벽하게 허용된다고 생각합니다. IQueryable과 직접 작동하고 정렬, 필터링, 그룹화 등을 처리 할 수있는 UI 도구가 있습니다.

즉, 저장소에 crud를 노출시키고 하루를 호출하는 것은 무책임하다고 생각합니다. 일반적인 쿼리에 유용한 작업 및 쿼리 도우미를 사용하면 쿼리의 논리를 중앙 집중화 할 수 있으며 리포지토리 소비자에게 더 작은 인터페이스 표면을 노출 할 수 있습니다.

프로젝트의 처음 몇 번의 반복에서 IQueryable을 저장소에 공개적으로 노출하면 더 빠르게 성장할 수 있습니다. 첫 번째 정식 릴리스 전에 쿼리 작업을 개인 또는 보호로 설정하고 와일드 쿼리를 한 지붕 아래로 가져 오는 것이 좋습니다.


4

내가 본 것과 내가 구현 한 것은 다음과 같습니다.

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

이것이 할 수 있는 일은 LINQy 비즈니스를 노출시키지 않고 리포지토리 IQueryable.Where(a => a.SomeFilter)에 대한 쿼리를 유연하게 수행하는 것 concreteRepo.GetAll(a => a.SomeFilter)입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.