>  기사  >  백엔드 개발  >  Linq, Criteria API 및 쿼리 오버를 사용하여 NHibernate에 대한 Ardalis.Specification 확장

Linq, Criteria API 및 쿼리 오버를 사용하여 NHibernate에 대한 Ardalis.Specification 확장

DDD
DDD원래의
2024-09-18 11:52:24302검색

Extending Ardalis.Specification for NHibernate with Linq, Criteria API, and Query Over

Ardalis.Specification은 주로 Entity Framework Core용으로 설계된 데이터베이스 쿼리용 사양 패턴을 지원하는 강력한 라이브러리이지만 여기서는 Ardalis.Specification을 확장하여 사용하는 방법을 보여 드리겠습니다. NHibernate는 ORM으로도 사용됩니다.

이 블로그 게시물은 귀하가 Ardalis.Specification에 대한 경험이 있고 NHibernate를 사용하는 프로젝트에서 이를 사용하고 싶다고 가정합니다. 아직 Ardalis.Specification에 익숙하지 않다면 설명서를 참조하여 자세히 알아보세요.

첫째, NHibernate에는 쿼리를 수행하는 세 가지 기본 제공 방법이 있습니다

  • Linq 쿼리(IQueryable 사용)
  • 기준 API
  • 쿼리오버

Ardalis.Specification을 확장하여 세 가지 방법을 모두 처리할 수 있는 방법을 살펴보겠습니다. 하지만 Linq to Query는 Entity Framework Core처럼 IQueryable에서도 작동하므로 해당 옵션을 먼저 살펴보겠습니다.

쿼리할 Linq

조인 관계를 생성할 때 Entity Framework Core와 NHIbernate 사이에는 약간의 차이가 있습니다. Entity Framework Core에는 IQueryable에 대한 확장 메서드인 include 및 ThenInclude(Ardalis.Specification에서 사용되는 메서드 이름이기도 함)가 있습니다.

Fetch, FetchMany, ThenFetch 및 ThenFetchMany는 조인을 수행하는 IQueryable의 NHibernate 특정 메서드입니다. IEvaluator는 NHibernate로 작업할 때 올바른 확장 메서드를 호출하는 데 필요한 확장성을 제공합니다.

다음과 같이 IEvaluator 구현을 추가합니다.

public class FetchEvaluator : IEvaluator
{
   private static readonly MethodInfo FetchMethodInfo = typeof(EagerFetchingExtensionMethods)
        .GetTypeInfo().GetDeclaredMethods(nameof(EagerFetchingExtensionMethods.Fetch))
        .Single();

   private static readonly MethodInfo FetchManyMethodInfo = typeof(EagerFetchingExtensionMethods)
       .GetTypeInfo().GetDeclaredMethods(nameof(EagerFetchingExtensionMethods.FetchMany))
       .Single();

   private static readonly MethodInfo ThenFetchMethodInfo
       = typeof(EagerFetchingExtensionMethods)
           .GetTypeInfo().GetDeclaredMethods(nameof(EagerFetchingExtensionMethods.ThenFetch))
           .Single();

   private static readonly MethodInfo ThenFetchManyMethodInfo
       = typeof(EagerFetchingExtensionMethods)
           .GetTypeInfo().GetDeclaredMethods(nameof(EagerFetchingExtensionMethods.ThenFetchMany))
           .Single();

    public static FetchEvaluator Instance { get; } = new FetchEvaluator();

    public IQueryable<T> GetQuery<T>(IQueryable<T> query, ISpecification<T> specification) where T : class
    {
        foreach (var includeInfo in specification.IncludeExpressions)
        {
            query = includeInfo.Type switch
            {
                IncludeTypeEnum.Include => BuildInclude<T>(query, includeInfo),
                IncludeTypeEnum.ThenInclude => BuildThenInclude<T>(query, includeInfo),
                _ => query
            };
        }

        return query;
    }

    public bool IsCriteriaEvaluator { get; } = false;

    private IQueryable<T> BuildInclude<T>(IQueryable query, IncludeExpressionInfo includeInfo)
    {
        _ = includeInfo ?? throw new ArgumentNullException(nameof(includeInfo));

        var methodInfo = (IsGenericEnumerable(includeInfo.PropertyType, out var propertyType)
            ? FetchManyMethodInfo 
            : FetchMethodInfo);

       var method = methodInfo.MakeGenericMethod(includeInfo.EntityType, propertyType);

       var result = method.Invoke(null, new object[] { query, includeInfo.LambdaExpression });
        _ = result ?? throw new TargetException();

        return (IQueryable<T>)result;
    }

    private IQueryable<T> BuildThenInclude<T>(IQueryable query, IncludeExpressionInfo includeInfo)
    {
        _ = includeInfo ?? throw new ArgumentNullException(nameof(includeInfo));
        _ = includeInfo.PreviousPropertyType ?? throw new ArgumentNullException(nameof(includeInfo.PreviousPropertyType));

        var method = (IsGenericEnumerable(includeInfo.PreviousPropertyType, out var previousPropertyType)
            ? ThenFetchManyMethodInfo
            : ThenFetchMethodInfo);

        IsGenericEnumerable(includeInfo.PropertyType, out var propertyType);

        var result = method.MakeGenericMethod(includeInfo.EntityType, previousPropertyType, propertyType)
            .Invoke(null, new object[] { query, includeInfo.LambdaExpression });

        _ = result ?? throw new TargetException();

        return (IQueryable<T>)result;
    }

    private static bool IsGenericEnumerable(Type type, out Type propertyType)
    {
        if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
        {
            propertyType = type.GenericTypeArguments[0];

            return true;
        }

        propertyType = type;

        return false;
    }
}

다음으로 FetchEvaluator(및 기타 평가기)를 사용하도록 ISpecificationEvaluator를 구성해야 합니다. 생성자에 구성된 평가자를 사용하여 다음과 같이 구현 ISpecificationEvaluator를 추가합니다. WhereEvaluator, OrderEvaluator 및 PaginationEvaluator는 모두 Ardalis.Specification에 있으며 NHibernate에서도 잘 작동합니다.

public class LinqToQuerySpecificationEvaluator : ISpecificationEvaluator
{
    private List<IEvaluator> Evaluators { get; } = new List<IEvaluator>();

    public LinqToQuerySpecificationEvaluator()
    {
        Evaluators.AddRange(new IEvaluator[]
        {
            WhereEvaluator.Instance,
            OrderEvaluator.Instance,
            PaginationEvaluator.Instance,
            FetchEvaluator.Instance
        });
    }


    public IQueryable<TResult> GetQuery<T, TResult>(IQueryable<T> query, ISpecification<T, TResult> specification) where T : class
    {
        if (specification is null) throw new ArgumentNullException(nameof(specification));
        if (specification.Selector is null && specification.SelectorMany is null) throw new SelectorNotFoundException();
        if (specification.Selector is not null && specification.SelectorMany is not null) throw new ConcurrentSelectorsException();

        query = GetQuery(query, (ISpecification<T>)specification);

        return specification.Selector is not null
            ? query.Select(specification.Selector)
            : query.SelectMany(specification.SelectorMany!);
    }

    public IQueryable<T> GetQuery<T>(IQueryable<T> query, ISpecification<T> specification, bool evaluateCriteriaOnly = false) where T : class
    {
        if (specification is null) throw new ArgumentNullException(nameof(specification));

        var evaluators = evaluateCriteriaOnly ? Evaluators.Where(x => x.IsCriteriaEvaluator) : Evaluators;

        foreach (var evaluator in evaluators)
           query = evaluator.GetQuery(query, specification);

        return query;
    }
}

이제 저장소에서 다음과 같은 LinqToQuerySpecificationEvaluator에 대한 참조를 생성할 수 있습니다.

public class Repository : IRepository
{
    private readonly ISession _session;
    private readonly ISpecificationEvaluator _specificationEvaluator;

    public Repository(ISession session)
    {
        _session = session;
        _specificationEvaluator = new LinqToQuerySpecificationEvaluator();
    } 

    ... other repository methods

    public IEnumerable<T> List<T>(ISpecification<T> specification) where T : class
    {
        return _specificationEvaluator.GetQuery(_session.Query<T>().AsQueryable(), specification).ToList();
    }

    public IEnumerable<TResult> List<T, TResult>(ISpecification<T, TResult> specification) where T : class
    {    
        return _specificationEvaluator.GetQuery(_session.Query<T>().AsQueryable(), specification).ToList();
    }

    public void Dispose()
    {
        _session.Dispose();
    }
}

그렇습니다. 이제 Ardalis.Specification에서 일반적으로 수행하는 것처럼 Linq를 사용하여 사양에서 쿼리할 수 있습니다.

public class TrackByName : Specification<Core.Entitites.Track>
{
    public TrackByName(string trackName)
    {
        Query.Where(x => x.Name == trackName);
    }
}

이제 Linq 기반 쿼리를 다루었으니 이제 다른 접근 방식이 필요한 Criteria API 및 Query Over를 처리하는 방법을 살펴보겠습니다.

NHibernate에서 Linq, Criteria 및 Query Over 혼합

Criteria API와 Query Over에는 SQL을 생성하는 자체 구현이 있고 IQueryable을 사용하지 않으므로 IEvaluator 인터페이스와 호환되지 않습니다. 내 해결책은 이 경우 이러한 메서드에 IEvaluator 인터페이스를 사용하지 않고 사양 패턴의 이점에 초점을 맞추는 것입니다. 하지만 나도 믹싱을 하고 싶다
내 솔루션에 Linq to Query, Criteria 및 Query Over가 포함되어 있습니다(이러한 구현 중 하나만 필요한 경우 가장 적합한 것을 선별하여 선택할 수 있습니다).

이를 수행하기 위해 사양 또는 사양을 상속하는 4개의 새로운 클래스를 추가합니다

참고: NHibernate에서 찾을 수 있는 Criteria 및 QueryOver에 대한 작업을 정의할 때 이러한 클래스를 정의하는 어셈블리에는 NHibernate에 대한 참조가 필요합니다.

public class CriteriaSpecification<T> : Specification<T>
{
    private Action<ICriteria>? _action;
    public Action<ICriteria> GetCriteria() => _action ?? throw new NotSupportedException("The criteria has not been specified. Please use UseCriteria() to define the criteria.");
    protected void UseCriteria(Action<ICriteria> action) => _action = action;
}

public class CriteriaSpecification<T, TResult> : Specification<T, TResult>
{
    private Action<ICriteria>? _action;
    public Action<ICriteria> GetCriteria() => _action ?? throw new NotSupportedException("The criteria has not been specified. Please use UseCriteria() to define the criteria.");
    protected void UseCriteria(Action<ICriteria> action) => _action = action;
}

public class QueryOverSpecification<T> : Specification<T>
{
    private Action<IQueryOver<T, T>>? _action;
    public Action<IQueryOver<T, T>> GetQueryOver() => _action ?? throw new NotSupportedException("The Query over has not been specified. Please use the UseQueryOver() to define the query over.");
    protected void UseQueryOver(Action<IQueryOver<T, T>> action) => _action = action;
}

public class QueryOverSpecification<T, TResult> : Specification<T, TResult>
{
    private Func<IQueryOver<T, T>, IQueryOver<T, T>>? _action;
    public Func<IQueryOver<T, T>, IQueryOver<T, T>> GetQueryOver() => _action ??  throw new NotSupportedException("The Query over has not been specified. Please use the UseQueryOver() to define the query over.");
    protected void UseQueryOver(Func<IQueryOver<T, T>, IQueryOver<T, T>> action) => _action = action;
}

그런 다음 저장소의 패턴 일치를 사용하여 NHibernate로 쿼리를 수행하는 방식을 변경할 수 있습니다

public IEnumerable<T> List<T>(ISpecification<T> specification) where T : class
{
    return specification switch
    {
        CriteriaSpecification<T> criteriaSpecification => 
            _session.CreateCriteria<T>()
                .Apply(query => criteriaSpecification.GetCriteria().Invoke(query))
                .List<T>(),

        QueryOverSpecification<T> queryOverSpecification => 
            _session.QueryOver<T>()
                .Apply(queryOver => queryOverSpecification.GetQueryOver().Invoke(queryOver))
                .List<T>(),

        _ => _specificationEvaluator.GetQuery(_session.Query<T>().AsQueryable(), specification).ToList()
    };
}

public IEnumerable<TResult> List<T, TResult>(ISpecification<T, TResult> specification) where T : class
{

    return specification switch
    {
        CriteriaSpecification<T, TResult> criteriaSpecification => 
            _session.CreateCriteria<T>()
                .Apply(query => criteriaSpecification.GetCriteria().Invoke(query))
                .List<TResult>(),

        QueryOverSpecification<T, TResult> queryOverSpecification =>
            _session.QueryOver<T>()
                .Apply(queryOver => queryOverSpecification.GetQueryOver().Invoke(queryOver))
                .List<TResult>(),

        _ => _specificationEvaluator.GetQuery(_session.Query<T>().AsQueryable(), specification).ToList()
    };
}

위의 Apply() 메서드는 쿼리를 한 줄로 단순화하는 확장 메서드입니다.

public static class QueryExtensions
{
    public static T Apply<T>(this T obj, Action<T> action)
    {
        action(obj);
        return obj;
    }

    public static TResult Apply<T, TResult>(this T obj, Func<T, TResult> func)
    {
        return func(obj);
    }
}

기준 사양 예시

참고: 이러한 클래스를 정의하는 어셈블리에는 NHibernate에서 찾을 수 있는 Criteria에 대한 작업을 정의할 때 NHibernate에 대한 참조가 필요합니다.

public class TrackByNameCriteria : CriteriaSpecification<Track>
{
    public TrackByNameCriteria(string trackName)
    {
        this.UseCriteria(criteria => criteria.Add(Restrictions.Eq(nameof(Track.Name), trackName)));
    }
}

사양 예에 대한 쿼리

참고: QueryOver에 대한 작업을 정의할 때 이러한 클래스를 정의하는 어셈블리에는 NHibernate에 대한 참조가 필요하며 이는 NHibernate에서 찾을 수 있습니다

public class TrackByNameQueryOver : QueryOverSpecification<Track>
{
    public TrackByNameQueryOver(string trackName)
    {
        this.UseQueryOver(queryOver => queryOver.Where(x => x.Name == trackName));
    }
}

NHibernate에 대한 Ardalis.Specification을 확장함으로써 단일 저장소 패턴 내에서 Linq를 사용하여 쿼리, 기준 API 및 쿼리 오버를 수행할 수 있는 기능을 잠금 해제합니다. 이 접근 방식은 NHibernate 사용자에게 적응력이 뛰어나고 강력한 솔루션을 제공합니다

위 내용은 Linq, Criteria API 및 쿼리 오버를 사용하여 NHibernate에 대한 Ardalis.Specification 확장의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.