考虑这个人为的域:
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";
}
}
最佳答案
您可以通过干扰 Device
es 检索的 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/