Какой алгоритм использовать с отношением «многие ко многим» в NHibernate - PullRequest
2 голосов
/ 03 мая 2009

У меня есть отношение многие ко многим между фотографиями и тегами: фотография может иметь несколько тегов, и несколько фотографий могут использовать одни и те же теги.

У меня есть цикл, который сканирует фотографии в каталоге, а затем добавляет их в NHibernate. Некоторые теги добавляются к фотографиям во время этого процесса, например, тег 2009 года, когда фотография сделана в 2009 году.

Класс Tag реализует Equals и GetHashCode и использует свойство Name в качестве единственного свойства подписи. И Фото, и Тег имеют суррогатные ключи и имеют версии.

У меня есть код, подобный следующему:

public void Import() {
    ...
    foreach (var fileName in fileNames) {
        var photo = new Photo { FileName = fileName };
        AddDefaultTags(_session, photo, fileName);
        _session.Save(photo);
    }
    ...
}

private void AddDefaultTags(…) {
    ...
    var tag =_session.CreateCriteria(typeof(Tag))
                    .Add(Restriction.Eq(“Name”, year.ToString()))
                    .UniqueResult<Tag>();

    if (tag != null) {
        photo.AddTag(tag);
    } else {
        var tag = new Tag { Name = year.ToString()) };
        _session.Save(tag);
        photo.AddTag(tag);
    }
}

Моя проблема в том, что тег не существует, например, первое фото нового года. Метод AddDefaultTags проверяет, существует ли тег в базе данных, а затем создает его и добавляет в NHibernate. Это прекрасно работает при добавлении одной фотографии, но при импорте нескольких фотографий в новом году и в рамках одной и той же единицы работы происходит сбой, поскольку она все еще не существует в базе данных и добавляется снова. По завершении единицы работы происходит сбой, поскольку он пытается добавить две записи в таблицу тегов с одинаковым именем ...

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

Ответы [ 3 ]

2 голосов
/ 03 мая 2009

Вам нужно запустить _session.Flush(), если ваши критерии не должны возвращать устаревшие данные. Или вы должны быть в состоянии сделать это правильно, установив _session.FlushMode на Авто.

При использовании FlushMode.Auto сессия автоматически сбрасывается перед выполнением критериев.

РЕДАКТИРОВАТЬ: И важно! При чтении кода, который вы показали, не похоже, что вы используете транзакцию для своей единицы работы. Я бы порекомендовал заключить вашу единицу работы в транзакцию - это необходимо для работы FlushMode.Auto, если вы используете NH2.0 +!

Подробнее читайте здесь: NHibernate ISession Flush: где и когда его использовать и почему?

0 голосов
/ 04 мая 2009

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

Это варианты, которые я знаю до сих пор:

  • Оптимистичный: имейте уникальное ограничение на имя и разрешите одному из сеансов генерировать коммит. Тогда попробуйте еще раз. Вы должны убедиться, что вы не заканчиваете бесконечный цикл, когда возникает другая ошибка.
  • Пессимистично: при добавлении нового тега вы блокируете всю таблицу тегов с помощью TSQL.
  • .NET Locking: вы синхронизируете потоки, используя .NET блокировки. Это работает, только если вы выполняете параллельные транзакции в одном и том же процессе.
  • Создание тегов с использованием собственного сеанса (см. Ниже)

Пример:

public static Tag CreateTag(string name)
{
  try
  {
    using (ISession session = factors.CreateSession())
    {
      session.BeginTransaction();
      Tag existingTag = session.CreateCriteria(typeof(Tag)) /* .... */
      if (existingtag != null) return existingTag;
      {
        session.Save(new Tag(name));
      }
      session.Transaction.Commit();
    }
  }
  // catch the unique constraint exception you get
  catch (WhatEverException ex)
  {
    // try again
    return CreateTag(name);
  }
}

Это выглядит просто, но с некоторыми проблемами. Вы всегда получаете тег, который либо существует, либо создан (и зафиксирован немедленно). Но получаемый вами тег относится к другому сеансу, поэтому он отключен для вашего основного сеанса. Вам необходимо прикрепить его к сеансу с помощью каскадов (которые вы, вероятно, не хотите) или обновить.

Создание тегов больше не связано с вашей основной транзакцией, это было целью, но также означает, что откат вашей транзакции оставляет все созданные теги в базе данных. Другими словами: создание тегов больше не является частью вашей транзакции.

0 голосов
/ 03 мая 2009

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

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

Вы должны опубликовать свои сопоставления, поскольку я, возможно, неправильно интерпретировал ваш вопрос.

...