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