无法使用 QueryOver 解析复合 属性
Cannot resolve a composite property using 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<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
只存在于server/client栈中!不在数据库端。
但是,问题出在这里:我的存储库代码。
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 不能解析我的组合 属性,尽管作为 属性 结果的两个属性都存在?
这个问题的 'quick-and-dirty' 解决方案是 OrderBy
姓氏,然后是名字。
var results = session
.QueryOver<Person>()
.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
...基于我的应用程序规则的 'correct' 顺序,以及此代码生成的列表,是
John Bane
Kit-Kat Chunky
Tim Dumbledore
Giselle Potter
Aberforth Scrooge
John Smith
案件结案...暂时。我不怀疑有更好的方法可以做到这一点;毕竟,我 是 QueryOver
语法的新手。
正如 指出的那样,QueryOver 的黄金规则几乎是 "If it's not mapped, you can't query on it"。
即使您的 属性 的定义非常简单,NHibernate 也不会深入研究 属性 并尝试理解实现并将该实现转换为 SQL。
但是,根据您的情况,有一些解决方法可能适用。
如果您的解决方案适合您,那么这可能就是您应该采用的解决方案,因为它非常简单。但是,您还可以做一些其他事情:
使用计算列并将其映射到DisplayName
.
我不确定您使用的是什么数据库引擎,但如果它支持计算列,那么您实际上可以在数据库中创建一个计算列来表示 DisplayName
。
例如在SQL服务器中:
alter table [Person] add [DisplayName] as [LastName] + N', ' + [FirstName]
这很简单,但是从关注点分离的角度来看,让您的数据库引擎关心特定行的列的显示方式可能是不正确的。
使用Projection
:
不幸的是,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 访问 DisplayName
属性 在 SQL[=80 中意味着什么=]
这非常复杂,但是如果您想在 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。 免责声明:这是link我的个人博客。
这可能是太多的信息,如果您出于某种原因对您的解决方案不满意,我个人会选择#1,然后是#2。
在我正在进行的项目中,我在 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
只存在于server/client栈中!不在数据库端。
但是,问题出在这里:我的存储库代码。
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 不能解析我的组合 属性,尽管作为 属性 结果的两个属性都存在?
这个问题的 'quick-and-dirty' 解决方案是 OrderBy
姓氏,然后是名字。
var results = session
.QueryOver<Person>()
.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
...基于我的应用程序规则的 'correct' 顺序,以及此代码生成的列表,是
John Bane
Kit-Kat Chunky
Tim Dumbledore
Giselle Potter
Aberforth Scrooge
John Smith
案件结案...暂时。我不怀疑有更好的方法可以做到这一点;毕竟,我 是 QueryOver
语法的新手。
正如
即使您的 属性 的定义非常简单,NHibernate 也不会深入研究 属性 并尝试理解实现并将该实现转换为 SQL。
但是,根据您的情况,有一些解决方法可能适用。
如果您的解决方案适合您,那么这可能就是您应该采用的解决方案,因为它非常简单。但是,您还可以做一些其他事情:
使用计算列并将其映射到
DisplayName
.我不确定您使用的是什么数据库引擎,但如果它支持计算列,那么您实际上可以在数据库中创建一个计算列来表示
DisplayName
。例如在SQL服务器中:
alter table [Person] add [DisplayName] as [LastName] + N', ' + [FirstName]
这很简单,但是从关注点分离的角度来看,让您的数据库引擎关心特定行的列的显示方式可能是不正确的。
使用
Projection
:不幸的是,
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 访问
DisplayName
属性 在 SQL[=80 中意味着什么=]这非常复杂,但是如果您想在 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。 免责声明:这是link我的个人博客。
这可能是太多的信息,如果您出于某种原因对您的解决方案不满意,我个人会选择#1,然后是#2。