NHibernate QueryOver 对每个一对多集合进行排序

标签 nhibernate fluent-nhibernate one-to-many queryover

考虑这个人为的域:

namespace TryHibernate.Example {

public class Computer
{
    public int Id { get; set; }
    public IList<Device> Devices { get; set; }
}

public class Device
{
    public int Id { get; set; }
    public Maker Maker { get; set; }
}

public class Maker
{
    public int Id { get; set; }
    public string Name { get; set; }
}

} // namespace

如果我只查询所有计算机,它们的设备将随机排序。我想让他们按制造商的名字订购。我真的不能用 HasMany().OrderBy() 来做,因为据我所知,OrderBy 只能使用本地列(所以我可以按 Device.Id,例如)。此外,在每个查询的基础上控制排序会很好,所以我正在寻找 QueryOver 解决方案。

我能到达的最远的地方是:

using (ISessionFactory sessionFactory = Fluently.Configure()
    .Database(SQLiteConfiguration.Standard.UsingFile("temp.sqlite").ShowSql())
    .Mappings(m => m.AutoMappings.Add(
        AutoMap.AssemblyOf<Computer>(new ExampleConfig())
            .Conventions.Add(DefaultLazy.Never())
            .Conventions.Add(DefaultCascade.All())))
    .ExposeConfiguration(c => new SchemaExport(c).Create(true, true))
    .BuildSessionFactory())
{
    using (ISession db = sessionFactory.OpenSession())
    {
        Computer comp = new Computer();
        comp.Devices = new List<Device>();
        Device dev1 = new Device();
        comp.Devices.Add(dev1);
        dev1.Maker = new Maker() { Name = "IBM"};
        Device dev2 = new Device();
        comp.Devices.Add(dev2);
        dev2.Maker = new Maker() { Name = "Acer"};
        db.Persist(comp);
        db.Flush();
    }
    using (ISession db = sessionFactory.OpenSession())
    {   // This is the part I'm having trouble with:
        Device devAlias = null;
        Maker makerAlias = null;
        IList<Computer> comps = db.QueryOver<Computer>()
            .JoinAlias(c => c.Devices, () => devAlias)
            .JoinAlias(() => devAlias.Maker, () => makerAlias)
            .OrderBy(() => makerAlias.Name).Asc
            .List();
        Console.WriteLine(comps.Count);
        foreach (Device dev in comps[0].Devices)
        {
            Console.WriteLine(dev.Maker.Name);
        }
    }
}

当然,它并没有按照我的意愿行事。它尝试按制造商的名称对整个列表进行排序。它也成功了,正如我从 SQL 中看到的那样,我实际上得到了一个无用的计算机笛卡尔积,其设备由设备制造商分类。

但随后它发出另一个 SQL 查询来获取设备,这次没有排序。我想 NHibernate 不知道我的连接是为了获取 child 。

问题是,我如何控制第二个查询?例如,按制造商的名称订购设备,或者让每个 Computer.Devices 列表只包含由 IBM 制造的设备(如果有的话)。我想我需要一个子查询,但我应该在哪里插入它呢?

为了完整起见,这是我的配置:

class ExampleConfig : DefaultAutomappingConfiguration
{
    public override bool ShouldMap(Type type)
    {
        return type.Namespace == "TryHibernate.Example";
    }
}

最佳答案

您可以通过干扰 Devicees 检索的 SQL 选择语句来实现此目的。对我来说最优雅的解决方案是使用自定义加载器。但这是
not supported through Fluent NHibernate .幸运的是,您可以在项目的某处编写单个 hbm.xml,然后像这样通过 fluent 添加它:

设备.hbm.xml

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
    <class name="TryHibernate.Example.Device, TryHibernate" table="Device">
        <id name="Id" type="System.Int32, mscorlib" column="Id" generator="identity" />
        <property name="Name" />
        <many-to-one name="Maker" column="MakerId" />
        <many-to-one name="Computer" column="ComputerId" />
        <loader query-ref="DeviceLoader" />
    </class>

    <sql-query name="DeviceLoader">
        <return alias="dev" class="TryHibernate.Example.Device, TryHibernate" lock-mode="read">
            <return-property name="Computer" column="ComputerId" />
            <return-property name="Maker" column="MakerId" />
        </return>

        SELECT Device.Id AS {dev.Id}, Device.Name As {dev.Name}, Device.MakerId AS {dev.MakerId}, Device.ComputerId AS {dev.ComputerId}
        FROM Device INNER JOIN Maker ON Maker.Id = Device.MakerId
        WHERE Device.Id=?
        ORDER BY Device.ComputerId, Maker.Name
    </sql-query>
</hibernate-mapping>

然后在构建 session 工厂时做:

    using (ISessionFactory sessionFactory = Fluently.Configure()
        .Database(SQLiteConfiguration.Standard.UsingFile("temp.sqlite").ShowSql())
        .Mappings(m => m.AutoMappings.Add(
            AutoMap.AssemblyOf(new ExampleConfig())
                .Conventions.Add(DefaultLazy.Never())
                .Conventions.Add(DefaultCascade.All())))
        .Mappings(m => m.HbmMappings.AddFromAssembly(Assembly.GetExecutingAssembly()))
        .ExposeConfiguration(c => new SchemaExport(c).Create(true, true))
        .BuildSessionFactory())

Another thought that hit me is you could specify Subselect() for your mapping; like this:

public class DeviceMap : ClassMap<Device>
{
    public DeviceMap()
    {
        Table("Device");
        Subselect(@"
  SELECT TOP 100 Device.Id , Device.Name, Device.MakerId , Device.ComputerId
  FROM Device INNER JOIN Maker ON Maker.Id = Device.MakerId
  ORDER BY Device.ComputerId, Maker.Name
");

        Id(x => x.Id);
        Map(x => x.Name);

        References(x => x.Computer).Column("ComputerId");
        References(x => x.Maker).Column("MakerId");
    }

如您所见,我使用了 TOP 子句。这是必要的,因为 sub-select 语句变成了一个子查询,你可能知道

The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified.


还有一个方法是实现一个IInterceptor 接口(interface)并适本地改变SQL。像这样:

public class SqlStatementInterceptor : EmptyInterceptor
{
    public override SqlString OnPrepareStatement(SqlString sql)
    {
        var result = sql;

        if (sql.IndexOfCaseInsensitive("FROM Device") != -1)
        {
            var sqlText = string.Format("WITH cteDev AS ({0}{1}{0}){0}SELECT cteDev.* FROM cteDev INNER JOIN Maker ON cteDev.MakerId1_0_ = Maker.Id ORDER BY Maker.Name", Environment.NewLine, sql.ToString());

            result = sql
                .Insert(0, "WITH cteDev AS (")
                .Append(")SELECT cteDev.* FROM cteDev INNER JOIN Maker ON cteDev.MakerId1_0_ = Maker.Id ORDER BY Maker.Name");
            ;

        }

        Trace.WriteLine(result.ToString());

        return result;
    }
}

*** 对于这个,您需要使用约定并了解 NHibernate 如何生成表和列的名称。

关于NHibernate QueryOver 对每个一对多集合进行排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38414904/

相关文章:

NHibernate:找出属性是否映射到字段

nhibernate - 为什么 NHibernate 要求实体方法是虚拟的?

c# - 保存实体时重复子条目

c# - 使用 Fluent nHibernate 生成多个模式

hibernate - 保存具有 OneToMany 关系的模型

java - 目标实体未定义与OneToMany关系的错误

c# - Fluent NHibernate Composite ID表问题

c# - Fluent NHibernate - 如何一对一映射子类?

ios - 在核心数据中有效地获取一对多关系中的最新记录

c# - Fluent NHibernate 日期时间 UTC