简体   繁体   English

NHibernate中表之间的复杂关系

[英]Complex relationship between tables in NHibernate

I'm writing a Fluent NHibernate mapping for a legacy Oracle database. 我正在为旧版Oracle数据库编写Fluent NHibernate映射。 The challenge is that the tables have composite primary keys. 挑战在于表具有复合主键。 If I were at total freedom, I would redesign the relationships and auto-generate primary keys, but other applications must write to the same database and read from it, so I cannot do it. 如果我有足够的自由,那么我将重新设计关系并自动生成主键,但是其他应用程序必须写入同一数据库并从中读取,因此我无法做到这一点。

These are the two tables I'll focus on: 这些是我将重点关注的两个表:

替代文字

Example data 示例数据

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 ...

In this case, vehicle 1 made three trips and has two faults. 在这种情况下,车辆1发生了三次跳闸,并发生了两次故障。 The first fault happened during the second trip, and the second fault happened while the vehicle was resting. 第一次故障发生在第二次行程中,第二次故障发生在车辆静止时。 Vehicle 2 had one trip, during which a fault happened. 车辆2行驶了一次,在此期间发生了故障。

Constraints 约束条件

Trips of the same vehicle never overlap. 同一辆车的行程永远不会重叠。 So the tables have an optional one-to-many relationship, because every fault either happens during a trip or it doesn't. 因此,表具有可选的一对多关系,因为每个故障要么在旅途中发生,要么不发生。 If I wanted to join them in SQL, I would write: 如果要在SQL中加入它们,我会写:

select ... 
from Faults left outer join Trips
  on Faults.VehicleId = Trips.VehicleId
  and Faults.FaultTime between Trips.TripStartTime and Trips.TripEndTime

and then I'd get a dataset where every fault appears exactly once (one-to-many as I said). 然后我得到一个数据集, 每个故障都恰好出现一次 (如我所说的一对多)。

Note that there is no Vehicles table, and I don't need one. 请注意,这里没有“车辆”表,我也不需要。 But I did create a view that contains all VehicleIds from both tables, so I can use it as a junction table. 但是我确实创建了一个包含两个表中所有VehicleId的视图,因此可以将其用作联结表。

What am I actually looking for? 我实际上在寻找什么?

The tables are huge because they cover years of data, and every time I only need to fetch a range of a few hours. 这些表格之所以庞大,是因为它们涵盖了多年的数据,并且每次我只需要获取几个小时的范围。

So I need a mapping and a criteria that will run something like the following SQL underneath: 因此,我需要一个映射和一个条件,该条件和条件将在下面运行类似以下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

Do you have any ideas how to achieve it? 您有什么想法要实现吗?

Note 1: Currently the application shouldn't write to the database, so persistence is not a must, although if the mapping supports persistence, it may help at some point in the future. 注1:当前,应用程序不应写入数据库,因此持久性不是必须的,尽管如果映射支持持久性,则将来可能会有所帮助。

Note 2: I know it's a tough one, so if you give me a great answer, you will be properly rewarded :) 注意2:我知道这是一个艰难的过程,因此,如果您给我一个很好的答案,您将得到适当的回报:)

Thank you for reading this long question, and now I only hope for the best :) 感谢您阅读这个冗长的问题,现在我只希望最好:)

Current Recommendation 当前建议

Given the additional information in the comments, I would now propose trying the following class mappings instead of using any of the custom SQL solutions mentioned further down this answer: 鉴于注释中的其他信息,我现在建议尝试使用以下类映射,而不是使用此答案后面提到的任何自定义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>

Using this mapping you can load and query the Fault entities however you like. 使用此映射,您可以根据需要加载和查询Fault实体。

Obsolete Suggestions 过时的建议

I originally considered a (named) custom SQL query here. 我最初在这里考虑了一个(命名的)自定义SQL查询。 You could enter the following query in your mapping file to load Fault objects with for a given vehicle: 您可以在映射文件中输入以下查询,以加载给定车辆的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>

If you need to load the Faults collection on a Vehicle object without explicit queries you could try the following mapping construct in XML: 如果需要在没有显式查询的情况下在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>

The key here is to define a custom collection loader for the Faults collection on the Vehicle class and to define a custom SQL query that receives the primary key of Vehicle as parameter. 此处的关键是为Vehicle类上的Faults集合定义一个自定义集合加载器,并定义一个自定义SQL查询,该查询将Vehicle的主键作为参数。 I haven't used fluent NHibernate yet myself, so I'm afraid I cannot help you with that part of the question. 我本人还没有使用过流利的NHibernate,所以恐怕我无法帮助您解决问题的那一部分。

Cheers, Gerke. 干杯,Gerke。

You example sql there is syntactically the same as 您的示例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)

with this in mind, you can create a regular map such as (fluent) 考虑到这一点,您可以创建一个常规地图,例如(流利)

HasMany< Trip >( fault => fault.Trips )
    .KeyColumn( "VehicleId" )
    .Table( "Trips" )
    .LazyLoad( )
    .Cascade.Delete( )
    .AsSet()

then using what every form of querying you are comfortable with, be it hql, icriteria, icriteriaover or linq do your standard query with a where clause as mentioned above. 然后使用hql,icriteria,icriteriaover或linq等您喜欢的每种查询形式,通过上述where子句进行标准查询。

in linq that would be: 在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();

If need be I can give you an example in ICriteria or IQueryOver. 如果需要,我可以在ICriteria或IQueryOver中给您一个示例。

Of course this only work because of the example statement you provided can be re-written as a where clause while having the result. 当然,由于您提供的示例语句,只有在得到结果时才可以将其重写为where子句,因此只能这样做。 If you real world desired sql is more complex you'd need to think if the desired sql can be re-written while archiving the same result. 如果现实世界中所需的sql更复杂,则需要考虑是否可以在归档相同结果的同时重写所需的sql。

I'm pretty new to NH and only know NH rudiments, so when I've hit a situation like this I've written a stored proc and then called it through NH. 我对NH很陌生,只了解NH基本知识,因此遇到这种情况时,我编写了一个存储的proc,然后通过NH 对其进行了调用 Eventually I'll find an all-NH solution, and then I'll refactor the code and remove the necessity for the stored proc. 最终,我将找到一个全NH解决方案,然后将重构代码并删除存储过程的必要性。

Another approach that might work is to just write the HQL you need. 另一种可行的方法是只编写所需的HQL

I'll make a suggestion, if you are using NHibernate 3, try Linq to NH. 我会提出一个建议,如果您使用的是NHibernate 3,请尝试使用Linq to NH。 Using Linq you can specify manualy/arbitrary relationships for a once off execution, or use pipes if you think it's going to be re-used (or being linq if you want to do a left/right join you need to specify it, if it's an isser join you don't need to specify a join at all, its all inferred from the mappings) and is business logic and not persitence logic. 使用Linq可以为一次执行指定手动/任意关系,或者如果您认为它将被重用,则使用管道(如果要进行左/右联接,则可以使用linq,如果需要,则需要指定它)一个isser联接,您根本不需要指定联接(从映射中推断出所有联接),它是业务逻辑而非持久性逻辑。

As a quick example it would be somethng like: 举个简单的例子:

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();

I've written this by hand so it might not be perfect, but close enough. 我已经亲手写了这本书,因此它可能并不完美,但是足够接近。 This should do exactly what you need, and allow you to change it as you see fit without changing you mappings. 这应该可以完全满足您的需求,并允许您在不改变映射的情况下根据需要进行更改。

If you already know what query you want the DB to execute, why not just execute the query directly using your own custom DAO class? 如果您已经知道要数据库执行的查询,为什么不使用自己的自定义DAO类直接执行查询呢? Why bother with the NHibernate abstraction, if it's just getting in the way? 如果只是妨碍使用NHibernate抽象,为什么还要麻烦呢?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM