ホームページ  >  記事  >  バックエンド開発  >  Linq、Criteria API、Query Over を使用した NHibernate の Ardalis.仕様の拡張

Linq、Criteria API、Query Over を使用した NHibernate の Ardalis.仕様の拡張

DDD
DDDオリジナル
2024-09-18 11:52:24298ブラウズ

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

Ardalis.Specific は、主に Entity Framework Core 用に設計された、データベースのクエリの仕様パターンを有効にする強力なライブラリですが、ここでは、Ardalis.Specific を拡張して使用できるようにする方法を示します。 NHibernate も ORM として機能します。

このブログ投稿は、Ardalis.仕様についてある程度の経験があり、NHibernate を使用するプロジェクトでそれを使用したいと考えていることを前提としています。 Ardalis.仕様にまだ慣れていない場合は、ドキュメントにアクセスして詳細を確認してください。

まず、Hibernate にはクエリを実行するための 3 つの異なる組み込み方法があります

  • クエリへの Linq (IQueryable を使用)
  • 基準 API
  • クエリオーバー

3 つの方法すべてを処理できるように Ardalis.Spec を拡張する方法を説明しますが、Linq to Query は Entity Framework Core と同様に IQueryable でも動作するため、最初にそのオプションを説明します。

クエリへのリンク

結合関係の作成に関しては、Entity Framework Core と NHIbernate の間には若干の違いがあります。 Entity Framework Core には、IQueryable の拡張メソッドである Include と thenInclude があります (これらは、Ardalis.仕様で使用されるメソッド名でもあります)。

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 (および他のエバリュエーター) を使用するように ISpecificEvaluator を構成する必要があります。次のように、コンストラクターで構成されたエバリュエーターを使用して、実装 ISpecifyEvaluator を追加します。 WhereEvaluator、OrderEvaluator、および PaginationEvaluator はすべて Ardalis.仕様に含まれており、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;
    }
}

これで、次のような LinqToQuerySpecEvaluator への参照をリポジトリに作成できます。

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.仕様:
を使用するのと同じように、仕様で Linq to Query を使用できるようになりました。

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 API と Query Over には SQL を生成するための独自の実装があり、IQueryable を使用しないため、IEvaluator インターフェイスと互換性がありません。私の解決策は、この場合、これらのメソッドに IEvaluator インターフェイスを使用することを避け、仕様パターンの利点に焦点を当てることです。でも、ミックスもできるようになりたいです
私のソリューションでは、Linq to Query、Criteria、および Query Over を使用しています (これらの実装の 1 つだけが必要な場合は、最適なニーズに合わせて選択できます)。

これを行うには、仕様または仕様を継承する 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() メソッドは、クエリを 1 行に単純化する拡張メソッドです。

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 への参照が必要です。これは 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.仕様を拡張することで、Linq to Query、Criteria API、Query Over をすべて単一のリポジトリ パターン内で使用できるようになります。このアプローチは、NHibernate ユーザーに適応性の高い強力なソリューションを提供します

以上がLinq、Criteria API、Query Over を使用した NHibernate の Ardalis.仕様の拡張の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。