Ardalis.Specification은 주로 Entity Framework Core용으로 설계된 데이터베이스 쿼리용 사양 패턴을 지원하는 강력한 라이브러리이지만 여기서는 Ardalis.Specification을 확장하여 사용하는 방법을 보여 드리겠습니다. NHibernate는 ORM으로도 사용됩니다.
이 블로그 게시물은 귀하가 Ardalis.Specification에 대한 경험이 있고 NHibernate를 사용하는 프로젝트에서 이를 사용하고 싶다고 가정합니다. 아직 Ardalis.Specification에 익숙하지 않다면 설명서를 참조하여 자세히 알아보세요.
첫째, NHibernate에는 쿼리를 수행하는 세 가지 기본 제공 방법이 있습니다
Ardalis.Specification을 확장하여 세 가지 방법을 모두 처리할 수 있는 방법을 살펴보겠습니다. 하지만 Linq to Query는 Entity Framework Core처럼 IQueryable에서도 작동하므로 해당 옵션을 먼저 살펴보겠습니다.
조인 관계를 생성할 때 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를 처리하는 방법을 살펴보겠습니다.
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!