Computed properties in NHibernate

Roland, 26.04.2016

Implementing interface IHqlGeneratorForProperty helps to provide a consistent computed properties defined as LINQ expression in the domain class. Suppose that we have a domain class having property NumberOfApprovedWorkItems in a class:

namespace ComputedProperties.Tests.Domain
{
    using ComputedProperties.Tests.Services;
    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.Linq.Expressions;

    public class Employee
    {
        // . . .

        public static readonly Expression<Func<Employee, int>> CalculateNumberOfApprovedWorkItemsExpression =
            x => x.Work.Count(w => w.ApprovalDate != null);

        private static readonly Func<Employee, int> CalculateNumberOfApprovedWorkItems =
            CalculateNumberOfApprovedWorkItemsExpression.Compile();

        public virtual int NumberOfApprovedWorkItems
        {
            get { return CalculateNumberOfApprovedWorkItems(this); }
        }

        // . . .
    }
}

Then having following implementations:

namespace ComputedProperties.Tests.CalculatedProperties
{
    using FluentNHibernate.Utils.Reflection;
    using NHibernate.Hql.Ast;
    using NHibernate.Linq.Functions;
    using NHibernate.Linq.Visitors;
    using System;
    using System.Linq.Expressions;
    using System.Reflection;

    public class CalculatedPropertyGenerator<T, TResult> : BaseHqlGeneratorForProperty
    {
        public static void Register(
            ILinqToHqlGeneratorsRegistry registry,
            Expression<Func<T, TResult>> property,
            Expression<Func<T, TResult>> calculationExp)
        {
            registry.RegisterGenerator(
                ReflectionHelper.GetMember(property).MemberInfo,
                new CalculatedPropertyGenerator<T, TResult>(calculationExp));
        }

        public CalculatedPropertyGenerator(Expression<Func<T, TResult>> calculationExp)
        {
            this.calculationExp = calculationExp;
        }

        private readonly Expression<Func<T, TResult>> calculationExp;

        public override HqlTreeNode BuildHql(MemberInfo member, Expression expression,
            HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
        {
            return visitor.Visit(calculationExp);
        }
    }
}

and:

namespace ComputedProperties.Tests.CalculatedProperties
{
    using Domain;
    using NHibernate.Linq.Functions;

    class ComputedPropertyGeneratorRegistry : DefaultLinqToHqlGeneratorsRegistry
    {
        public ComputedPropertyGeneratorRegistry()
        {
            CalculatedPropertyGenerator<Employee, int>.Register(
                this,
                x => x.NumberOfApprovedWorkItems,
                Employee.CalculateNumberOfApprovedWorkItemsExpression);
        }
    }
}

we have computed property query in the form of LINQ expression instead of raw SQL string embeded in the mapping of the entity class. So now we can write a query using that property:

session.Query<Employee>().Where(x => x.NumberOfApprovedWorkItems > 3);

NHibernate knows the formula for computed property - it is defined in expression. So it will issue appropriate SQL for that so we can have it in our Where clause. On the other hand, when we want to use that property in our code - we just use it and the expression would get applied again.

Maybe it is not perfect because in our code the property is going to be computed over and over again what could have some consequences. But it is some way to get it achieved without providing SQL query part string.