我暗含了LinqToHqlGeneratorsRegistry在模型中使用规范模式的含义。我可以对对象和查询使用规范,而不必重复代码(请参见示例)。您可以看到所有代码here。我的代码适用于除一种情况以外的所有情况。如果规范包含DateTime变量,则会收到InvalidCastException。
public class Client
{
public static readonly Specification<Client> IsMaleSpecification = new Specification<Client>(x => x.Sex == "Male");
public static readonly Specification<Client> IsAdultSpecification = new Specification<Client>(x => x.Birthday < DateTime.Today);
[Specification(nameof(IsAdultSpecification))]
public virtual bool IsAdult => IsAdultSpecification.IsSatisfiedBy(this);
[Specification(nameof(IsMaleSpecification))]
public virtual bool IsMale => IsMaleSpecification.IsSatisfiedBy(this);
}
...
var client = new Client() {Sex = "Male"};
var isMale = client.IsMale; //true
var maleCount = session.Query<Client>().Count(x => x.IsMale); //ok
var adultCount = session.Query<Client>().Count(x => x.IsAdult);//exception
...
例外
в NHibernate.Hql.Ast.ANTLR.HqlSqlWalker.addrExprDot(Boolean root)
в NHibernate.Hql.Ast.ANTLR.HqlSqlWalker.addrExpr(Boolean root)
в NHibernate.Hql.Ast.ANTLR.HqlSqlWalker.expr()
в NHibernate.Hql.Ast.ANTLR.HqlSqlWalker.exprOrSubquery()
в NHibernate.Hql.Ast.ANTLR.HqlSqlWalker.comparisonExpr()
в NHibernate.Hql.Ast.ANTLR.HqlSqlWalker.logicalExpr()
в NHibernate.Hql.Ast.ANTLR.HqlSqlWalker.whereClause()
в NHibernate.Hql.Ast.ANTLR.HqlSqlWalker.unionedQuery()
в NHibernate.Hql.Ast.ANTLR.HqlSqlWalker.query()
в NHibernate.Hql.Ast.ANTLR.HqlSqlWalker.selectStatement()
в NHibernate.Hql.Ast.ANTLR.HqlSqlWalker.statement()
в NHibernate.Hql.Ast.ANTLR.HqlSqlTranslator.Translate()
в NHibernate.Hql.Ast.ANTLR.QueryTranslatorImpl.Analyze(String collectionRole)
в NHibernate.Hql.Ast.ANTLR.QueryTranslatorImpl.DoCompile(IDictionary`2 replacements, Boolean shallow, String collectionRole)
в NHibernate.Hql.Ast.ANTLR.ASTQueryTranslatorFactory.CreateQueryTranslators(IASTNode ast, String queryIdentifier, String collectionRole, Boolean shallow, IDictionary`2 filters, ISessionFactoryImplementor factory)
в NHibernate.Hql.Ast.ANTLR.ASTQueryTranslatorFactory.CreateQueryTranslators(IQueryExpression queryExpression, String collectionRole, Boolean shallow, IDictionary`2 filters, ISessionFactoryImplementor factory)
в NHibernate.Engine.Query.QueryPlanCache.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow, IDictionary`2 enabledFilters)
в NHibernate.Impl.AbstractSessionImpl.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow)
в NHibernate.Impl.AbstractSessionImpl.CreateQuery(IQueryExpression queryExpression)
в NHibernate.Linq.DefaultQueryProvider.PrepareQuery(Expression expression, IQuery& query)
в NHibernate.Linq.DefaultQueryProvider.Execute(Expression expression)
в NHibernate.Linq.DefaultQueryProvider.Execute[TResult](Expression expression)
в System.Linq.Queryable.Count[TSource](IQueryable`1 source, Expression`1 predicate)
в ConsoleApp1.Program.Main(String[] args) в C:\git\TestApp\ConsoleApp1\Program.cs:строка 32
为什么带有任何其他类型变量的规范都能正常工作?
答案 0 :(得分:2)
特殊的问题不是DateTime
类型,而是DateTime.Today
方法。
一般的问题是HqlGenerators在NHibernate LINQ查询表达式处理管道中被调用为时已晚,因此原始表达式预处理的许多部分(如部分求值,参数化等)都丢失了。即使使用“工作”查询,也可以轻松看出差异-如果直接在LINQ查询中使用x => x.Sex == "Male"
,则将对SQL查询进行参数化,而从x => x.IsMale
转换过来的SQL将使用常量文字。>
您想要实现的基本上是用另一个内部表达式树替换一个表达式,这正是ExpressionVisitor
的目的。而且,您所需要的就是能够在查询提供程序之前 预处理查询表达式。
奇怪的是,没有主要的LINQ查询提供程序(NHibernate,EF6,EF Core)提供这样做的方法。但是稍后会详细介绍。首先让我展示应用规范所需的方法(省略错误检查):
public static class SpecificationExtensions
{
public static Expression ApplySpecifications(this Expression source) =>
new SpecificationsProcessor().Visit(source);
class SpecificationsProcessor : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression != null && node.Member is PropertyInfo property)
{
var info = property.GetCustomAttribute<SpecificationAttribute>();
if (info != null)
{
var type = property.DeclaringType;
var specificationMemberInfo = type.GetFields(BindingFlags.Static | BindingFlags.Public)
.Single(x => x.Name == info.FieldName);
var specification = (BaseSpecification)specificationMemberInfo.GetValue(null);
var specificationExpression = (LambdaExpression)specification.ToExpression();
var expression = specificationExpression.Body.ReplaceParameter(
specificationExpression.Parameters.Single(), Visit(node.Expression));
return Visit(expression);
}
}
return base.VisitMember(node);
}
}
}
使用以下帮助程序:
public static partial class ExpressionExtensions
{
public static Expression ReplaceParameter(this Expression source, ParameterExpression from, Expression to)
=> new ParameterReplacer { From = from, To = to }.Visit(source);
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression From;
public Expression To;
protected override Expression VisitParameter(ParameterExpression node) => node == From ? To : base.VisitParameter(node);
}
}
现在是管道部分。实际上,NHibernate允许您用自己的LINQ提供程序替换。从理论上讲,您应该能够创建DefaultQueryProvider
派生类,重写PrepareQuery
方法并在调用基本实现之前对传递的表达式进行预处理。
不幸的是,IQueryProviderWithOptions.WithOptions
类中的DefaultQueryProvider
方法存在一个实现缺陷,需要一些基于丑陋反射的技巧。但是如果查询使用的是WithOptions
扩展方法中的某些扩展方法,则查询提供程序将被默认值替换,从而抵消了我们的所有努力。
话虽如此,以下是提供者代码:
public class CustomQueryProvider : DefaultQueryProvider, IQueryProviderWithOptions
{
// Required constructors
public CustomQueryProvider(ISessionImplementor session) : base(session) { }
public CustomQueryProvider(ISessionImplementor session, object collection) : base(session, collection) { }
// The code we need
protected override NhLinqExpression PrepareQuery(Expression expression, out IQuery query)
=> base.PrepareQuery(expression.ApplySpecifications(), out query);
// Hacks for correctly supporting IQueryProviderWithOptions
IQueryProvider IQueryProviderWithOptions.WithOptions(Action<NhQueryableOptions> setOptions)
{
if (setOptions == null)
throw new ArgumentNullException(nameof(setOptions));
var options = (NhQueryableOptions)_options.GetValue(this);
var newOptions = options != null ? (NhQueryableOptions)CloneOptions.Invoke(options, null) : new NhQueryableOptions();
setOptions(newOptions);
var clone = (CustomQueryProvider)this.MemberwiseClone();
_options.SetValue(clone, newOptions);
return clone;
}
static readonly FieldInfo _options = typeof(DefaultQueryProvider).GetField("_options", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly MethodInfo CloneOptions = typeof(NhQueryableOptions).GetMethod("Clone", BindingFlags.NonPublic | BindingFlags.Instance);
}
不再需要类LinqToHqlGeneratorsRegistry
和SpecificationHqlGenerator
,因此请删除并替换
cfg.LinqToHqlGeneratorsRegistry<LinqToHqlGeneratorsRegistry>();
使用
cfg.LinqQueryProvider<CustomQueryProvider>();
一切都会按预期进行。