NHibernate中表之间的复杂关系

时间:2011-01-05 19:22:40

标签: c# nhibernate fluent-nhibernate nhibernate-mapping automapping

我正在为旧版Oracle数据库编写Fluent NHibernate映射。挑战在于表具有复合主键。如果我完全自由,我会重新设计关系并自动生成主键,但其他应用程序必须写入同一个数据库并从中读取,所以我不能这样做。

这些是我将关注的两个表:

alt text

示例数据

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:我知道这是一个艰难的,所以如果你给我一个很好的答案,你将获得适当的奖励:)

感谢您阅读这个长期问题,现在我只希望获得最佳效果:)

5 个答案:

答案 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抽象,如果它只是妨碍了呢?