且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

NHibernate QueryOver 将一个属性合并到另一个属性

更新时间:2023-01-08 16:59:26

让我们从这个开始:

// This doesn't compile:
//.Where(() => taskAlias.StartDateOverride.Coalesce(() => wiAlias.StartDate) <= end)
//.And(() => taskAlias.EndDateOverride.Coalesce(() => wiAlias.EndDate) >= start)

并将其修改为:

.Where(() => taskAlias.StartDateOverride.Coalesce(wiAlias.StartDate) <= end)
.And(() => taskAlias.EndDateOverride.Coalesce(wiAlias.EndDate) >= start)

现在可以编译了.但是在运行时它会生成相同的 NullReferenceException.不好.

now it will compile. But at runtime it generates the same NullReferenceException. No good.

事实证明,NHibernate 确实尝试评估 Coalesce 参数.这可以通过查看 ProjectionExtensions 类实现轻松看出.以下方法处理 Coalesce 转换:

It turns out that NHibernate indeed tries to evaluate the Coalesce argument. This can easily be seen by looking at ProjectionExtensions class implementation. The following method handles the Coalesce translation:

internal static IProjection ProcessCoalesce(MethodCallExpression methodCallExpression)
{
  IProjection projection = ExpressionProcessor.FindMemberProjection(methodCallExpression.Arguments[0]).AsProjection();
  object obj = ExpressionProcessor.FindValue(methodCallExpression.Arguments[1]);
  return Projections.SqlFunction("coalesce", (IType) NHibernateUtil.Object, projection, Projections.Constant(obj));
}

注意第一个参数 (FindMemberExpresion) 和第二个参数 (FindValue) 的不同处理.好吧,FindValue 只是尝试对表达式求值.

Notice the different handling of the first argument (FindMemberExpresion) vs second argument (FindValue). Well, FindValue simply tries to evaluate the expression.

现在我们知道是什么导致了问题.我不知道为什么它是这样实现的,所以将专注于寻找解决方案.

Now we know what is causing the issue. I have no idea why it is implemented that way, so will concentrate on finding a solution.

幸运的是,ExpressionProcessor 类是公共的,还允许您通过 RegisterCustomMethodCall/RegisterCustomProjection 方法注册自定义方法.这使我们找到了解决方案:

Fortunately, the ExpressionProcessor class is public and also allows you to register a custom methods via RegisterCustomMethodCall / RegisterCustomProjection methods. Which leads us to the solution:

  • 创建一个类似于 Coalesce 的自定义扩展方法(例如,我们称它们为 IfNull)
  • 注册自定义处理器
  • 使用它们代替 Coalesce
  • Create a custom extensions methods similar to Coalesce (let call them IfNull for instance)
  • Register a custom processor
  • Use them instead of Coalesce

这是实现:

public static class CustomProjections
{
    static CustomProjections()
    {
        ExpressionProcessor.RegisterCustomProjection(() => IfNull(null, ""), ProcessIfNull);
        ExpressionProcessor.RegisterCustomProjection(() => IfNull(null, 0), ProcessIfNull);
    }

    public static void Register() { }

    public static T IfNull<T>(this T objectProperty, T replaceValueIfIsNull)
    {
        throw new Exception("Not to be used directly - use inside QueryOver expression");
    }

    public static T? IfNull<T>(this T? objectProperty, T replaceValueIfIsNull) where T : struct
    {
        throw new Exception("Not to be used directly - use inside QueryOver expression");
    }

    private static IProjection ProcessIfNull(MethodCallExpression mce)
    {
        var arg0 = ExpressionProcessor.FindMemberProjection(mce.Arguments[0]).AsProjection();
        var arg1 = ExpressionProcessor.FindMemberProjection(mce.Arguments[1]).AsProjection();
        return Projections.SqlFunction("coalesce", NHibernateUtil.Object, arg0, arg1);
    }
}

由于这些方法从未被调用,因此您需要通过调用Register 方法来确保自定义处理器已注册.这是一个空方法,只是为了确保调用类的静态构造函数,实际注册发生在那里.

Since these methods are never called, you need to ensure the custom processor is registered by calling Register method. It's an empty method just to make sure the static constructor of the class is invoked, where the actual registration happens.

所以在你的例子中,包括在开头:

So in your example, include at the beginning:

CustomProjections.Register();

然后在查询中使用:

.Where(() => taskAlias.StartDateOverride.IfNull(wiAlias.StartDate) <= end)
.And(() => taskAlias.EndDateOverride.IfNull(wiAlias.EndDate) >= start)

它会按预期工作.

附言上述实现适用于常量和表达式参数,因此它确实是 Coalesce 的安全替代.

P.S. The above implementation works for both constant and expression arguments, so it's really a safe replacement of the Coalesce.