自定义LinqToHqlGeneratorsRegistry-InvalidCastException:'无法强制转换"Antlr.Runtime.Tree.CommonTree"到"NHibernate.Hql.Ast.ANTLR.Tree.IASTNode"; [英] Custom LinqToHqlGeneratorsRegistry - InvalidCastException: 'Unable cast "Antlr.Runtime.Tree.CommonTree" to "NHibernate.Hql.Ast.ANTLR.Tree.IASTNode"

查看:62
本文介绍了自定义LinqToHqlGeneratorsRegistry-InvalidCastException:'无法强制转换"Antlr.Runtime.Tree.CommonTree"到"NHibernate.Hql.Ast.ANTLR.Tree.IASTNode";的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我为在模型中使用规范模式制作了自己的LinqToHqlGeneratorsRegistry实现.我可以将规范与对象和查询一起使用,而不必重复代码(请参见示例).您可以在此处看到所有代码.我的代码适用于除一种情况以外的所有情况.如果规范包含DateTime变量,则会收到InvalidCastException.

I make my own implementation of LinqToHqlGeneratorsRegistry for using specification pattern in my models. I can use specification with objects and query and do not repeat code (see sample). You can see all code here. My code work fine with all cases except one. I got InvalidCastException if specification contains DateTime variable.

    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

为什么带有任何其他类型变量的规范都能正常工作?

Why specification with any another type variables work fine?

推荐答案

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

The particular problem is not the DateTime type, but the DateTime.Today method.

一般的问题是HqlGenerator在NHibernate LINQ查询表达式处理管道中被调用为时已晚,因此原始表达式预处理的许多部分(如部分求值,参数化等)都丢失了.即使直接使用 x => ;,即使使用有效"查询也可以轻松看出差异.x.Sex =="Male" 在LINQ查询中,对SQL查询进行了参数化,而从 x =>x.IsMale 使用常量文字.

The general problem is that the HqlGenerators are called too late in the NHibernate LINQ query expression processing pipeline, so many parts of the original expression preprocessing like partial evaluation, parameterization etc. are missing there. The difference can easily be seen even with the "working" query - if you use directly x => x.Sex == "Male" inside the LINQ query, the SQL query is parameterized, while the translated SQL from x => x.IsMale uses constant literal.

您要实现的基本上是用一个内部表达式树替换另一个表达式,这正是 ExpressionVisitor 的目的.而您所需要的就是能够在查询提供程序之前 预处理查询表达式.

What you are trying to achieve is basically replacing one expression with another inside expression tree, which is exactly what ExpressionVisitors are for. And all you need is to be able to preprocess the query expression before the query provider.

奇怪的是,没有主要的LINQ查询提供程序(NHibernate,EF6,EF Core)提供这样做的方法.但是稍后会详细介绍.首先让我展示应用规范所需的方法(省略错误检查):

Strangely, none of the major LINQ query providers (NHibernate, EF6, EF Core) provide a way to do that. But more on this later. Let me first show the method needed to apply the specifications (error checking omitted):

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 方法并在调用基本实现之前对传递的表达式进行预处理.

Now the plumbing part. Actually NHibernate allows you to replace the LINQ provider with your own. In theory you should be able to create DefaultQueryProvider derived class, override the PrepareQuery method and preprocess the passed expression before calling the base implementation.

不幸的是,在 DefaultQueryProvider 类中, IQueryProviderWithOptions.WithOptions 方法的实现存在缺陷,这需要基于丑陋的反射的技巧.但是如果没有使用该查询提供程序,则如果查询使用的是某些 WithOptions 扩展方法,则查询提供程序将替换为默认提供程序,从而使我们的工作量全部减少.

Unfortunately there is an implementation flaw of IQueryProviderWithOptions.WithOptions method in the DefaultQueryProvider class which requires some ugly reflection based hacks. But without that the query provider will be replaced with the default if the query is using some of the WithOptions extension methods, thus negating all our efforts.

话虽如此,以下是提供者代码:

With that being said, here is the provider code:

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 ,因此请将其删除并替换

The classes LinqToHqlGeneratorsRegistry and SpecificationHqlGenerator are not needed anymore, so remove them and replace

cfg.LinqToHqlGeneratorsRegistry<LinqToHqlGeneratorsRegistry>();

使用

cfg.LinqQueryProvider<CustomQueryProvider>();

一切都会按预期进行.

这篇关于自定义LinqToHqlGeneratorsRegistry-InvalidCastException:'无法强制转换"Antlr.Runtime.Tree.CommonTree"到"NHibernate.Hql.Ast.ANTLR.Tree.IASTNode";的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆