C# 自定义LinqToHqlGeneratorsRegistry-InvalidCastException:';“无法铸造”;Antlr.Runtime.Tree.CommonTree“的;至;NHibernate.Hql.Ast.ANTLR.Tree.IASTNode“;

C# 自定义LinqToHqlGeneratorsRegistry-InvalidCastException:';“无法铸造”;Antlr.Runtime.Tree.CommonTree“的;至;NHibernate.Hql.Ast.ANTLR.Tree.IASTNode“;,c#,nhibernate,expression-trees,C#,Nhibernate,Expression Trees,我自己实现了LinqToHqlGeneratorsRegistry,以便在模型中使用规范模式。我可以将规范用于对象和查询,并且不重复代码(参见示例)。您可以看到所有代码。我的代码适用于除一个以外的所有情况。如果规范包含DateTime变量,则得到InvalidCastException 公共类客户端 { 公共静态只读规范IsMaleSpecification=新规范(x=>x.Sex==“男性”); 公共静态只读规范isadaltspecification=新规范(x=>x.birthisada

我自己实现了LinqToHqlGeneratorsRegistry,以便在模型中使用规范模式。我可以将规范用于对象和查询,并且不重复代码(参见示例)。您可以看到所有代码。我的代码适用于除一个以外的所有情况。如果规范包含DateTime变量,则得到InvalidCastException

公共类客户端
{
公共静态只读规范IsMaleSpecification=新规范(x=>x.Sex==“男性”);
公共静态只读规范isadaltspecification=新规范(x=>x.birthisadaltspecification.issatifiedby(this);
[规范(名称(IsMaleSpecification))]
公共虚拟bool IsMale=>ismalesspecification.issatifiedby(this);
}
...
var client=new client(){Sex=“Male”};
var isMale=client.isMale//真的
var malecont=session.Query().Count(x=>x.IsMale)//好啊
var adultCount=session.Query().Count(x=>x.IsAdult)//例外
...
例外情况

   в 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

为什么使用其他类型变量的规范可以正常工作?

具体的问题不是
DateTime
类型,而是
DateTime.Today
方法

一般的问题是,在NHibernate LINQ查询表达式处理管道中调用HQLGenerator太晚,因此缺少原始表达式预处理的许多部分,如部分求值、参数化等。即使使用“工作”查询,也可以很容易地看到差异-如果在LINQ查询中直接使用
x=>x.Sex==“Male”
,则SQL查询是参数化的,而从
x=>x.IsMale
转换的SQL使用常量文本

您试图实现的基本目标是在表达式树中用另一个表达式替换一个表达式,这正是
ExpressionVisitor
s的用途。您所需要的只是能够在查询提供程序之前预处理查询表达式

奇怪的是,没有一个主要的LINQ查询提供程序(NHibernate、EF6、EF-Core)提供这样做的方法。但稍后会有更多关于这方面的内容。首先让我展示应用规范所需的方法(省略错误检查):

现在是管道部分。实际上,NHibernate允许您用自己的替代LINQ提供程序。理论上,您应该能够创建
DefaultQueryProvider
派生类,重写
PrepareQuery
方法,并在调用基本实现之前预处理传递的表达式

不幸的是,
DefaultQueryProvider
类中的
IQueryProviderWithOptions.WithOptions
方法存在一个实现缺陷,这需要一些丑陋的基于反射的攻击。但是,如果查询使用了一些
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);
}

cfg.LinqQueryProvider();

一切都会按预期进行。

哇,这太棒了。几个月来我一直在寻找解决这个问题的办法!我正在我的应用程序中实现它,一旦它运行起来,我会很高兴地更新它。问:为什么表达式必须是规范,而不是任意表达式?例如,我需要计算所有类型的属性并从中生成SQL。此外,您是否重新访问了NH的后续版本,以查看您必须编写的笨拙设计是否得到了改进?@Michael(1)没有理由,只是这是一个具体的OP问题。(2) 不,从那以后我就再也没有看Nh了,最近我主要关注EF核心。它成功了!我必须编写一个稍微不同的属性来处理任意表达式(与规范相反),但它起作用了!谢谢。
在NHibernate 5.3中,您必须编写的笨拙设计是否得到了改进
,您可以简单地重写
CreateWithOptions
方法,而不是实现
IQueryProviderWithOptions。WithOptions
请参阅
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);
    }
}
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);
}
cfg.LinqToHqlGeneratorsRegistry<LinqToHqlGeneratorsRegistry>();
cfg.LinqQueryProvider<CustomQueryProvider>();