在我正在从事的项目中,我在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]
这很简单,但是从关注点分离的角度来看,让数据库引擎关心特定行的列的显示方式可能是不正确的。
使用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在SQL中访问DisplayName
属性意味着什么
这很复杂,但是如果您想在QueryOver查询中使用DisplayName
,则实际上可以告诉QueryOver该属性应转换为什么访问权限。
我实际上不建议这样做,因为它很复杂并且重复了逻辑(现在在两个地方定义了DisplayName
)。也就是说,这对于处于类似情况的其他人可能很有用。
无论如何,如果您感到好奇(或者更可能是QueryOver惩罚的glut嘴),那么情况将是这样:
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。免责声明:这是指向我的个人博客的链接。
这可能是太多的信息,如果您出于某种原因对解决方案不满意,我个人将排名第一,然后排名第二。
为什么要使用Func <string>而不是string? - c#为什么要使用Func<string>而不是string?我的问题特别是关于this回购。有问题的行是22: private static Func<string> getToken = () => Environment.GetEnvironmentVariable("GitHubToken", Enviro…
无法从ArrayList <String>转换为List <Comparable> - java当我写下面的代码时,编译器说 无法从ArrayList<String>转换为List<Comparable>private List<Comparable> get(){ return new ArrayList<String>(); } 但是当我用通配符编写返回类型时,代码会编译。private List&l…
Div单击与单选按钮相同吗? - php有没有一种方法可以使div上的click事件与表单环境中的单选按钮相同?我只希望下面的div提交值,单选按钮很丑代码输出如下:<input id="radio-2011-06-08" value="2011-06-08" type="radio" name="radio_date…
将谓词<T>转换为Func <T,bool> - c#我有一个包含成员Predicate的类,希望在Linq表达式中使用该类:using System.Linq; class MyClass { public bool DoAllHaveSomeProperty() { return m_instrumentList.All(m_filterExpression); } private IEnumerable&…
故障排除“警告:session_start():无法发送会话高速缓存限制器-标头已发送” - php我收到警告:session_start()[function.session-start]:无法发送会话缓存限制器-标头已发送(错误输出开始如果我将表单数据提交到其他文件进行处理,则可以正常工作。但是,如果我将表单数据提交到同一页面,则会出现此错误。请建议<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0…