Как лучше всего получить и обновить эти объекты в NHibernate? - PullRequest
0 голосов
/ 12 ноября 2009

Ранее я задавал вопрос относительно моделирования ситуации с Users, Items и UserRatings. В моем примере UserRatings связаны с одним пользователем и одним Item. Хороший ответ дал Натан Фишер, и я включил модель, которую он предложил ниже.

Но теперь у меня есть вопрос относительно поиска этих объектов .

Модель связывает сущности, удерживая ссылки на сущности. Мой вопрос таков: как лучше всего получить конкретный UserRating для обновления? В этой ситуации У меня будет идентификатор пользователя (из сеанса аутентификации asp.net), и itemID (из URL). Кроме того, может быть 1000 оценок на пользователя или элемент.

В прежней школе это было бы так же просто, как один запрос на обновление, где x = userID и y = itemID. Легко. Однако лучший способ добиться этого в NHibernate с использованием правильной объектной модели не так ясен.

A) Я понимаю, что мог бы создать метод репозитория GetRatingByUserAndItem и передать ему как объект User, так и объект Item, для чего он будет выполнять запрос HQL / критерии для получения объекта Rating . Однако для этого я предполагаю, что сначала мне нужно будет извлечь пользователя и элемент из ORM, а затем передать их обратно в ORM в запросе. Затем я получу объект UserRating, обновлю его, а затем ORM сохранит изменения. Это кажется мне смехотворно неэффективным по сравнению с методом старой школы.

B) Может быть, я мог бы просто обновить объект UserRating и выполнить тип createorupdate, вызвав ORM (не уверен в точном синтаксисе). Это было бы лучше, но по-видимому, мне все равно нужно было бы сначала получить пользователя и элемент, который все еще довольно неэффективен.

C) Возможно, мне следует просто извлечь пользователя (или элемент) из ORM и найти правильный UserRating из его коллекции UserRatings. Однако, если я это сделаю, как мне убедиться, что я не получаю все UserRatings, относящиеся к этому пользователю (или элементу), а только тот, который относится к конкретному элементу и конкретному пользователю?

D) Мне пришло в голову, что я могу просто отбросить полномасштабные ссылки на User и Item из UserRating в модели и вместо этого иметь примитивные ссылки (UserID и ItemID). Это позволило бы мне сделать что-то столь же простое, как метод oldschool. Очень заманчиво, но мне это не кажется правильным - не очень объектно-ориентированным (и, конечно, это главная причина, по которой мы используем ORM в первую очередь!)

Итак, кто-нибудь может дать совет мудреца? Я на правильном пути с любым из вариантов выше? Или есть лучший способ, который я не рассмотрел?

Спасибо заранее за вашу помощь! :)

UPDATE:

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

  • Получить все элементы, которые пользователь НЕ оценил.
  • Извлечение предмета (ов) и рейтинга (ов) предметов, которые пользователь оценил самым низким.

Модель следует ниже:

public class User 
{
    public virtual int UserId { get; set; }
    public virtual string UserName { get; set; }
    public virtual IList<UserRating> Ratings { get; set; }
}

public class Item 
{
    public virtual int ItemId { get; set; }
    public virtual string ItemName { get; set; }
    public virtual IList<UserRating> Ratings { get; set; }
}
public class UserRating 
{
    public virtual User User { get; set; }
    public virtual Item Item { get; set; }
    public virtual Int32 Rating { get; set; }
}

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Test" namespace="Test" >
    <class name="User">
        <id name="UserId" >
                <generator class="native" />
        </id>
        <property name="UserName" />
        <bag name="Ratings" generic="true" inverse="true" table="UserRating">
                <key column="UserId" />
                <one-to-many class="UserRating"/>
        </bag>
    </class>
    <class name="Item" >
        <id name="ItemId" >
                <generator class="native" />
        </id>
        <property name="ItemName" />
        <bag name="Ratings" generic="true" inverse="true" table="UserRating">
                <key column="ItemId" />
                <one-to-many class="UserRating"/>
        </bag>
    </class>
    <class name="UserRating" >
        <composite-id>
                <key-many-to-one class="User" column="UserId" name="User" />
                <key-many-to-one class="Item" column="ItemId" name="Item" />
        </composite-id>
        <property name="Rating" />
    </class>
</hibernate-mapping>

Ответы [ 6 ]

1 голос
/ 17 ноября 2009
  1. Запрос к базе данных (используя HQL, Критерии, SQL-запрос и т. Д.) Для пользовательского рейтинга, который вы хотите изменить
  2. Измените UserRating, как вам нравится
  3. Передайте ваши изменения

В псевдокоде это будет выглядеть примерно так:

using (ITransaction transaction = session.BeginTransaction())
{
    UserRating userRating = userRatingRepository.GetUserRating(userId, itemId);
    userRating.Rating = 5;
    transaction.Commit();
}

Это будет включать два запроса (в отличие от одного запроса «старой школы»). Первый запрос (который происходит в вызове GetUserRating) запускает SQL «SELECT», чтобы получить UserRating из базы данных. Второй запрос (который происходит в транзакции.Commit) обновит рейтинг пользователя в базе данных.

GetUserRating (с использованием критериев) будет выглядеть примерно так:

public IList<UserRating> GetUserRating(int userId, int itemId)
{
    session.CreateCriteria(typeof (UserRating))
        .Add(Expression.Eq("UserId", userId))
        .Add(Expression.Eq("ItemId", itemId))
        .List<UserRating>();
}
1 голос
/ 17 ноября 2009
public void UpdateRating( int userId, int itemId, int newRating, ISession session )
{
  using( var tx = session.BeginTransaction())
  {
    var ratingCriteria = session.CreateCriteria<UserRating>()
      .CreateAlias( "Item" "item" )
      .CreateAlias( "User" "user" )
      .Add( Restrictions.Eq( "item.ItemId", itemId ) )
      .Add( Restrictions.Eq( "user.UserId", userId ) );
    var userRating = ratingCriteria.UniqueResult<UserRating>();
    userRating.Rating = newRating;
    tx.Commit();
  }
}

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

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

В зависимости от версии NHibernate, которую вы используете, вы также можете запросить, используя NHLinq или NHLambda, которые были интегрированы и теперь доступны через session.QueryOver<T>.

Чтобы получить список всех элементов, которые пользователь не оценил, вам нужно будет использовать подзапрос, чтобы определить все элементы, которые пользователь оценил, а затем применить условие not in ко всем элементам (по сути, получить мне все предметы, не вошедшие в предметы, которые пользователь оценил).

var ratedItemsCriteria = DetachedCriteria.For<UserRating>()
    .CreateAlias( "Item" "item" )
    .SetProjection( Projections.Property( "item.ItemId" ) )
      .CreateCriteria( "User" )
        .Add( Restrictions.Eq( "UserId", userId ) );
var unratedItemsCriteria = session.CreateCriteria<Item>()
  .Add( Subqueries.PropertyNotIn( "ItemId", ratedItemsCriteria ) );
var unratedItems - unratedItemsCriteria.List<Item>();

В целом, я думаю, что большинство ваших проблем можно решить с помощью разумного применения google, nhforge и списка рассылки nhibernate.

1 голос
/ 13 ноября 2009

При использовании HQL ваш запрос будет выглядеть так

Select From UserRating
Where ItemId=: @ItemId
and UserId=: @UserId

Это даст вам объект UserRating, после которого вы сможете обновиться и сохранить его при необходимости.

И альтернативой будет

Session.CreateCriteria(typeof(ClassLibrary1.UserRating))
                    .Add(Expression.Sql(String.Format("ItemId={0}",UserId)))
                    .Add(Expression.Sql(String.Format("UserId={0}",ItemId)))
                    .List<ClassLibrary1.UserRating>();

Это был самый простой способ заставить это работать. Я не доволен встроенными строками, но он работает.

1 голос
/ 12 ноября 2009

Я бы выбрал вариант С. Ваши опасения по поводу производительности указывают на то, что вы можете оптимизировать преждевременно. Я думаю, что было бы хорошо, если бы у вас был метод GetUser (int userId) , затем найдите соответствующий элемент в своей коллекции рейтингов и обновите его.

Это, однако, поднимает общую проблему, которой страдают ORM, называемой проблемой N + 1 SELECT. Если посмотреть на каждый UserRating, чтобы найти подходящий, скорее всего, будет один оператор SELECT на UserRating. Есть несколько способов решить эту проблему. Одним из них является изменение файла сопоставления, чтобы либо отключить отложенную загрузку коллекции Ratings, либо загрузить ее с помощью выборки 'join' - см. этот раздел документации NHibernate.

1 голос
/ 12 ноября 2009

Обычно, когда вы используете ORM, вы хотите реализовать бизнес-логику (скажем, изменение данных), ориентированную на объект. Это требует загрузки объектов из базы данных. NH позволяет вам загрузить их один раз и изменить, не ссылаясь на какие-либо вещи, связанные с базой данных, а просто меняя значения свойств.

Тем не менее, это не всегда так просто. Иногда существуют причины производительности, которые требуют других способов обновления данных.

Вы можете использовать Обновления HQL или даже обновления SQL.

Другой, более классический способ сделать это - загрузить только UserRatings. Это требует, чтобы сделать его независимым объектом (ему нужен идентификатор, в любом случае избегайте составного идентификатора, заменяйте его ссылками "многие-к-одному"). Затем вы фильтруете UserRatings по пользователю и элементу, загружаете элементы, которые хотите изменить, в базу данных и изменяете их с помощью объектно-ориентированного программирования.

Это всегда компромисс между производительностью и объектно-ориентированным программированием. Вы должны попытаться сделать это как можно более ОО и выполнять оптимизацию только в случае необходимости. Ремонтопригодность важна.

Я бы не стал переносить внешние ключи в модель домена.

0 голосов
/ 23 ноября 2009

Я вижу, что этот вопрос не был помечен как ответивший, поэтому я попробую. На мой взгляд, вы должны много смотреть на то, как используются объекты. Мне кажется, что вы бы связывали UserRating больше с Item, чем с пользователем, просто потому, что отображали бы его рядом с элементом в пользовательском интерфейсе. Не всегда важно показывать это пользователю.

Поэтому я бы удалил список оценок у пользователя:

public class User 
{
    public virtual int UserId { get; set; }
    public virtual string UserName { get; set; }
}

Если вы хотите получить оценки для пользователя, вы получите это через репозиторий.

Я бы оставил класс Item без изменений, так как вы всегда хотите видеть оценки с элементом:

public class Item 
{
    public virtual int ItemId { get; set; }
    public virtual string ItemName { get; set; }
    public virtual IList<UserRating> Ratings { get; set; }
}

Класс UserRating может быть полностью отключен от классов Item и User. Просто держите там идентификаторы, чтобы вы могли получить Предмет или Пользователя из хранилища, если вам нужно:

public class UserRating 
{
    public virtual int UserId { get; set; }
    public virtual int ItemId { get; set; }
    public virtual Int32 Rating { get; set; }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...