c# – NHibernate QueryOver将属性合并到另一个属性

前端之家收集整理的这篇文章主要介绍了c# – NHibernate QueryOver将属性合并到另一个属性前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
考虑这个愚蠢的域名:
  1. namespace TryHibernate.Example
  2. {
  3. public class Employee
  4. {
  5. public int Id { get; set; }
  6. public string Name { get; set; }
  7. }
  8.  
  9. public class WorkItem
  10. {
  11. public int Id { get; set; }
  12. public string Description { get; set; }
  13. public DateTime StartDate { get; set; }
  14. public DateTime EndDate { get; set; }
  15. }
  16.  
  17. public class Task
  18. {
  19. public int Id { get; set; }
  20. public Employee Assignee { get; set; }
  21. public WorkItem WorkItem { get; set; }
  22. public string Details { get; set; }
  23. public DateTime? StartDateOverride { get; set; }
  24. public DateTime? EndDateOverride { get; set; }
  25. }
  26. }

这个想法是每个工作项可能被分配给具有不同细节的多个员工,可能会覆盖工作项本身的开始/结束日期.如果这些覆盖为空,则应该从工作项中取出它们.

现在我想执行一个有效日期限制的查询.我先试过这个:

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

不幸的是,它没有编译,因为Coalesce期望一个常量,而不是属性表达式.

好的,我试过这个:

  1. .Where(() => (taskAlias.StartDateOverride == null
  2. ? wiAlias.StartDate
  3. : taskAlias.StartDateOverride) <= end)
  4. .And(() => (taskAlias.EndDateOverride == null
  5. ? wiAlias.EndDate
  6. : taskAlias.EndDateOverride) >= start)

这会抛出NullReferenceException.不知道为什么,但可能是因为NHibernate没有正确翻译那个三元运算符(并尝试实际调用它),或者因为== null不是检查空值的正确方法.无论如何,我甚至没想到它会起作用.

最后,这个工作:

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

但我无法称之为干净的代码.也许我可以将某些表达式提取到单独的方法中来清理它,但是使用表达式语法而不是这些丑陋的预测会好得多.有办法吗? NHibernate背后是否有任何理由不支持Coalesce扩展中的属性表达式?

一个明显的选择是选择所有内容,然后使用Linq或其他任何方式过滤结果.但它可能会成为一个总行数很多的性能问题.

这是完整的代码,以防有人想要尝试:

  1. using (ISessionFactory sessionFactory = Fluently.Configure()
  2. .Database(sqliteConfiguration.Standard.UsingFile("temp.sqlite").Showsql())
  3. .Mappings(m => m.AutoMappings.Add(
  4. AutoMap.AssemblyOf<Employee>(new ExampleConfig())
  5. .Conventions.Add(DefaultLazy.Never())
  6. .Conventions.Add(DefaultCascade.All())))
  7. .ExposeConfiguration(c => new SchemaExport(c).Create(true,true))
  8. .BuildSessionFactory())
  9. {
  10. using (ISession db = sessionFactory.OpenSession())
  11. {
  12. Employee empl = new Employee() { Name = "Joe" };
  13. WorkItem wi = new WorkItem()
  14. {
  15. Description = "Important work",StartDate = new DateTime(2016,01,01),EndDate = new DateTime(2017,01)
  16. };
  17. Task task1 = new Task()
  18. {
  19. Assignee = empl,WorkItem = wi,Details = "Do this",};
  20. db.Save(task1);
  21. Task task2 = new Task()
  22. {
  23. Assignee = empl,Details = "Do that",StartDateOverride = new DateTime(2016,7,1),EndDateOverride = new DateTime(2017,1,};
  24. db.Save(task2);
  25. Task taskAlias = null;
  26. WorkItem wiAlias = null;
  27. DateTime start = new DateTime(2016,1);
  28. DateTime end = new DateTime(2016,6,30);
  29. IList<Task> tasks = db.QueryOver<Task>(() => taskAlias)
  30. .JoinAlias(() => taskAlias.WorkItem,() => wiAlias)
  31. // This doesn't compile:
  32. //.Where(() => taskAlias.StartDateOverride.Coalesce(() => wiAlias.StartDate) <= end)
  33. //.And(() => taskAlias.EndDateOverride.Coalesce(() => wiAlias.EndDate) >= start)
  34. // This throws NullReferenceException:
  35. //.Where(() => (taskAlias.StartDateOverride == null ? wiAlias.StartDate : taskAlias.StartDateOverride) <= end)
  36. //.And(() => (taskAlias.EndDateOverride == null ? wiAlias.EndDate : taskAlias.EndDateOverride) >= start)
  37. // This works:
  38. .Where(Restrictions.LeProperty(
  39. Projections.sqlFunction("COALESCE",Projections.Constant(end)))
  40. .And(Restrictions.GeProperty(
  41. Projections.sqlFunction("COALESCE",Projections.Constant(start)))
  42. .List();
  43. foreach (Task t in tasks)
  44. Console.WriteLine("Found task: {0}",t.Details);
  45. }
  46. }

配置非常简单:

  1. class ExampleConfig : DefaultAutomappingConfiguration
  2. {
  3. public override bool ShouldMap(Type type)
  4. {
  5. return type.Namespace == "TryHibernate.Example";
  6. }
  7. }

解决方法

让我们从这开始:
  1. // This doesn't compile:
  2. //.Where(() => taskAlias.StartDateOverride.Coalesce(() => wiAlias.StartDate) <= end)
  3. //.And(() => taskAlias.EndDateOverride.Coalesce(() => wiAlias.EndDate) >= start)

并将其修改为:

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

现在它将编译.但在运行时它会生成相同的NullReferenceException.不好.

事实证明,NHibernate确实试图评估Coalesce论证.通过查看ProjectionExtensions类实现可以很容易地看到这一点.以下方法处理Coalesce转换:

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

注意第一个参数(FindMemberExpresion)与第二个参数(FindValue)的不同处理.好吧,FindValue只是试图评估表达式.

现在我们知道造成这个问题的原因了.我不知道为什么会这样实现,所以将集中精力寻找解决方案.

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

>创建类似于Coalesce的自定义扩展方法(例如,将它们称为IfNull)
>注册自定义处理器
>使用它们而不是Coalesce

这是实施:

  1. public static class CustomProjections
  2. {
  3. static CustomProjections()
  4. {
  5. ExpressionProcessor.RegisterCustomProjection(() => IfNull(null,""),ProcessIfNull);
  6. ExpressionProcessor.RegisterCustomProjection(() => IfNull(null,0),ProcessIfNull);
  7. }
  8.  
  9. public static void Register() { }
  10.  
  11. public static T IfNull<T>(this T objectProperty,T replaceValueIfIsNull)
  12. {
  13. throw new Exception("Not to be used directly - use inside QueryOver expression");
  14. }
  15.  
  16. public static T? IfNull<T>(this T? objectProperty,T replaceValueIfIsNull) where T : struct
  17. {
  18. throw new Exception("Not to be used directly - use inside QueryOver expression");
  19. }
  20.  
  21. private static IProjection ProcessIfNull(MethodCallExpression mce)
  22. {
  23. var arg0 = ExpressionProcessor.FindMemberProjection(mce.Arguments[0]).AsProjection();
  24. var arg1 = ExpressionProcessor.FindMemberProjection(mce.Arguments[1]).AsProjection();
  25. return Projections.sqlFunction("coalesce",NHibernateUtil.Object,arg0,arg1);
  26. }
  27. }

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

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

CustomProjections.Register();

然后在查询中使用:

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

它将按预期工作.

附:上面的实现适用于常量和表达式参数,因此它实际上是Coalesce的安全替代品.

猜你在找的C#相关文章