c# - 无法使用 QueryOver 解析复合属性

标签 c# nhibernate

在我正在进行的一个项目中,我在 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。

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

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

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

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

    以 SQL Server 为例:

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

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

  2. 使用投影:

    不幸的是,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 在 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/

相关文章:

C# 如何正确地对遵循装饰器模式的类进行单元测试?

c# - 允许对实体使用自定义属性的最佳设计

database - 在数据库中存储枚举值的最佳方式 - String 或 Int

c# - 为什么 MSDTC 在使用 mstest 进行单元测试时表现不一致?

c# - 尝试使用 INSERT 方法但不起作用

c# - 在 web api Controller 中进行通用查询/getby 规范?

c# - 有没有更好的方法来调用 C# 中对象列表的比较?

c# - 大文件下载功能失败

c# - 从一个 session 中检索对象并在另一个 session 中更新 nHibernate

c# - 使用 NHibernate 对连接的全名进行模糊搜索