无法使用QueryOver解析复合属性

在我正在开发的项目中,我在NHibernate中采用了更新的QueryOver语法。 但是,我在复合属性上实现排序时遇到问题。

我要查询的模型看起来像这样:

 public class Person { public virtual int Id { get; set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } // Not really relevant; it has an ID, but that's all we care about // for this question. public virtual Group Group { get; set; } // This is the culprit of my troubles. public virtual string DisplayName { get { return LastName + ", " + FirstName; } } } 

…我的映射看起来像这样:

 public class PersonMap : ClassMap { Table("Persons"); Id(x => x.Id); Map(x => x.FirstName); Map(x => x.LastName); References(x => x.Group) .Not.Nullable() .Column("GroupId") .Fetch.Join(); } 

注意: DisplayName仅存在于服务器/客户端堆栈中! 不在数据库方面。

但是,这就是问题发生的地方:我的存储库代码。

 public class PersonRepository { // ...Other methods... public static IEnumerable GetPeopleByGroup(int groupId) { // This just gets a cached NHibernate session using(var session = DataContext.GetSession()) { var results = session .QueryOver() .Where(p => p.Group.GroupId == groupId) // Exception thrown here! .OrderBy(p => p.DisplayName) .List().ToList(); return results; } } } 

据我所知,这应该是有效的。 问题:为什么NHibernate不能解析我的复合属性,尽管事实上两个属性都是存在该属性的结果?

就像@RadimKöhler指出的那样,黄金的QueryOver规则几乎就是“如果它没有映射,你就无法查询它”

即使你的属性定义很简单,NHibernate也不会深入研究该属性并尝试理解实现,然后将该实现转换为SQL。

但是,根据您的具体情况,可能会有一些可能适用的解决方法。

如果您的解决方案适合您,那么这可能是您应该使用的,因为它非常简单。 但是,您还可以做一些其他事情:

  1. 使用计算列并将其映射到DisplayName

    我不确定你正在使用什么数据库引擎,但如果它支持计算列,那么你实际上可以在数据库中创建一个表示DisplayName的计算列。

    例如在SQL服务器中:

     alter table [Person] add [DisplayName] as [LastName] + N', ' + [FirstName] 

    这很简单,但从关注点分离的角度来看,让数据库引擎关注特定行的列的显示方式可能是不正确的。

  2. 使用Projection

    不幸的是, Projections.Concat没有采用任意Projections ,所以你必须使用Projections.SqlFunctionProjections.Concat仍然使用它)。 你最终得到这样的东西:

     var orderByProjection = Projections.SqlFunction( "concat", NHibernateUtil.String, Projections.Property(p => p.LastName), Projections.Constant(", "), Projections.Property(p => p.FirstName)); var people = session.QueryOver() .OrderBy(orderByProjection).Asc .List(); 
  3. 告诉QueryOver在SQL中访问DisplayName属性的含义

    这非常复杂,但如果您想在QueryOver查询中使用DisplayName ,您实际上可以告诉QueryOver访问该属性应该转换成什么。

    我实际上不会推荐这个,因为它非常复杂并且它重复了逻辑(现在有两个地方定义了DisplayName )。 也就是说,它可能对类似情况下的其他人有用。

    无论如何,如果你好奇(或者更可能是对QueryOver惩罚的贪婪),这就是这样:

     public static class PersonExtensions { /// Builds correct property access for use inside of /// a projection. ///  private static string BuildPropertyName(string alias, string property) { if (!string.IsNullOrEmpty(alias)) { return string.Format("{0}.{1}", alias, property); } return property; } ///  /// Instructs QueryOver how to process the `DisplayName` property access /// into valid SQL. ///  public static IProjection ProcessDisplayName( System.Linq.Expressions.Expression expression) { Expression> firstName = p => p.FirstName; Expression> lastName = p => p.LastName; string aliasName = ExpressionProcessor.FindMemberExpression(expression); string firstNameName = ExpressionProcessor.FindMemberExpression(firstName.Body); string lastNameName = ExpressionProcessor.FindMemberExpression(lastName.Body); PropertyProjection firstNameProjection = Projections.Property(BuildPropertyName(aliasName, firstNameName)); PropertyProjection lastNameProjection = Projections.Property(BuildPropertyName(aliasName, lastNameName)); return Projections.SqlFunction( "concat", NHibernateUtil.String, lastNameProjection, Projections.Constant(", "), firstNameProjection); } } 

    然后,您需要使用NHibernate注册处理逻辑,可能就在您的其他配置代码之后:

     ExpressionProcessor.RegisterCustomProjection( () => default(Person).DisplayName, expr => PersonExtensions.ProcessDisplayName(expr.Expression)); 

    最后,您可以在QueryOver查询中使用您的(未映射)属性:

     var people = session.QueryOver() .OrderBy(p => p.DisplayName).Asc .List(); 

    哪个生成以下SQL:

     SELECT this_.Id as Id0_0_, this_.FirstName as FirstName0_0_, this_.LastName as LastName0_0_ FROM Person this_ ORDER BY (this_.LastName + ', ' + this_.FirstName) asc 

    您可以在此处找到有关此技术的更多信息 免责声明 :这是我个人博客的链接。

这可能是太多的信息,如果你因为某些原因对你的解决方案不满意,我个人会选择#1然后#2。

这个问题的“快速而肮脏”的解决方案是OrderBy Last Name,然后是First Name。

 var results = session .QueryOver() .Where(p => p.Group.GroupId == groupId) .OrderBy(p => p.LastName).Asc() .OrderBy(p => p.FirstName).Asc() .List().ToList(); 

我本可以做一个投影,但我觉得它不太可读。 无论如何,给出样本人员列表……

 John Smith Aberforth Scrooge Tim Dumbledore Giselle Potter John Bane Kit-Kat Chunky 

…基于我的应用规则的’正确’顺序,以及此代码生成的列表,是

 John Bane Kit-Kat Chunky Tim Dumbledore Giselle Potter Aberforth Scrooge John Smith 

案件结束……现在。 我不怀疑有更好的方法来做到这一点; QueryOver ,我 QueryOver语法的QueryOver