Уникальные ограничения NHibernate

У меня возникли проблемы с уникальными ограничениями в NHibernate.

У меня есть сущность User, которая сопоставлена ​​с уникальным ограничением для свойства Username. Что я хочу сделать, так это иметь возможность проверить, существует ли конкретное имя пользователя, до добавления нового пользователя, а также до того, как существующий пользователь обновит свое имя пользователя.

Первый сценарий (добавление нового пользователя) работает нормально. Однако, когда я пытаюсь проверить, существует ли имя пользователя перед обновлением существующего пользователя, я получаю нарушение ограничения. Вот как выглядит код моего метода Save.

public void Save<T>(T entity) where T : User
    {
        using (var session = GetSession())
        using (var transaction = session.BeginTransaction())
        {
            try
            {
                CheckIfUsernameExists(entity);

                session.SaveOrUpdate(entity);
                session.Flush();
                transaction.Commit();
            }
            catch (HibernateException)
            {
                transaction.Rollback();
                throw;
            }
        }
    }

Ограничение нарушено в методе CheckIfUsernameExists () и выглядит это так:

public void CheckIfUsernameExists<T>(T entity) where T : User
    {
        var user = GetUserByUsername(entity);
        if (user != null)
            throw new UsernameExistsException();
    }

    private T GetUserByUsername<T>(T entity) where T : User
    {
        var username = entity.Username;
        var idToExclude = entity.Id;

        var session = GetSession();

        var user = session.CreateCriteria<T>()
            .Add(Restrictions.Eq("Username", username))
            .Add(Restrictions.Not(Restrictions.IdEq(idToExclude)))
            .UniqueResult() as T;

        return user;
    }

Это строка session.CreateCriteria (), которая вызывает сбой, в результате чего возникает исключение NHibernateException (SQLiteException) с сообщением «Прервать из-за нарушения ограничения. Имя пользователя столбца не уникально».

Связано ли это с наличными NHibernate? Сущность, переданная методу сохранения, была обновлена ​​с желаемым именем пользователя во время вызова session.CreateCriteria ().

Возможно, я все делаю неправильно (я новичок в NHibernate), поэтому, пожалуйста, не стесняйтесь заявлять очевидное и предлагать альтернативы.

Любая помощь высоко ценится!


person Kristoffer Ahl    schedule 25.09.2009    source источник


Ответы (4)


Хм, я не уверен в сути проблемы, но для стратегии попытки увидеть, существует ли уже пользователь, зачем вам ".UniqueResult ()"?

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

public bool ExistsUsername(string username, int idToExclude)
{
   IList<User> usersFound = someNHibernateCriteria excluding entries that have id = idToExclude

   return (usersFound.Count > 0)
}
person Juri    schedule 25.09.2009
comment
Ответ на ваш вопрос: нет. Я изменил код на тот, который вы предложили, но результат тот же. Спасибо за вклад! - person Kristoffer Ahl; 25.09.2009
comment
хорошо ... Я думаю, вы наложили уникальное ограничение на имя пользователя col в своей БД? Более того, я читал, что у вас есть объект, который передается методу сохранения, который был обновлен с желаемым именем пользователя во время сеанса ... Я бы не стал этого делать. Я бы назвал ExistsUsername раньше, чтобы вы также могли избежать idToAvoid. - person Juri; 25.09.2009
comment
Да, в базе данных есть ограничение для имени пользователя. Что касается вызова ExistsUsername перед обновлением объекта; Возможно, это возможно, но мне нужно будет продолжить расследование. Спасибо. - person Kristoffer Ahl; 25.09.2009
comment
Это было просто мнение для оптимизации логики. Назначение имени пользователя сущности, когда вы на самом деле не знаете, действительно ли имя пользователя, звучит для меня неправильно. Логически это неверно. Вместо этого я бы проверил это, скажем, на вашем контроллере пользовательского интерфейса с помощью вызова ExistsUsername (имя пользователя) и, если да, отобразил соответствующее сообщение, в противном случае вызовите SaveUser (...). - person Juri; 25.09.2009
comment
@Juri: Предлагаемая вами стратегия - это то, к чему мы стремились. Теперь у нас есть служба поверх нашего репозитория, которая обрабатывает проверку уникального имени пользователя. Спасибо! - person Kristoffer Ahl; 09.10.2009

Две мысли: - Почему бы вам просто не сохранитьOrUpdate и не посмотреть, получится ли у вас. Это невозможно в вашем сценарии? - Я видел, как вы упоминали SQLite. Это ваша настоящая производственная система или просто то, что вы используете для тестирования. Если да, то проверяли ли вы, вызывает ли проблемы SQLite и работает ли запрос с полнофункциональной СУБД? - SQLite часто создает такие проблемы, потому что не поддерживает все виды ограничений ...

person Thomas Weller    schedule 25.09.2009
comment
Томас, я надеялся, что смогу просто вызвать SaveOrUpdate, но поскольку я не могу проверить, какой тип исключения был сгенерирован, у меня нет возможности уведомить пользователя о том, что пошло не так. Вот почему я хотел выбросить собственное исключение (UsernameExistsException). Или есть способ получить более конкретные исключения NHibernate, о которых я не знаю? По поводу SQLite; да, мы используем SQLite на наших локальных машинах разработки и для наших интеграционных тестов. В настоящее время у нас нет настроенной производственной среды. Я буду иметь это в виду, но кажется маловероятным, что SQLite не справился бы с этим простым сценарием. - person Kristoffer Ahl; 25.09.2009
comment
Вы генерируете свой экземпляр SQLite через hbm2ddl из сопоставлений NH? Тогда в этом проблема: SQLite не поддерживает операторы ALTER TABLE, AFAIK они молча игнорируются. Вы просто не можете использовать SQLite для интеграционных тестов, если вы планируете использовать другую СУБД в производстве - это не полная замена. Это может быть быстро и легко, но может дать неверные результаты. Вам понадобится полчаса, чтобы протестировать этот код на полной СУБД - я уверен, что тест пройдет ... - person Thomas Weller; 26.09.2009
comment
@ Томас Веллер: Боюсь, что это не так. Мы отбрасываем таблицы в базу данных и используем экспорт схемы для добавления новых таблиц. Что касается SQLite для интеграционных тестов, ваше утверждение прямо противоположно тому, что я слышал от многих людей. Почему вы не можете использовать его для интеграционных тестов? Спасибо за вклад! - person Kristoffer Ahl; 09.10.2009
comment
@Kristoffer: вы не можете его использовать, потому что SQLite, например, не имеет ограничений уникального или внешнего ключа. Поэтому, если вы проверяете логику, которая полагается на это, вы получите неверные результаты. И что еще хуже, на мой взгляд: если вы используете экспорт схемы для создания базы данных на лету из сопоставлений NH, вы вообще не получите на это никаких подсказок (сообщение об ошибке, сообщение журнала или что-то еще). Я знаю, что многим нравится использовать SQLite для такого рода тестов, но, тем не менее, они имеют серьезные недостатки и, строго говоря, бесполезны. - person Thomas Weller; 01.11.2009

Вы уверены, что исключение выбрано в CreateCriteria? Потому что я не понимаю, как можно получить исключение ограничения SQLlite из оператора select. Я делаю практически то же самое ...

public bool NameAlreadyExists(string name, int? exclude_id)
{
    ICriteria crit = session.CreateCriteria<User>()
        .SetProjection(Projections.Constant(1))
        .Add(Restrictions.Eq(Projections.Property("name"), name));

    if (exclude_id.HasValue)
        crit.Add(Restrictions.Not(Restrictions.IdEq(exclude_id.Value)));

    return crit.List().Count > 0;
}

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

person dotjoe    schedule 25.09.2009

transaction.Rollback () не удаляет вашу сущность из кеша сеанса, вместо этого используйте session.Evict ().

См .: - https://www.hibernate.org/hib_docs/nhibernate/html/performance.html#performance-sessioncache

person SHSE    schedule 26.09.2009