我如何让 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 中的代码。
我用 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