Сложные отношения между таблицами в NHibernate - PullRequest
4 голосов
/ 05 января 2011

Я пишу отображение Fluent NHibernate для устаревшей базы данных Oracle. Проблема в том, что таблицы имеют составные первичные ключи. Если бы у меня была полная свобода, я бы перепроектировал отношения и автоматически сгенерировал первичные ключи, но другие приложения должны записывать в ту же базу данных и читать из нее, поэтому я не могу этого сделать.

Вот две таблицы, на которых я сосредоточусь:

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

и тогда я получу набор данных, в котором каждая ошибка появляется ровно один раз (как я сказал один-ко-многим).

Обратите внимание, что нет стола Транспортных средств, и он мне не нужен. Но я создал представление, которое содержит все идентификаторы транспортных средств из обеих таблиц, поэтому я могу использовать его в качестве соединительной таблицы.

Что я на самом деле ищу?

Таблицы огромны, потому что они охватывают данные за многие годы, и каждый раз, когда мне нужно выбрать диапазон в несколько часов.

Так что мне нужно сопоставление и критерии, которые будут запускать что-то вроде следующего 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 ]

3 голосов
/ 09 января 2011

Текущая рекомендация

Учитывая дополнительную информацию в комментариях, я бы сейчас предложил попробовать следующие сопоставления классов вместо использования какого-либо из пользовательских решений 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>

Если вам нужно загрузить коллекцию Faults в объект Vehicle без явных запросов, вы можете попробовать следующую конструкцию отображения в 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>

Ключевым моментом здесь является определение пользовательского загрузчика коллекции для коллекции Faults в классе Vehicle и определение пользовательского запроса SQL, который получает первичный ключ Vehicle в качестве параметра. Я еще не использовал беглый NHibernate, поэтому боюсь, что не смогу помочь вам с этой частью вопроса.

Ура, Герка.

2 голосов
/ 11 января 2011

Ваш пример 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 при архивировании того же результата.

1 голос
/ 11 января 2011

Я сделаю предложение, если вы используете NHibernate 3, попробуйте Linq to NH. Используя Linq, вы можете указать ручные / произвольные отношения для однократного выполнения или использовать каналы, если вы думаете, что это будет использоваться повторно (или быть linq, если вы хотите сделать соединение влево / вправо, вам нужно указать это, если isser join, вам вообще не нужно указывать соединение, все это выводится из отображений) и является бизнес-логикой, а не логикой постоянства.

В качестве быстрого примера это будет что-то вроде:

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

Я написал это от руки, так что это может быть не идеально, но достаточно близко. Это должно делать именно то, что вам нужно, и позволяет вам изменять его по своему усмотрению, не меняя отображения.

1 голос
/ 09 января 2011

Я довольно новичок в NH и знаю только зачатки NH, поэтому, когда я попал в подобную ситуацию, я написал сохраненный процесс, а затем назвал его через NH.В конце концов я найду решение для всех NH, а затем реорганизую код и устраню необходимость в хранимых процессах.

Еще один подход, который может сработать, - написать HQL тебе нужно.

0 голосов
/ 06 января 2011

Если вы уже знаете, какой запрос вы хотите, чтобы БД выполняла, почему бы просто не выполнить запрос напрямую, используя свой собственный класс DAO? Зачем беспокоиться об абстракции NHibernate, если она только мешает?

...