NHibernate的QueryOver联合财产他人财产 [英] NHibernate QueryOver Coalesce a property to another property

查看:477
本文介绍了NHibernate的QueryOver联合财产他人财产的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑这个愚蠢的域名:

 命名空间TryHibernate.Example 
{
公共类员工
{
公众诠释标识{搞定;组; }
公共字符串名称{;组; }
}

公共类工作项目
{
公众诠释标识{搞定;组; }
公共字符串描述{搞定;组; }
公共DateTime的起始日期{搞定;组; }
公共DateTime的结束日期{搞定;组; }
}

公共类任务
{
公众诠释标识{搞定;组; }
公务员受让人{搞定;组; }
公开工作项工作项{搞定;组; }
公共字符串详细{搞定;组; }
公众的DateTime? StartDateOverride {搞定;组; }
公众的DateTime? EndDateOverride {搞定;组; }
}
}



的想法是,每个工作项可以被分配多个员工不同的细节,可能压倒一切的开始/结束日期的工作项目本身。如果这些覆盖为空,他们应该从工作项目,而不是服用。



现在我想与在的有效限制日期。我已经试过这首:

 的IList<任务>任务= db.QueryOver<任务>(()=> taskAlias)
.JoinAlias(()=> taskAlias.WorkItem,()=> wiAlias)
。凡(()=> ; taskAlias.StartDateOverride.Coalesce(()=> wiAlias.StartDate)LT =结束)
。而(()=> taskAlias.EndDateOverride.Coalesce(()=> wiAlias.EndDate)GT; =启动)
的.List();



不幸的是,它并不如编译合并需要一个恒定的,而不是一个属性表达式



好吧,我已经试过这样:

 。凡(()=>(taskAlias.StartDateOverride == NULL 
wiAlias.StartDate
:taskAlias.StartDateOverride)LT =结束)
。而(()=>(taskAlias.EndDateOverride == NULL
wiAlias.EndDate
:taskAlias.EndDateOverride)> =启动)

这将引发NullReferenceException异常。不知道为什么,但可能是因为NHibernate的不正确翻译是三元运算符(并尝试实际调用它来代替),或者因为 == NULL 是不完全正确的方法来检查空值。反正,我甚至没有想到它的工作。



最后,这个作品:

 的IList<任务>任务= db.QueryOver<任务>(()=> taskAlias)
.JoinAlias(()=> taskAlias.WorkItem,()=> wiAlias)
。凡(Restrictions.LeProperty(
Projections.SqlFunction(COALESCE,NHibernateUtil.DateTime,
Projections.Property(()=> taskAlias.StartDateOverride),
Projections.Property(()=> wiAlias.StartDate )),
Projections.Constant(结束)))
。而(Restrictions.GeProperty(
Projections.SqlFunction(COALESCE,NHibernateUtil.DateTime,
Projections.Property( ()=> taskAlias.EndDateOverride),
Projections.Property(()=> wiAlias.EndDate)),
Projections.Constant(开始)))
的.List();



但没有办法,我可以调用干净的代码。也许我可以提取一定的表达成单独的方法来清理一点点,但它会好得多使用表达式语法,而不是这些丑陋的预测。有没有办法做到这一点?没有任何理由的背后NHibernate的在合并扩展不支持属性表达式?



一个显而易见的选择是选择一切然后筛选使用LINQ或任何结果。但它可能成为大量总行的性能问题。



下面是万一有人完整的代码想尝试一下:



 使用(ISessionFactory SessionFactory的= Fluently.Configure()
.Database(SQLiteConfiguration.Standard.UsingFile(temp.sqlite)。ShowSql())
.Mappings(M = GT; m.AutoMappings.Add(
AutoMap.AssemblyOf<员工>(新ExampleConfig())
.Conventions.Add(DefaultLazy.Never())
.Conventions.Add(DefaultCascade.All())))
.ExposeConfiguration(C =>新建的SchemaExport(C).Create(真,真))
.BuildSessionFactory())
{使用
(ISession的DB = sessionFactory.OpenSession())
{
员工EMPL =新员工(){名称=乔};
工作项无线=新工作项目()
{
说明=重要工作,
起始日期=新的日期时间(2016,01,01),
结束日期=新日期时间(2017年,01,01)
};
任务任务1 =新的任务()
{
受让人= EMPL,
=工作项目WI,
详细=做这个,
};
db.Save(独立写作);
任务TASK2 =新任务()
{
受让人= EMPL,
工作项目=无线,
详细=做,
StartDateOverride =新日期时间(2016年,7,1),
EndDateOverride =新的日期时间(2017年,1,1),
};
db.Save(TASK2);
任务taskAlias = NULL;
工作项目wiAlias = NULL;
日期时间开始=新日期时间(2016年,1,1);
日期时间结束=新日期时间(2016年,6,30);
&IList的LT;任务>任务= db.QueryOver<任务>(()=> taskAlias)
.JoinAlias(()=> taskAlias.WorkItem,()=> wiAlias)
//这不能编译:
//凡(()=> taskAlias.StartDateOverride.Coalesce(()=> wiAlias.StartDate)LT =结束)
//而(()=> taskAlias .EndDateOverride.Coalesce(()=> wiAlias.EndDate)> =启动)
//这会引发的NullReferenceException:
//凡(()=>(taskAlias.StartDateOverride == NULL ?wiAlias.StartDate:taskAlias.StartDateOverride)< =结束)
//而(()=>(taskAlias.EndDateOverride ==空wiAlias.EndDate:taskAlias.EndDateOverride)> =启动)$? b $ b //这工作:
。凡(Restrictions.LeProperty(
Projections.SqlFunction(COALESCE,NHibernateUtil.DateTime,
Projections.Property(()=> taskAlias。 StartDateOverride),
Projections.Property(()=> wiAlias.StartDate)),
Projections.Constant(结束)))
。而(Restrictions.GeProperty(
Projections.SqlFunction(COALESCE,NHibernateUtil.DateTime,
预测.Property(()=> taskAlias.EndDateOverride),
Projections.Property(()=> wiAlias.EndDate)),
Projections.Constant(开始)))
的.List ();
的foreach(在任务任务T)
Console.WriteLine(找到任务:{0},t.Details);
}
}

和配置是非常简单的:

 类ExampleConfig:DefaultAutomappingConfiguration 
{
公众覆盖布尔ShouldMap(type类型)
{
返回type.Namespace ==TryHibernate.Example
}
}


解决方案

让开始与此:

  //这不能编译:
//凡(()=> taskAlias.StartDateOverride.Coalesce(()=> wiAlias.StartDate)LT; =结束)
//而(()=> taskAlias.EndDateOverride.Coalesce(()=> wiAlias.EndDate)GT =启动)

和它修改为:

 。凡(()=> taskAlias.StartDateOverride.Coalesce(wiAlias.StartDate)LT =结束)
。而(()=> taskAlias.EndDateOverride .Coalesce(wiAlias.EndDate)> =启动)

现在它将编译。但在运行时,它会产生相同的的NullReferenceException 。不好。



原来,NHibernate的确实试图评估合并参数。这很容易通过观察 ProjectionExtensions 类的实现可以看出。下面的方法处理合并翻译:

 内部静态IProjection ProcessCoalesce( methodCallExpression methodCallExpression)
{
IProjection投影= ExpressionProcessor.FindMemberProjection(methodCallExpression.Arguments [0])AsProjection();
obj对象= ExpressionProcessor.FindValue(methodCallExpression.Arguments [1]);
返回Projections.SqlFunction(COALESCE(ITYPE)NHibernateUtil.Object,投影,Projections.Constant(OBJ));
}



注意,不同的处理的第一个参数( FindMemberExpresion )比第二个参数( FindValue )。好吧, FindValue 只是尝试计算表达式。



现在我们知道是什么原因造成的问题。我不知道为什么它是实现这种方式,所以会集中精力寻找解决办法。



幸运的是, ExpressionProcessor 类是公共的,也可以让你注册通过 RegisterCustomMethodCall / RegisterCustomProjection 方法。这使我们的解决方案:




  • 创建类似于合并自定义的扩展方法(姑且称他们为 IFNULL 例如)

  • 注册一个自定义的处理器

  • 使用他们,而不是的合并



下面是执行:

 公共静态类CustomProjections 
{
静态CustomProjections()
{
ExpressionProcessor.RegisterCustomProjection(()= > IFNULL(NULL,),ProcessIfNull);
ExpressionProcessor.RegisterCustomProjection(()=> IFNULL(空,0),ProcessIfNull);
}

公共静态无效的注册(){}

公共静态牛逼IFNULL< T>(这件T OBJECTPROPERTY,T replaceValueIfIsNull)
{
抛出新的异常(不直接使用 - QueryOver表达式中使用);
}

公共静态T' IFNULL< T>(?这件T OBJECTPROPERTY,T replaceValueIfIsNull)其中T:结构
{
抛出新的异常(不直接使用 - 使用QueryOver表达式中);
}

私有静态IProjection ProcessIfNull(MethodCallExpression MCE)
{
变种为arg0 = ExpressionProcessor.FindMemberProjection(mce.Arguments [0])AsProjection()。
变种的arg1 = ExpressionProcessor.FindMemberProjection(mce.Arguments [1])AsProjection();
返回Projections.SqlFunction(聚结,NHibernateUtil.Object,为arg0,ARG1);
}
}



由于这些方法都不会被调用,您需要确保定制的处理器是通过调用注册方法进行注册。这是一个空的方法,只是为了确保类的静态构造函数被调用,而实际报名情况。



因此,在你的榜样,包括开头:



CustomProjections.Register( );



然后查询中使用:

 。凡(()=> taskAlias.StartDateOverride.IfNull(wiAlias.StartDate)LT =结束)
。而(()=> taskAlias.EndDateOverride.IfNull(wiAlias.EndDate)GT =启动)

和预期它会工作。



P.S。上面的实现同时适用于恒和表达参数,所以它是真正安全的替换合并


Consider this silly domain:

namespace TryHibernate.Example
{
    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class WorkItem
    {
        public int Id { get; set; }
        public string Description { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
    }

    public class Task
    {
        public int Id { get; set; }
        public Employee Assignee { get; set; }
        public WorkItem WorkItem { get; set; }
        public string Details { get; set; }
        public DateTime? StartDateOverride { get; set; }
        public DateTime? EndDateOverride { get; set; }
    }
}

The idea is that each work item may be assigned to multiple employees with different details, potentially overriding start/end dates the of work item itself. If those overrides are null, they should be taken from the work item instead.

Now I'd like to perform a query with restrictions on the effective dates. I've tried this first:

IList<Task> tasks = db.QueryOver<Task>(() => taskAlias)
    .JoinAlias(() => taskAlias.WorkItem, () => wiAlias)
    .Where(() => taskAlias.StartDateOverride.Coalesce(() => wiAlias.StartDate) <= end)
    .And(() => taskAlias.EndDateOverride.Coalesce(() => wiAlias.EndDate) >= start)
    .List();

Unfortunately, it doesn't compile as Coalesce expects a constant, not a property expression.

OK, I've tried this:

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

This throws NullReferenceException. Not sure why, but probably either because NHibernate doesn't properly translate that ternary operator (and tries to actually invoke it instead) or because == null isn't exactly the right way to check for nulls. Anyway, I didn't even expect it to work.

Finally, this one works:

IList<Task> tasks = db.QueryOver<Task>(() => taskAlias)
    .JoinAlias(() => taskAlias.WorkItem, () => wiAlias)
    .Where(Restrictions.LeProperty(
        Projections.SqlFunction("COALESCE", NHibernateUtil.DateTime,
            Projections.Property(() => taskAlias.StartDateOverride),
            Projections.Property(() => wiAlias.StartDate)),
        Projections.Constant(end)))
    .And(Restrictions.GeProperty(
        Projections.SqlFunction("COALESCE", NHibernateUtil.DateTime,
            Projections.Property(() => taskAlias.EndDateOverride),
            Projections.Property(() => wiAlias.EndDate)),
        Projections.Constant(start)))
    .List();

But there is no way I can call that clean code. Maybe I can extract certain expressions into separate methods to clean it up a little bit, but it would be much better to use expression syntax rather than these ugly projections. Is there a way to do it? Is there any reason behind NHibernate not supporting property expressions in the Coalesce extension?

One obvious alternative is to select everything and then filter results using Linq or whatever. But it could become a performance problem with large number of total rows.

Here is full code in case someone wants to try it:

using (ISessionFactory sessionFactory = Fluently.Configure()
    .Database(SQLiteConfiguration.Standard.UsingFile("temp.sqlite").ShowSql())
    .Mappings(m => m.AutoMappings.Add(
        AutoMap.AssemblyOf<Employee>(new ExampleConfig())
            .Conventions.Add(DefaultLazy.Never())
            .Conventions.Add(DefaultCascade.All())))
    .ExposeConfiguration(c => new SchemaExport(c).Create(true, true))
    .BuildSessionFactory())
{
    using (ISession db = sessionFactory.OpenSession())
    {
        Employee empl = new Employee() { Name = "Joe" };
        WorkItem wi = new WorkItem()
        {
            Description = "Important work",
            StartDate = new DateTime(2016, 01, 01),
            EndDate = new DateTime(2017, 01, 01)
        };
        Task task1 = new Task()
        {
            Assignee = empl,
            WorkItem = wi,
            Details = "Do this",
        };
        db.Save(task1);
        Task task2 = new Task()
        {
            Assignee = empl,
            WorkItem = wi,
            Details = "Do that",
            StartDateOverride = new DateTime(2016, 7, 1),
            EndDateOverride = new DateTime(2017, 1, 1),
        };
        db.Save(task2);
        Task taskAlias = null;
        WorkItem wiAlias = null;
        DateTime start = new DateTime(2016, 1, 1);
        DateTime end = new DateTime(2016, 6, 30);
        IList<Task> tasks = db.QueryOver<Task>(() => taskAlias)
            .JoinAlias(() => taskAlias.WorkItem, () => wiAlias)
            // This doesn't compile:
            //.Where(() => taskAlias.StartDateOverride.Coalesce(() => wiAlias.StartDate) <= end)
            //.And(() => taskAlias.EndDateOverride.Coalesce(() => wiAlias.EndDate) >= start)
            // This throws NullReferenceException:
            //.Where(() => (taskAlias.StartDateOverride == null ? wiAlias.StartDate : taskAlias.StartDateOverride) <= end)
            //.And(() => (taskAlias.EndDateOverride == null ? wiAlias.EndDate : taskAlias.EndDateOverride) >= start)
            // This works:
            .Where(Restrictions.LeProperty(
                Projections.SqlFunction("COALESCE", NHibernateUtil.DateTime,
                    Projections.Property(() => taskAlias.StartDateOverride),
                    Projections.Property(() => wiAlias.StartDate)),
                Projections.Constant(end)))
            .And(Restrictions.GeProperty(
                Projections.SqlFunction("COALESCE", NHibernateUtil.DateTime,
                    Projections.Property(() => taskAlias.EndDateOverride),
                    Projections.Property(() => wiAlias.EndDate)),
                Projections.Constant(start)))
            .List();
        foreach (Task t in tasks)
            Console.WriteLine("Found task: {0}", t.Details);
    }
}

And the configuration is really simple:

class ExampleConfig : DefaultAutomappingConfiguration
{
    public override bool ShouldMap(Type type)
    {
        return type.Namespace == "TryHibernate.Example";
    }
}

解决方案

Let start with this:

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

and modify it to:

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

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

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));
}

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.

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:

  • Create a custom extensions methods similar to Coalesce (let call them IfNull for instance)
  • Register a custom processor
  • Use them instead of Coalesce

Here is the implementation:

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);
    }
}

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();

then use inside the query:

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

and it will work as expected.

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

这篇关于NHibernate的QueryOver联合财产他人财产的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆