ardalis.specification 是一个功能强大的库,支持查询数据库的规范模式,主要是为 entity framework core 设计的,但在这里我将演示如何扩展 ardalis.specification 以将 nhibernate 用作 orm。
这篇博文假设您对 ardalis.specification 有一定的经验,并且希望在使用 nhibernate 的项目中使用它。如果您还不熟悉 ardalis.specification,请参阅文档以了解更多信息。
首先,nhibernate 中有三种不同的内置方法来执行查询
linq 查询(使用 iqueryable)
标准 api
查询结束
我将介绍如何扩展 ardalis.specification 来处理所有 3 种方式,但由于 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; } } </t></t></t></t></t></t></t></t></t></t></t></t>
接下来我们需要配置 ispecificationevaluator 以使用我们的 fetchevaluator(和其他评估器)。我们添加一个实现 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; } } </t></t></t></t></t></t></t></t></tresult></ievaluator></ievaluator>
现在我们可以在我们的存储库中创建对 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(); } } </t></t></t></tresult></t></t></t></t>
就是这样。现在,我们可以在规范中使用 linq to query,就像我们通常使用 ardalis 一样。规范:
public class trackbyname : specification<core.entitites.track> { public trackbyname(string trackname) { query.where(x => x.name == trackname); } } </core.entitites.track>
现在我们已经介绍了基于 linq 的查询,让我们继续处理 criteria api 和 query over,这需要不同的方法。
在 nhibernate 中混合 linq、criteria 和 query over
由于 criteria api 和 query over 有自己的实现来生成 sql,并且不使用 iqueryable,因此它们与 ievaluator 接口不兼容。我的解决方案是在这种情况下避免对这些方法使用 ievaluator 接口,而是关注规范模式的好处。但我也希望能够混搭
我的解决方案中包含 linq to query、criteria 和 query over(如果您只需要其中一种实现,您可以根据您的最佳需求进行挑选)。
为了能够做到这一点,我添加了四个继承specification或specification的新类
注意: 定义这些类的程序集需要对 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>>? _action; public action<iqueryover 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>> action) => _action = action; } public class queryoverspecification<t tresult> : specification<t tresult> { private func<iqueryover t>, iqueryover<t t>>? _action; public func<iqueryover 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>, iqueryover<t t>> action) => _action = action; } </t></iqueryover></t></iqueryover></t></iqueryover></t></t></iqueryover></iqueryover></iqueryover></t></t></icriteria></icriteria></icriteria></t></t></icriteria></icriteria></icriteria></t></t>
然后我们可以在存储库中使用模式匹配来更改使用 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() }; } </t></tresult></t></t></tresult></t></t></t></t></tresult></t></t></t></t></t></t></t></t></t></t>
上面的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); } } </t></t></t></t>
标准规范示例
注意: 定义这些类的程序集需要对 nhibernate 的引用,因为我们定义 criteria 的操作,可以在 nhibernate
中找到
public class trackbynamecriteria : criteriaspecification<track> { public trackbynamecriteria(string trackname) { this.usecriteria(criteria => criteria.add(restrictions.eq(nameof(track.name), trackname))); } } </track>
查询超规格示例
注意: 定义这些类的程序集需要对 nhibernate 的引用,因为我们定义 queryover 的操作,可以在 nhibernate
中找到
public class TrackByNameQueryOver : QueryOverSpecification<track> { public TrackByNameQueryOver(string trackName) { this.UseQueryOver(queryOver => queryOver.Where(x => x.Name == trackName)); } } </track>
通过扩展 nhibernate 的 ardalis.specification,我们解锁了在单个存储库模式中使用 linq to query、criteria api 和 query over 的能力。这种方法为 nhibernate 用户提供了高度适应性和强大的解决方案
以上就是使用 Linq、Criteria API 和 Query Over 扩展 NHibernate 的 ArdalisSpecification的详细内容,更多请关注本网内其它相关文章!