我正在为旧版Oracle数据库编写Fluent NHibernate映射。挑战在于表具有复合主键。如果我完全自由,我会重新设计关系并自动生成主键,但其他应用程序必须写入同一个数据库并从中读取,所以我不能这样做。
这些是我将关注的两个表:
Trips table:
1, 10:00, 11:00 ...
1, 12:00, 15:00 ...
1, 16:00, 19:00 ...
2, 12:00, 13:00 ...
3, 9:00, 18:00 ...
Faults table:
1, 13:00 ...
1, 23:00 ...
2, 12:30 ...
在这种情况下,车辆1进行了三次行程并且有两次故障。第一次故障发生在第二次行程中,第二次故障发生在车辆静止时。车辆2有一次行程,在此期间发生了故障。
同一辆车的行程从不重叠。因此,表格具有可选的一对多关系,因为每个故障要么在旅行期间发生,要么不发生。如果我想在SQL中加入它们,我会写:
select ...
from Faults left outer join Trips
on Faults.VehicleId = Trips.VehicleId
and Faults.FaultTime between Trips.TripStartTime and Trips.TripEndTime
然后我会得到一个数据集,其中每个错误只出现一次(正如我所说的那样一对多)。
请注意,没有车辆表,我也不需要。但我确实创建了一个包含两个表中所有VehicleId的视图,因此我可以将它用作联结表。
这些表格很大,因为它们涵盖了多年的数据,每次我只需要获取几个小时的范围。
所以我需要一个映射和一个标准,它将运行类似下面的SQL:
select ...
from Faults left outer join Trips
on Faults.VehicleId = Trips.VehicleId
and Faults.FaultTime between Trips.TripStartTime and Trips.TripEndTime
where Faults.FaultTime between :p0 and :p1
你有什么想法如何实现它?
注1:目前应用程序不应该写入数据库,因此持久性不是必须的,尽管如果映射支持持久性,它可能在将来的某个时候有所帮助。
注2:我知道这是一个艰难的,所以如果你给我一个很好的答案,你将获得适当的奖励:)
感谢您阅读这个长期问题,现在我只希望获得最佳效果:)
答案 0 :(得分:3)
鉴于评论中的其他信息,我现在建议尝试以下类映射,而不是使用此答案中提到的任何自定义SQL解决方案:
<class name="Fault" table="Faults">
<composite-id>
<key-property name="VehicleId" />
<key-property name="FaultTime" />
<key-property name="FaultType" />
<generator class="assigned" />
</id>
<many-to-one name="Trip" class="Trip">
<!-- Composite Key of Trip is calculated on the fly -->
<formula>VehicleId</formula>
<formula>
( SELECT TripStartTime
FROM Trips t
WHERE VehicleId = t.VehicleId
AND FaultTime BETWEEN t.TripStartTime AND t.TripEndTime
)
</formula>
</many-to-one>
...
</class>
<class name="Trip" table="Trips">
<composite-id>
<key-property name="VehicleId" />
<key-property name="TripStartTime" />
</composite-id>
...
</class>
使用此映射,您可以根据需要加载和查询Fault实体。
我最初在这里考虑了一个(命名的)自定义SQL查询。您可以在映射文件中输入以下查询,以便为给定的车辆加载Fault对象:
<sql-query name="LoadFaultsAndTrips" xml:space="preserve">
<return class="Fault" alias="f"/>
<return-join alias="t" property="f.Trip"/>
SELECT {f.*}
, {t.*}
FROM Faults f
LEFT OUTER JOIN Trips t
ON f.VehicleId = t.VehicleId
AND f.FaultTime BETWEEN t.TripStartTime AND t.TripEndTime
WHERE f.VehicleId = ?
</sql-query>
如果您需要在没有显式查询的情况下在Vehicle对象上加载Faults集合,您可以在XML中尝试以下映射构造:
<class name="Vehicle">
<id name="VehicleId" type="...">
<generator class="..." />
</id>
...
<bag name="Faults" table="Faults" inverse="true">
<key column="VehicleId" />
<loader query-ref="VehicleFaultsLoader" />
</bag>
...
</class>
<sql-query name="VehicleFaultsLoader" xml:space="preserve">
<load-collection role="Vehicle.Faults" alias="f" />
<return-join alias="t" property="f.Trip"/>
SELECT {f.*}
, {t.*}
FROM Faults f
LEFT OUTER JOIN Trips t
ON f.VehicleId = t.VehicleId
AND f.FaultTime BETWEEN t.TripStartTime AND t.TripEndTime
WHERE f.VehicleId = ?
</sql-query>
此处的关键是为Vehicle类上的Faults集合定义自定义集合加载器,并定义接收Vehicle作为参数的主键的自定义SQL查询。我还没有使用流利的NHibernate,所以我担心我无法帮助你处理这部分问题。
干杯, Gerke。
答案 1 :(得分:2)
你的例子sql在语法上与
相同select ...
from Faults left join Trips
on Faults.VehicleId = Trips.VehicleId
where Faults.VehicleId is null or (Faults.FaultTime between Trips.TripStartTime and Trips.TripEndTime)
考虑到这一点,您可以创建一个常规地图,如(流利)
HasMany< Trip >( fault => fault.Trips )
.KeyColumn( "VehicleId" )
.Table( "Trips" )
.LazyLoad( )
.Cascade.Delete( )
.AsSet()
然后使用你所熟悉的每种形式的查询,无论是hql,icriteria,icriteriaover还是linq都使用上面提到的where子句进行标准查询。
linq中的将是:
IList<Trip> results =
(
fault in Session.Query< Entities.Faults >
join trip in Session.Query< Entities.Trips > on fault.VehicleId equals trip.VehicleId into trip
where
fault.FaultTime > startTime && fault.FaultTime < endTime &&
// Here is the rest of the join criteria expressed as a where criteria
(
trip == null
||
(
fault.FaultTime > trip.TripStartTime && fault.FaultTime < trip.TripEndTime
)
)
select fault
).ToList();
如果需要,我可以在ICriteria或IQueryOver中给你一个例子。
当然这只能起作用,因为您提供的示例语句可以在获得结果时重写为where子句。如果您真实世界所需的sql更复杂,您需要考虑是否可以在归档相同结果时重写所需的sql。
答案 2 :(得分:1)
我对NH很新,只知道NH的基本知识,所以当我遇到这样的情况时,我写了一个存储过程,然后通过NH写called it。最终我会找到一个全NH解决方案,然后我将重构代码并删除存储过程的必要性。
可能有效的另一种方法是编写您需要的HQL。
答案 3 :(得分:1)
我会提出一个建议,如果您使用的是NHibernate 3,请尝试使用Linq转换为NH。使用Linq,您可以为一次性执行指定manualy /任意关系,或者如果您认为它将被重复使用,则使用管道(或者如果您想要进行左/右连接,则需要指定它,如果它是一个isser join你根本不需要指定一个连接,它是从映射中推断出来的,而且是业务逻辑而不是persitence逻辑。
作为一个简单的例子,它可能会像:
var result = (
fault in Session.Query< Entities.Faults >
join trip in Session.Query< Entities.Trips > on fault.VehicleId equals trip.VehicleId into trip
where
fault.FaultTime > startTime && fault.FaultTime < endTime &&
fault.FaultTime > trip.TripStartTime && fault.FaultTime < trip.TripEndTime
select fault
).ToList();
我用手写这个,所以它可能不完美,但足够接近。这应该完全符合您的需要,并允许您在不改变映射的情况下根据需要进行更改。
答案 4 :(得分:0)
如果您已经知道要执行数据库的查询,为什么不直接使用自己的自定义DAO类执行查询?为什么要干扰NHibernate抽象,如果它只是妨碍了呢?