无法使用 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。

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

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

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

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

    例如在SQL服务器中:

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

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

  2. 使用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>();
    
  3. 告诉 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。