我如何让 NHibernate 为计算的 属性 生成 SQL?

How do I get NHibernate to generate SQL for a computed property?

我用 Google 搜索并提供了示例代码,但这给我带来了麻烦。根据我的发现,这是我得到的:

在坚持class我有

public static readonly Expression<Func<Detail, decimal>> TotExpression = d =>
    (decimal)((d.Fee == null ? 0 : d.Fee) + (d.Expenses == null ? 0 : d.Expenses));

public static Func<Detail, decimal> CompiledTot => TotExpression.Compile();
public virtual decimal Tot => CompiledTot(this);

我使用

注册属性
class ComputedPropertyGeneratorRegistry : DefaultLinqToHqlGeneratorsRegistry
{
    public ComputedPropertyGeneratorRegistry()
    {
        CalculatedPropertyGenerator<Detail, decimal>.Register(
            this,
            x => x.Tot,
            Detail.TotExpression);
    }
}

public class CalculatedPropertyGenerator<T, TResult> : BaseHqlGeneratorForProperty
{
    public static void Register(ILinqToHqlGeneratorsRegistry registry, Expression<Func<T, TResult>> property, Expression<Func<T, TResult>> calculationExp)
    {
        registry.RegisterGenerator(ReflectHelper.GetProperty(property), new CalculatedPropertyGenerator<T, TResult> { _calculationExp = calculationExp });
    }
    private CalculatedPropertyGenerator() { } // Private constructor

    private Expression<Func<T, TResult>> _calculationExp;
    public override HqlTreeNode BuildHql(MemberInfo member, Expression expression, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
    {
        return visitor.Visit(_calculationExp);
    }
}

在我的会话工厂配置中我有

cfg.LinqToHqlGeneratorsRegistry<ComputedPropertyGeneratorRegistry>();

然而当我运行

session.Query<Detail>().Select(x => x.Tot).First();

我明白了

NHibernate.Hql.Ast.ANTLR.InvalidPathException: Invalid path: 'd.Fee'

似乎当 NH 尝试生成 SQL 时,它会在某个时刻调用 d.Fee 上的 LiteralProcessor.LookupConstant,后者会调用 ReflectHelper.GetConstantValue("d.Fee"),出于某种原因假定“d”是 属性 所属的 class 的名称。当然不是,这打破了一切。我不知道为什么它会走这条错误的路。

好的,问题似乎是 HQL 生成器 returns 一个表达式,其中 'd' 参数未被视为参数,因此生成的 HQL 不知道该怎么做用它。如果我将查询中的 'x' 参数更改为 'd',如

session.Query<Detail>().Select(d => d.Tot).First();

这一切都挂在一起。这显然是一个麻烦,但还不足以胜过能够搜索和 select 计算属性。我假设更了解 HQL“访问者”的人能够在 HQL 生成器中进行适当的调整,但我会把它留给其他志愿者。

更新:我不能就此罢手,所以我在 Phil Klein 的一些代码的帮助下拼凑了一种方法来做到这一点。 =18=]

Phil 提供了这个 class

public class PredicateRewriter
{
    public static Expression<Func<T, TResult>> Rewrite<T, TResult>(Expression<Func<T, TResult>> exp, string newParamName)
    {
        var param = Expression.Parameter(exp.Parameters[0].Type, newParamName);
        var newExpression = new PredicateRewriterVisitor(param).Visit(exp);

        return (Expression<Func<T, TResult>>)newExpression;
    }

    private class PredicateRewriterVisitor : ExpressionVisitor
    {
        private readonly ParameterExpression _parameterExpression;

        public PredicateRewriterVisitor(ParameterExpression parameterExpression)
        {
            _parameterExpression = parameterExpression;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            return _parameterExpression;
        }
    }
}

我在这里使用

    public override HqlTreeNode BuildHql(MemberInfo member, Expression expression, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
    {
        // this is a kludge because I don't know how to pry the name out of the parameter expression
        var inside = new Regex("\[(.*)\]");
        var name = inside.Match(expression.ToString()).Groups[1].Value;
        return visitor.Visit(PredicateRewriter.Rewrite<T, TResult>(_calculationExp, name));
    }

如果表达式有多个参数,它就不会工作,但这种情况很少发生,我真的不希望以完善它为职业:)。

更新:我有一个解决方案。而不是 subclassing DefaultLinqToHqlGeneratorsRegistry,这似乎在将 LINQ 转换为 SQL 的链中太远了,我 subclassed DefaultQueryProvider, IQueryProviderWithOptions 并插入了它cfg.LinqQueryProvider<CustomQueryProvider>().

这是基于 Ivan Stoev that I found 中的代码。