在我正在进行的一个项目中,我在 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<Person>
{
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<Person> GetPeopleByGroup(int groupId)
{
// This just gets a cached NHibernate session
using(var session = DataContext.GetSession())
{
var results = session
.QueryOver<Person>()
.Where(p => p.Group.GroupId == groupId)
// Exception thrown here!
.OrderBy(p => p.DisplayName)
.List().ToList();
return results;
}
}
}
据我所知,这应该有效。 问题:为什么 NHibernate 无法解析我的复合属性,尽管事实上存在获取该属性结果的两个属性?
最佳答案
喜欢@Radim Köhler指出,黄金 QueryOver 规则几乎就是“如果未映射,则无法对其进行查询”。。
即使您的属性的定义非常简单,NHibernate 也不会深入研究该属性并尝试理解其实现,然后将该实现转换为 SQL。
但是,根据您的具体情况,有一些可能适用的解决方法。
如果您的解决方案适合您,那么您可能应该采用该解决方案,因为它非常简单。但是,您还可以执行其他一些操作:
使用计算列并将其映射到
DisplayName
。我不确定您使用的是什么数据库引擎,但如果它支持计算列,那么您实际上可以在数据库中创建一个代表
DisplayName
的计算列。以 SQL Server 为例:
alter table [Person] add [DisplayName] as [LastName] + N', ' + [FirstName]
这很简单,但从关注点分离的角度来看,让数据库引擎关心特定行的列的显示方式可能是不正确的。
使用
投影
:不幸的是,
Projections.Concat
不接受任意Projections
,因此您必须使用Projections.SqlFunction
(其中>Projections.Concat
无论如何使用)。你最终会得到这样的结果:var orderByProjection = Projections.SqlFunction( "concat", NHibernateUtil.String, Projections.Property<Person>(p => p.LastName), Projections.Constant(", "), Projections.Property<Person>(p => p.FirstName)); var people = session.QueryOver<Person>() .OrderBy(orderByProjection).Asc .List<Person>();
告诉 QueryOver 在 SQL 中访问
DisplayName
属性意味着什么这非常复杂,但如果您想在 QueryOver 查询中使用
DisplayName
,您实际上可以告诉 QueryOver 访问该属性应转换为什么。我实际上不会推荐这样做,因为它非常复杂并且重复逻辑(现在有两个地方定义了
DisplayName
)。也就是说,它可能对处于类似情况的其他人有用。无论如何,如果你很好奇(或者更可能是对 QueryOver 惩罚的贪吃者),那么这就是下面的样子:
public static class PersonExtensions { /// <summary>Builds correct property access for use inside of /// a projection. /// </summary> private static string BuildPropertyName(string alias, string property) { if (!string.IsNullOrEmpty(alias)) { return string.Format("{0}.{1}", alias, property); } return property; } /// <summary> /// Instructs QueryOver how to process the `DisplayName` property access /// into valid SQL. /// </summary> public static IProjection ProcessDisplayName( System.Linq.Expressions.Expression expression) { Expression<Func<Person, string>> firstName = p => p.FirstName; Expression<Func<Person, string>> 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<Person>() .OrderBy(p => p.DisplayName).Asc .List<Person>();
生成以下 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
您可以找到有关此技术的更多信息 here 。 免责声明:这是我个人博客的链接。
这可能是太多的信息,就我个人而言,如果您出于某种原因对解决方案不满意,我会选择#1,然后选择#2。
关于c# - 无法使用 QueryOver 解析复合属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29103643/