I've Googled around and come up with sample code, but it's giving me trouble. Here's what I've got, based on what I found:
In the persistent class I have
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);
I register the property using
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);
}
}
And in my session-factory configuration I have
cfg.LinqToHqlGeneratorsRegistry<ComputedPropertyGeneratorRegistry>();
Yet when I run
session.Query<Detail>().Select(x => x.Tot).First();
I get
NHibernate.Hql.Ast.ANTLR.InvalidPathException: Invalid path: 'd.Fee'
It seems that when NH tries to generate the SQL it calls, at some point, LiteralProcessor.LookupConstant on d.Fee, which calls ReflectHelper.GetConstantValue("d.Fee"), which for some reason assumes that "d" is the name of the class to which the property belongs. Of course it isn't, which breaks everything. I have no idea why it's going down this wrong path.
Okay, the problem seems to be that the HQL generator returns an expression where the 'd' parameter isn't treated as a parameter, so the resultant HQL doesn't know what to do with it. If I change the 'x' parameter in my query to 'd', as in
it all hangs together. This is obviously a bother, but not enough of one to outweigh being able to search on and select computed properties. I assume someone who understands the HQL "visitor" better would be able to make the proper adjustments in the HQL generator, but I'll leave that for some other volunteer.
UPDATE: I couldn't leave it at that, so I cobbled together a way to do it, with the help of some code from Phil Klein.
Phil's provided this class
which I use here
It's not going to work if the expression has multiple parameters, but that rarely happens and I'm really not looking to make a career of refining this :).
UPDATE: I have a resolution. Rather than subclassing
DefaultLinqToHqlGeneratorsRegistry, which seems to be too far down the chain of converting LINQ to SQL, I subclassedDefaultQueryProvider, IQueryProviderWithOptionsand inserted it withcfg.LinqQueryProvider<CustomQueryProvider>().This is based on code from Ivan Stoev that I found here.