
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 getquery(iqueryable query, ispecification specification) where t : class
{
foreach (var includeinfo in specification.includeexpressions)
{
query = includeinfo.type switch
{
includetypeenum.include => buildinclude(query, includeinfo),
includetypeenum.theninclude => buildtheninclude(query, includeinfo),
_ => query
};
}
return query;
}
public bool iscriteriaevaluator { get; } = false;
private iqueryable buildinclude(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)result;
}
private iqueryable buildtheninclude(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)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;
}
}
接下来我们需要配置 ispecificationevaluator 以使用我们的 fetchevaluator(和其他评估器)。我们添加一个实现 ispecificationevaluator ,如下所示,并在构造函数中配置评估器。 whereevaluator、orderevaluator 和 paginationevaluator 都在 ardalis.specification 中,并且在 nhibernate 中也能很好地工作。
public class linqtoqueryspecificationevaluator : ispecificationevaluator
{
private list evaluators { get; } = new list();
public linqtoqueryspecificationevaluator()
{
evaluators.addrange(new ievaluator[]
{
whereevaluator.instance,
orderevaluator.instance,
paginationevaluator.instance,
fetchevaluator.instance
});
}
public iqueryable getquery(iqueryable query, ispecification 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)specification);
return specification.selector is not null
? query.select(specification.selector)
: query.selectmany(specification.selectormany!);
}
public iqueryable getquery(iqueryable query, ispecification 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 list(ispecification specification) where t : class
{
return _specificationevaluator.getquery(_session.query().asqueryable(), specification).tolist();
}
public ienumerable list(ispecification specification) where t : class
{
return _specificationevaluator.getquery(_session.query().asqueryable(), specification).tolist();
}
public void dispose()
{
_session.dispose();
}
}
就是这样。现在,我们可以在规范中使用 linq to query,就像我们通常使用 ardalis 一样。规范:
public class trackbyname : specification{ public trackbyname(string trackname) { query.where(x => x.name == trackname); } }
现在我们已经介绍了基于 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: specification { private action ? _action; public action getcriteria() => _action ?? throw new notsupportedexception("the criteria has not been specified. please use usecriteria() to define the criteria."); protected void usecriteria(action action) => _action = action; } public class criteriaspecification : specification { private action ? _action; public action getcriteria() => _action ?? throw new notsupportedexception("the criteria has not been specified. please use usecriteria() to define the criteria."); protected void usecriteria(action action) => _action = action; } public class queryoverspecification : specification { private action >? _action; public action > 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 > action) => _action = action; } public class queryoverspecification : specification { private func , iqueryover >? _action; public func , iqueryover > 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 > action) => _action = action; }
然后我们可以在存储库中使用模式匹配来更改使用 nhibernate 进行查询的方式
public ienumerablelist (ispecification specification) where t : class { return specification switch { criteriaspecification criteriaspecification => _session.createcriteria () .apply(query => criteriaspecification.getcriteria().invoke(query)) .list (), queryoverspecification queryoverspecification => _session.queryover () .apply(queryover => queryoverspecification.getqueryover().invoke(queryover)) .list (), _ => _specificationevaluator.getquery(_session.query ().asqueryable(), specification).tolist() }; } public ienumerable list (ispecification specification) where t : class { return specification switch { criteriaspecification criteriaspecification => _session.createcriteria () .apply(query => criteriaspecification.getcriteria().invoke(query)) .list (), queryoverspecification queryoverspecification => _session.queryover () .apply(queryover => queryoverspecification.getqueryover().invoke(queryover)) .list (), _ => _specificationevaluator.getquery(_session.query ().asqueryable(), specification).tolist() }; }
上面的apply()方法是一个扩展方法,它将查询简化为一行:
public static class queryextensions
{
public static t apply(this t obj, action action)
{
action(obj);
return obj;
}
public static tresult apply(this t obj, func func)
{
return func(obj);
}
}
标准规范示例
注意: 定义这些类的程序集需要对 nhibernate 的引用,因为我们定义 criteria 的操作,可以在 nhibernate
中找到
public class trackbynamecriteria : criteriaspecification
查询超规格示例
注意: 定义这些类的程序集需要对 nhibernate 的引用,因为我们定义 queryover 的操作,可以在 nhibernate
中找到
public class TrackByNameQueryOver : QueryOverSpecification
通过扩展 nhibernate 的 ardalis.specification,我们解锁了在单个存储库模式中使用 linq to query、criteria api 和 query over 的能力。这种方法为 nhibernate 用户提供了高度适应性和强大的解决方案










