Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Memperluas Ardalis.Spesifikasi untuk NHibernate dengan Linq, API Kriteria dan Pertanyaan Selesai

Memperluas Ardalis.Spesifikasi untuk NHibernate dengan Linq, API Kriteria dan Pertanyaan Selesai

DDD
DDDasal
2024-09-18 11:52:24298semak imbas

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

Ardalis.Specification ialah perpustakaan berkuasa yang membolehkan corak spesifikasi untuk pangkalan data pertanyaan, yang direka terutamanya untuk Teras Rangka Kerja Entiti, tetapi di sini saya akan menunjukkan cara anda boleh melanjutkan Ardalis.Specification untuk digunakan NHibernate sebagai ORM juga.

Siaran blog ini menganggap anda mempunyai sedikit pengalaman dengan Ardalis.Specification, dan ingin menggunakannya dalam projek menggunakan NHibernate. Jika anda belum biasa dengan Ardalis.Spesifikasi, pergi ke dokumentasi untuk mengetahui lebih lanjut.

Pertama, dalam NHibernate terdapat tiga cara terbina dalam yang berbeza untuk melakukan pertanyaan

  • Linq untuk bertanya (menggunakan IQueryable)
  • API Kriteria
  • Pertanyaan Selesai

Saya akan menerangkan cara anda boleh melanjutkan Ardalis.Spesifikasi untuk mengendalikan kesemua 3 cara, tetapi memandangkan Linq kepada Query juga berfungsi dengan IQueryable seperti Teras Rangka Kerja Entiti, saya akan melalui pilihan itu dahulu.

Linq untuk bertanya

Terdapat sedikit nuansa antara Teras Rangka Kerja Entiti dan NHIbernate apabila ia datang untuk mewujudkan perhubungan gabungan. Dalam Teras Rangka Kerja Entiti kami mempunyai kaedah sambungan pada IQueryable: Include dan ThenInclude (ini juga merupakan nama kaedah yang digunakan dalam Ardalis.Specification).

Fetch, FetchMany, ThenFetch dan ThenFetchMany ialah kaedah khusus NHibernate pada IQueryable yang boleh bergabung. IEvaluator memberi kami kebolehlanjutan yang kami perlukan untuk menggunakan kaedah sambungan yang betul apabila kami bekerja dengan NHibernate.

Tambahkan pelaksanaan IEvaluator seperti berikut:

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

Seterusnya kami perlu mengkonfigurasi ISpecificationEvaluator untuk menggunakan FetchEvaluator kami (dan penilai lain). Kami menambah ISpecificationEvaluator pelaksanaan seperti berikut dengan Penilai yang dikonfigurasikan dalam pembina. Di manaEvaluator, OrderEvaluator dan PenomboranEvaluator semuanya ada dalam Ardalis.Spesifikasi dan berfungsi dengan baik NHibernate juga.

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

Kini kami boleh membuat rujukan kepada LinqToQuerySpecificationEvaluator dalam repositori kami yang mungkin kelihatan seperti ini:

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

Itu sahaja. Kami kini boleh menggunakan Linq untuk Pertanyaan dalam spesifikasi kami seperti yang biasa kami lakukan dengan Ardalis. Spesifikasi:

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

Sekarang kita telah membincangkan pertanyaan berasaskan Linq, mari kita teruskan untuk mengendalikan API Kriteria dan Pertanyaan Selesai, yang memerlukan pendekatan berbeza.

Mencampurkan Linq, Kriteria dan Pertanyaan Dalam NHibernate

Memandangkan Criteria API dan Query Over mempunyai pelaksanaannya sendiri untuk menjana SQL, dan tidak menggunakan IQueryable, ia tidak serasi dengan antara muka IEvaluator. Penyelesaian saya adalah untuk mengelak daripada menggunakan antara muka IEvaluator untuk kaedah ini dalam kes ini, tetapi lebih fokus pada faedah corak spesifikasi. Tapi nak jugak boleh campur
Linq kepada Pertanyaan, Kriteria dan Pertanyaan Selesaikan dengan dalam penyelesaian saya (jika anda hanya memerlukan salah satu pelaksanaan ini, anda boleh memilih untuk keperluan terbaik anda).

Untuk dapat melakukannya, saya menambah empat kelas baharu yang mewarisi Spesifikasi atau Spesifikasi

NOTA: Himpunan tempat anda mentakrifkan kelas ini memerlukan rujukan kepada NHibernate semasa kami mentakrifkan tindakan untuk Kriteria dan QueryOver, yang boleh didapati dalam 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;
}

Kemudian kami boleh menggunakan padanan corak dalam repositori kami untuk menukar cara kami melakukan pertanyaan dengan 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()
    };
}

Kaedah Apply() di atas ialah kaedah sambungan yang memudahkan pertanyaan kepada satu baris:

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

Contoh spesifikasi kriteria

NOTA: Himpunan tempat anda mentakrifkan kelas ini memerlukan rujukan kepada NHibernate semasa kami mentakrifkan tindakan untuk Kriteria, yang boleh didapati dalam NHibernate

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

Contoh soal spesifikasi

NOTA: Himpunan tempat anda mentakrifkan kelas ini memerlukan rujukan kepada NHibernate semasa kami mentakrifkan tindakan untuk QueryOver, yang boleh didapati dalam NHibernate

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

Dengan melanjutkan Ardalis.Specification untuk NHibernate, kami membuka kunci keupayaan untuk menggunakan Linq kepada Query, Criteria API dan Query Over—semuanya dalam satu corak repositori. Pendekatan ini menawarkan penyelesaian yang sangat mudah disesuaikan dan berkuasa untuk pengguna NHibernate

Atas ialah kandungan terperinci Memperluas Ardalis.Spesifikasi untuk NHibernate dengan Linq, API Kriteria dan Pertanyaan Selesai. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn