잘 설계된 쿼리 명령 및 / 또는 사양


91

나는 전형적인 리포지토리 패턴 (특수 질의에 대한 메소드 목록 증가 등)이 제시하는 문제에 대한 좋은 해결책을 꽤 오랫동안 찾고있었습니다. http://ayende.com/blog/3955/repository- is-the-new-singleton ).

특히 사양 패턴을 사용하여 명령 쿼리를 사용하는 아이디어가 정말 마음에 듭니다. 그러나 사양에 대한 내 문제는 단순 선택 기준 (기본적으로 where 절)에만 관련되고 조인, 그룹화, 하위 집합 선택 또는 투영 등과 같은 쿼리의 다른 문제는 처리하지 않는다는 것입니다. 기본적으로 올바른 데이터 집합을 얻기 위해 많은 쿼리가 거쳐야하는 모든 추가 작업이 있습니다.

(참고 : 쿼리 개체라고도하는 명령 패턴에서 "명령"이라는 용어를 사용합니다. 쿼리와 명령 (업데이트, 삭제, 끼워 넣다))

그래서 저는 전체 쿼리를 캡슐화하는 대안을 찾고 있지만, 명령 클래스의 폭발적인 증가를 위해 스파게티 리포지토리를 교체 할 수있을만큼 충분히 유연합니다.

예를 들어 Linqspecs를 사용했으며 선택 기준에 의미있는 이름을 할당 할 수 있다는 점에서 가치를 찾았지만 충분하지 않습니다. 아마도 여러 접근 방식을 결합한 혼합 솔루션을 찾고 있습니다.

다른 사람들이이 문제를 해결하거나 다른 문제를 해결하기 위해 개발했을 수있는 솔루션을 찾고 있지만 여전히 이러한 요구 사항을 충족합니다. 링크 된 기사에서 Ayende는 nHibernate 컨텍스트를 직접 사용하도록 제안하지만, 이제 쿼리 정보도 포함해야하므로 비즈니스 계층이 크게 복잡해집니다.

대기 기간이 지나는대로 현상금을 지급하겠습니다. 따라서 좋은 설명과 함께 귀하의 솔루션을 바운티에 합당하게 만드십시오. 최선의 솔루션을 선택하고 준우승자를 찬성하겠습니다.

참고 : ORM 기반의 것을 찾고 있습니다. 명시 적으로 EF 또는 nHibernate 일 필요는 없지만 가장 일반적이며 가장 적합합니다. 다른 ORM에 쉽게 적용 할 수 있다면 보너스가 될 것입니다. Linq 호환도 좋을 것입니다.

업데이트 : 여기에 좋은 제안이 많지 않다는 사실에 정말 놀랐습니다. 사람들은 완전히 CQRS이거나 완전히 Repository 캠프에있는 것 같습니다. 내 앱의 대부분은 CQRS를 보증하기에 충분히 복잡하지 않습니다 (대부분의 CQRS 옹호자들은이를 사용해서는 안된다고 쉽게 말합니다).

업데이트 : 여기에 약간의 혼란이있는 것 같습니다. 저는 새로운 데이터 액세스 기술을 찾고있는 것이 아니라 비즈니스와 데이터 간의 합리적으로 잘 설계된 인터페이스를 찾고 있습니다.

이상적으로 내가 찾고있는 것은 쿼리 개체, 사양 패턴 및 저장소 간의 교차입니다. 위에서 말했듯이 사양 패턴은 조인, 하위 선택 등과 같은 쿼리의 다른 측면이 아닌 where 절 측면 만 처리합니다. 리포지토리는 전체 쿼리를 처리하지만 잠시 후 손을 뗍니다. . 쿼리 개체도 전체 쿼리를 처리하지만 단순히 리포지토리를 쿼리 개체의 폭발로 바꾸고 싶지는 않습니다.


5
환상적인 질문입니다. 저도 제가 제안한 것보다 더 많은 경험을 가진 사람들을보고 싶습니다. 나는 일반 리포지토리에 Command 개체 또는 Query 개체에 대한 오버로드도 포함되어있는 현재 코드 기반에서 작업 중입니다. 누가 그 구조는 Ayende가 그의 블로그에서 설명하는 것과 유사합니다. PS : 이것은 프로그래머들에게도 약간의 관심을 끌 수 있습니다.
Simon Whitehead 2013 년

LINQ에 대한 종속성이 마음에 들지 않는다면 IQueryable을 노출하는 리포지토리를 사용하지 않는 이유는 무엇입니까? 일반적인 접근 방식은 일반 리포지토리이며, 위의 재사용 가능한 논리가 필요한 경우 추가 메서드로 파생 리포지토리 유형을 만듭니다.
devdigital 2013 년

@devdigital-Linq에 대한 종속성은 데이터 구현에 대한 종속성과 동일하지 않습니다. 다른 비즈니스 계층 기능을 정렬하거나 수행 할 수 있도록 개체에 Linq를 사용하고 싶습니다. 하지만 그렇다고 데이터 모델 구현에 대한 종속성을 원한다는 의미는 아닙니다. 내가 여기서 정말로 말하는 것은 레이어 / 티어 인터페이스입니다. 예를 들어 쿼리를 200 개 위치에서 변경할 필요없이 쿼리를 변경할 수 있기를 원하는데, IQueryable을 비즈니스 모델에 직접 푸시하면 이런 일이 발생합니다.
Erik Funkenbusch 2013 년

1
@devdigital-기본적으로 저장소의 문제를 비즈니스 계층으로 이동합니다. 당신은 단지 문제를 뒤섞 고 있습니다.
Erik Funkenbusch 2013 년

답변:


95

면책 조항 : 아직 훌륭한 답변이 없기 때문에 얼마 전에 읽은 훌륭한 블로그 게시물의 일부를 거의 그대로 복사하기로 결정했습니다. 여기 에서 전체 블로그 게시물을 찾을 수 있습니다 . 그래서 여기 있습니다 :


다음 두 가지 인터페이스를 정의 할 수 있습니다.

public interface IQuery<TResult>
{
}

public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
    TResult Handle(TQuery query);
}

IQuery<TResult>지정는 사용하여 반환하는 데이터를 특정 쿼리를 정의하는 메시지 TResult일반적인 유형입니다. 이전에 정의 된 인터페이스를 사용하여 다음과 같은 쿼리 메시지를 정의 할 수 있습니다.

public class FindUsersBySearchTextQuery : IQuery<User[]>
{
    public string SearchText { get; set; }
    public bool IncludeInactiveUsers { get; set; }
}

이 클래스는 두 개의 매개 변수를 사용하여 쿼리 작업을 정의하며 결과적으로 User개체 배열이 생성 됩니다. 이 메시지를 처리하는 클래스는 다음과 같이 정의 할 수 있습니다.

public class FindUsersBySearchTextQueryHandler
    : IQueryHandler<FindUsersBySearchTextQuery, User[]>
{
    private readonly NorthwindUnitOfWork db;

    public FindUsersBySearchTextQueryHandler(NorthwindUnitOfWork db)
    {
        this.db = db;
    }

    public User[] Handle(FindUsersBySearchTextQuery query)
    {
        return db.Users.Where(x => x.Name.Contains(query.SearchText)).ToArray();
    }
}

이제 소비자가 일반 IQueryHandler인터페이스에 의존하도록 할 수 있습니다 .

public class UserController : Controller
{
    IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler;

    public UserController(
        IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler)
    {
        this.findUsersBySearchTextHandler = findUsersBySearchTextHandler;
    }

    public View SearchUsers(string searchString)
    {
        var query = new FindUsersBySearchTextQuery
        {
            SearchText = searchString,
            IncludeInactiveUsers = false
        };

        User[] users = this.findUsersBySearchTextHandler.Handle(query);    
        return View(users);
    }
}

즉시이 모델은 우리에게 많은 유연성을 제공합니다 UserController.. 완전히 다른 구현 또는 실제 구현을 래핑하는 구현을 삽입 할 수 있습니다.UserController 해당 인터페이스의 다른 모든 소비자를 있습니다.

IQuery<TResult>인터페이스를 지정하거나 주입 할 때 우리가 지원 컴파일시 제공 IQueryHandlers우리의 코드. 우리는을 변경하면 FindUsersBySearchTextQuery반환 UserInfo[](구현하는 대신 IQuery<UserInfo[]>)의은 UserController에 제네릭 형식 제약 조건이 있기 때문에, 컴파일 할 수 없게됩니다 IQueryHandler<TQuery, TResult>매핑 할 수 없습니다 FindUsersBySearchTextQueryUser[] .

주입 IQueryHandler그러나 소비자에게 인터페이스를 것은 여전히 ​​해결해야 할 몇 가지 덜 분명한 문제를 가지고 있습니다. 소비자의 종속성 수가 너무 커져 생성자가 너무 많은 인수를 사용하는 경우 생성자 과잉 주입으로 이어질 수 있습니다. 클래스가 실행하는 쿼리 수는 자주 변경 될 수 있으므로 생성자 인수 수를 지속적으로 변경해야합니다.

IQueryHandlers추가 추상화 계층으로 너무 많이 주입해야하는 문제를 해결할 수 있습니다 . 소비자와 쿼리 처리기 사이에있는 중재자를 만듭니다.

public interface IQueryProcessor
{
    TResult Process<TResult>(IQuery<TResult> query);
}

IQueryProcessor하나 개의 일반적인 방법이 아닌 일반적인 인터페이스입니다. 인터페이스 정의에서 볼 수 있듯이 인터페이스에 IQueryProcessor따라 다릅니다 IQuery<TResult>. 이를 통해 .NET Framework에 의존하는 소비자에서 컴파일 시간을 지원할 수 있습니다 IQueryProcessor. UserControllernew를 사용 하도록를 다시 작성해 보겠습니다 IQueryProcessor.

public class UserController : Controller
{
    private IQueryProcessor queryProcessor;

    public UserController(IQueryProcessor queryProcessor)
    {
        this.queryProcessor = queryProcessor;
    }

    public View SearchUsers(string searchString)
    {
        var query = new FindUsersBySearchTextQuery
        {
            SearchText = searchString,
            IncludeInactiveUsers = false
        };

        // Note how we omit the generic type argument,
        // but still have type safety.
        User[] users = this.queryProcessor.Process(query);

        return this.View(users);
    }
}

UserController지금에 따라 IQueryProcessor우리의 모든 쿼리를 처리 할 수. UserControllerSearchUsers방법은 호출 IQueryProcessor.Process초기화 된 질의 객체를 전달 방법. 인터페이스를 FindUsersBySearchTextQuery구현 하므로 IQuery<User[]>일반 Execute<TResult>(IQuery<TResult> query)메서드에 전달할 수 있습니다 . C # 유형 추론 덕분에 컴파일러는 제네릭 유형을 결정할 수 있으므로 명시 적으로 유형을 명시 할 필요가 없습니다. 반환 유형Process메서드 도 알려져 있습니다.

이제의 구현 IQueryProcessor에서 권리를 찾을 책임이 IQueryHandler있습니다. 이를 위해서는 동적 타이핑과 선택적으로 의존성 주입 프레임 워크의 사용이 필요하며 몇 줄의 코드로 모두 수행 할 수 있습니다.

sealed class QueryProcessor : IQueryProcessor
{
    private readonly Container container;

    public QueryProcessor(Container container)
    {
        this.container = container;
    }

    [DebuggerStepThrough]
    public TResult Process<TResult>(IQuery<TResult> query)
    {
        var handlerType = typeof(IQueryHandler<,>)
            .MakeGenericType(query.GetType(), typeof(TResult));

        dynamic handler = container.GetInstance(handlerType);

        return handler.Handle((dynamic)query);
    }
}

QueryProcessor클래스는 특정 구조 IQueryHandler<TQuery, TResult>제공된 조회 인스턴스의 유형에 따라 유형입니다. 이 유형은 제공된 컨테이너 클래스에 해당 유형의 인스턴스를 가져 오도록 요청하는 데 사용됩니다. 안타깝게도 Handle리플렉션을 사용하여 메서드 를 호출해야합니다 (이 경우 C # 4.0 동적 키워드 TQuery사용). 컴파일 타임에 제네릭 인수를 사용할 수 없기 때문에이 시점에서 핸들러 인스턴스를 캐스팅 할 수 없기 때문 입니다. 그러나 Handle메서드의 이름을 바꾸거나 다른 인수를 가져 오지 않는 한이 호출은 실패하지 않으며 원하는 경우이 클래스에 대한 단위 테스트를 작성하는 것이 매우 쉽습니다. 리플렉션을 사용하면 약간 떨어질 수 있지만 실제로 걱정할 필요는 없습니다.


우려 사항 중 하나에 답변하려면 :

그래서 저는 전체 쿼리를 캡슐화하는 대안을 찾고 있지만, 명령 클래스의 폭발적인 증가를 위해 스파게티 리포지토리를 교체 할 수있을만큼 충분히 유연합니다.

이 디자인을 사용한 결과는 시스템에 작은 클래스가 많이있을 것이지만 작은 / 집중된 클래스 (명확한 이름 포함)를 많이 갖는 것은 좋은 것입니다. 이 접근 방식은 하나의 쿼리 클래스로 그룹화 할 수 있으므로 저장소의 동일한 메서드에 대해 다른 매개 변수가있는 많은 오버로드를 갖는 것보다 훨씬 낫습니다. 따라서 여전히 저장소의 메서드보다 쿼리 클래스가 훨씬 적습니다.


2
상을받은 것 같습니다. 나는 개념을 좋아한다. 나는 누군가가 진정으로 다른 것을 제시하기를 바랐다. 축하합니다.
Erik Funkenbusch 2013 년

1
@FuriCuri, 단일 클래스에 실제로 5 개의 쿼리가 필요합니까? 아마도 당신은 너무 많은 책임을 가진 수업으로 볼 수있을 것입니다. 또는 쿼리가 집계되는 경우 실제로 단일 쿼리 여야합니다. 물론 이것은 단지 제안 일뿐입니다.
Sam

1
@stakx 내 첫 번째 예제 TResult에서 IQuery인터페이스 의 일반 매개 변수 가 유용하지 않다는 것이 절대적으로 맞습니다 . 그러나 업데이트 된 응답에서 TResult매개 변수는의 Process메서드 에서 런타임 IQueryProcessor에 문제를 해결하는 데 사용됩니다 IQueryHandler.
david.s

1
또한 매우 유사한 구현이있는 블로그가있어 올바른 경로를 선택하고 있습니다.이 링크는 jupaol.blogspot.mx/2012/11/… 이고 PROD 응용 프로그램에서 한동안 사용하고 있습니다. 그러나 나는이 접근 방식에 문제가 있습니다. 쿼리 연결 및 재사용 더 복잡한 쿼리를 생성 하기 위해 결합 해야하는 작은 쿼리가 여러 개 있다고 가정 해 보겠습니다 . 코드를 복제하기 만했지만 더 나은 방법을 찾고 있습니다. 어떤 아이디어?
Jupaol

4
@Cemre 결국 Extension 메서드에서 쿼리를 캡슐화 IQueryable하고 컬렉션을 열거하지 않도록 한 다음 QueryHandler방금 쿼리를 호출 / 체인에서 호출했습니다. 이것은 내 쿼리를 단위 테스트하고 연결할 수있는 유연성을 제공했습니다. 내을 (를) 위에 응용 프로그램 서비스가 QueryHandler있고 컨트롤러가 핸들러 대신 서비스와 직접 대화를 담당합니다.
Jupaol 2015-08-13

4

그것을 다루는 나의 방식은 실제로 단순하고 ORM에 구애받지 않습니다. 리포지토리에 대한 내 견해는 다음과 같습니다. 리포지토리의 역할은 컨텍스트에 필요한 모델을 앱에 제공하는 것입니다. 따라서 앱은 리포지토리에 원하는 것을 요청 하지만 방법을 알려주지 않습니다. 가져 오는 을 .

리포지토리 메서드에 Criteria (예, DDD 스타일)를 제공합니다. 이는 리포지토리에서 쿼리를 생성하는 데 사용할 것입니다 (또는 필요한 것은 무엇이든-웹 서비스 요청 일 수 있음). 조인 및 그룹 imho는 방법에 대한 세부 사항이며, 무엇과 기준이 where 절을 작성하는 기반이되어야합니다.

모델 = 앱에 필요한 최종 개체 또는 데이터 구조.

public class MyCriteria
{
   public Guid Id {get;set;}
   public string Name {get;set;}
    //etc
 }

 public interface Repository
  {
       MyModel GetModel(Expression<Func<MyCriteria,bool>> criteria);
   }

원하는 경우 ORM 기준 (Nhibernate)을 직접 사용할 수 있습니다. 저장소 구현은 기본 저장소 또는 DAO와 함께 기준을 사용하는 방법을 알고 있어야합니다.

귀하의 도메인과 모델 요구 사항을 모르지만 앱이 쿼리 자체를 작성하는 것이 가장 좋은 방법이라면 이상 할 것입니다. 모델이 너무 많이 변경되어 안정적인 것을 정의 할 수 없습니까?

이 솔루션에는 분명히 몇 가지 추가 코드가 필요하지만 나머지 코드를 ORM 또는 스토리지에 액세스하는 데 사용하는 모든 것에 연결하지 않습니다. 저장소는 파사드 역할을하고 IMO는 깨끗하고 '기준 변환'코드를 재사용 할 수 있습니다.


이는 리포지토리 증가 문제를 해결하지 못하며 다양한 종류의 데이터를 반환하는 방법 목록이 계속 확장되고 있습니다. 나는 당신이 이것에 대한 문제를 보지 못할 수도 있다는 것을 이해합니다 (많은 사람들은 그렇지 않습니다), 다른 사람들은 그것을 다르게 보입니다 (제가 링크 한 기사를 읽는 것이 좋습니다. 비슷한 의견을 가진 많은 사람들이 있습니다).
Erik Funkenbusch 2013 년

1
기준이 많은 방법을 불필요하게 만들기 때문입니다. 물론, 그들 모두는 당신이 필요로하는 것에 대해 아무것도 모르고는 많이 말할 수 없습니다. DB를 직접 쿼리하고 싶지만 저장소가 방해가 될 수 있습니다. 만약 당신이 관계형 정신을 가지고 일해야한다면, 리포지터리가 필요하지 않고 직접 가십시오. 그리고 참고로 얼마나 많은 사람들이 그 게시물에 Ayende를 인용하는지 짜증이납니다. 나는 그것에 동의하지 않으며 많은 개발자들이 패턴을 잘못된 방식으로 사용하고 있다고 생각합니다.
MikeSW

1
문제를 다소 줄일 수 있지만 충분히 큰 응용 프로그램이 주어지면 여전히 괴물 저장소를 생성합니다. 나는 메인 로직에서 직접 nHibernate를 사용하는 Ayende의 솔루션에 동의하지 않지만, 통제 할 수없는 저장소 성장의 부조리에 대해서는 동의합니다. 데이터베이스를 직접 쿼리하고 싶지는 않지만 저장소에서 쿼리 개체의 폭발로 문제를 옮기고 싶지는 않습니다.
Erik Funkenbusch 2013 년

2

나는 이것을했고, 이것을 지원하고 이것을 취소했다.

주요 문제는 이것입니다. 어떻게하든 추가 된 추상화는 독립성을 얻지 못합니다. 정의에 따라 누출됩니다. 본질적으로 코드를 귀엽게 보이게하기 위해 전체 레이어를 발명하는 것입니다.하지만 유지 보수를 줄이거 나 가독성을 높이거나 어떤 유형의 모델 불가지론도 얻지는 못합니다.

재미있는 부분은 Olivier의 응답에 대한 응답으로 자신의 질문에 답했다는 것입니다. "이것은 본질적으로 Linq에서 얻는 모든 이점없이 Linq의 기능을 복제하는 것입니다."

스스로에게 물어보십시오. 어떻게 그럴 수 없습니까?


글쎄요, 저는 Linq를 귀하의 비즈니스 계층에 통합하는 문제를 확실히 경험했습니다. 매우 강력하지만 데이터 모델을 변경하면 악몽입니다. 리포지토리를 사용하면 상황이 개선됩니다. 비즈니스 계층에 많은 영향을주지 않고 현지화 된 장소에서 변경할 수 있기 때문입니다 (변경 사항을 지원하기 위해 비즈니스 계층도 변경해야하는 경우 제외). 그러나 리포지토리는 SRP를 엄청나게 위반하는 이러한 부풀어 오른 레이어가됩니다. 나는 당신의 요점을 이해하지만 실제로 어떤 문제도 해결하지 못합니다.
Erik Funkenbusch 2013 년

데이터 계층에서 LINQ를 사용하고 데이터 모델을 변경해야 비즈니스 계층을 변경해야하는 경우 제대로 계층화되지 않습니다.
스투

나는 당신이 더 이상 그 레이어를 추가하지 않는다고 말하는 줄 알았는데. 추가 된 추상화가 아무 것도 얻지 못한다고 말하면, 이는 nHibernate 세션 (또는 EF 컨텍스트)을 비즈니스 계층으로 직접 전달하는 것에 대해 Ayende와 동의 함을 의미합니다.
Erik Funkenbusch 2013 년

1

유창한 인터페이스를 사용할 수 있습니다. 기본 아이디어는 클래스의 메서드가 어떤 작업을 수행 한 후 바로이 클래스의 현재 인스턴스를 반환한다는 것입니다. 이를 통해 메서드 호출을 연결할 수 있습니다.

적절한 클래스 계층을 만들면 액세스 가능한 메서드의 논리적 흐름을 만들 수 있습니다.

public class FinalQuery
{
    protected string _table;
    protected string[] _selectFields;
    protected string _where;
    protected string[] _groupBy;
    protected string _having;
    protected string[] _orderByDescending;
    protected string[] _orderBy;

    protected FinalQuery()
    {
    }

    public override string ToString()
    {
        var sb = new StringBuilder("SELECT ");
        AppendFields(sb, _selectFields);
        sb.AppendLine();

        sb.Append("FROM ");
        sb.Append("[").Append(_table).AppendLine("]");

        if (_where != null) {
            sb.Append("WHERE").AppendLine(_where);
        }

        if (_groupBy != null) {
            sb.Append("GROUP BY ");
            AppendFields(sb, _groupBy);
            sb.AppendLine();
        }

        if (_having != null) {
            sb.Append("HAVING").AppendLine(_having);
        }

        if (_orderBy != null) {
            sb.Append("ORDER BY ");
            AppendFields(sb, _orderBy);
            sb.AppendLine();
        } else if (_orderByDescending != null) {
            sb.Append("ORDER BY ");
            AppendFields(sb, _orderByDescending);
            sb.Append(" DESC").AppendLine();
        }

        return sb.ToString();
    }

    private static void AppendFields(StringBuilder sb, string[] fields)
    {
        foreach (string field in fields) {
            sb.Append(field).Append(", ");
        }
        sb.Length -= 2;
    }
}

public class GroupedQuery : FinalQuery
{
    protected GroupedQuery()
    {
    }

    public GroupedQuery Having(string condition)
    {
        if (_groupBy == null) {
            throw new InvalidOperationException("HAVING clause without GROUP BY clause");
        }
        if (_having == null) {
            _having = " (" + condition + ")";
        } else {
            _having += " AND (" + condition + ")";
        }
        return this;
    }

    public FinalQuery OrderBy(params string[] fields)
    {
        _orderBy = fields;
        return this;
    }

    public FinalQuery OrderByDescending(params string[] fields)
    {
        _orderByDescending = fields;
        return this;
    }
}

public class Query : GroupedQuery
{
    public Query(string table, params string[] selectFields)
    {
        _table = table;
        _selectFields = selectFields;
    }

    public Query Where(string condition)
    {
        if (_where == null) {
            _where = " (" + condition + ")";
        } else {
            _where += " AND (" + condition + ")";
        }
        return this;
    }

    public GroupedQuery GroupBy(params string[] fields)
    {
        _groupBy = fields;
        return this;
    }
}

당신은 이것을 이렇게 부를 것입니다

string query = new Query("myTable", "name", "SUM(amount) AS total")
    .Where("name LIKE 'A%'")
    .GroupBy("name")
    .Having("COUNT(*) > 2")
    .OrderBy("name")
    .ToString();

의 새 인스턴스 만 만들 수 있습니다 Query. 다른 클래스에는 보호 된 생성자가 있습니다. 계층 구조의 요점은 메서드를 "비활성화"하는 것입니다. 예를 들어, GroupBy메소드가 리턴 GroupedQuery의 기본 클래스 인 Query와이없는 Where방법을합니다 (여기서 방법에 선언되어있다 Query). 따라서 Where이후 에 호출 할 수 없습니다 GroupBy.

그러나 완벽하지는 않습니다. 이 클래스 계층을 사용하면 멤버를 연속적으로 숨길 수 있지만 새 멤버는 표시 할 수 없습니다. 따라서 Having이전에 호출되면 예외가 발생합니다 GroupBy.

Where여러 번 전화를 걸 수 있습니다. 이것은 AND기존 조건에 새로운 조건을 추가합니다 . 이렇게하면 단일 조건에서 프로그래밍 방식으로 필터를 쉽게 구성 할 수 있습니다. 에서도 마찬가지입니다 Having.

필드 목록을 허용하는 메소드에는 매개 변수가 params string[] fields있습니다. 단일 필드 이름 또는 문자열 배열을 전달할 수 있습니다.


Fluent 인터페이스는 매우 유연하며 다양한 매개 변수 조합을 사용하여 많은 메서드 오버로드를 생성 할 필요가 없습니다. 내 예제는 문자열로 작동하지만 접근 방식은 다른 유형으로 확장 될 수 있습니다. 특수한 경우에 대해 미리 정의 된 메서드 나 사용자 지정 형식을 허용하는 메서드를 선언 할 수도 있습니다. ExecuteReader또는 같은 메서드를 추가 할 수도 있습니다 ExceuteScalar<T>. 이렇게하면 다음과 같은 쿼리를 정의 할 수 있습니다.

var reader = new Query<Employee>(new MonthlyReportFields{ IncludeSalary = true })
    .Where(new CurrentMonthCondition())
    .Where(new DivisionCondition{ DivisionType = DivisionType.Production})
    .OrderBy(new StandardMonthlyReportSorting())
    .ExecuteReader();

이러한 방식으로 구성된 SQL 명령도 명령 매개 변수를 가질 수 있으므로 SQL 주입 문제를 방지하는 동시에 데이터베이스 서버에서 명령을 캐시 할 수 있습니다. 이것은 O / R- 매퍼를 대체하는 것은 아니지만 간단한 문자열 연결을 사용하여 명령을 만드는 상황에서 도움이 될 수 있습니다.


3
흠 .. 흥미롭지 만 솔루션이 SQL 주입 가능성에 문제가있는 것 같고 실제로 미리 컴파일 된 실행을 위해 준비된 명령문을 생성하지 않습니다 (따라서 더 느리게 수행됨). 아마도 이러한 문제를 해결하기 위해 조정될 수 있지만, 우리는 유형이 아닌 안전한 데이터 세트 결과와 그렇지 않은 것에 갇혀 있습니다. 저는 ORM 기반 솔루션을 선호하며 아마도 명시 적으로 지정해야합니다. 이것은 본질적으로 Linq에서 얻는 모든 이점없이 Linq의 기능을 복제하는 것입니다.
Erik Funkenbusch 2013 년

나는 이러한 문제를 알고 있습니다. 이것은 유창한 인터페이스를 구성하는 방법을 보여주는 빠르고 더러운 솔루션입니다. 실제 솔루션에서는 기존 접근 방식을 필요에 맞게 조정 된 유창한 인터페이스로 "구울"것입니다.
Olivier Jacot-Descombes 2013
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.