【问题标题】:How best to retrieve and update these objects in NHibernate?如何最好地在 NHibernate 中检索和更新这些对象?
【发布时间】:2009-11-12 11:56:22
【问题描述】:

我之前曾向a question 询问过关于用户、项目和用户评级的情况建模。在我的示例中,用户评级与一个用户和一个项目相关联。 A Nathan Fisher 提供了很好的答案,我在下面提供了他建议的模型。

但我现在有一个关于检索这些对象的问题。

模型通过保存对实体的引用来链接实体。我的问题是,如何才能最好地检索要更新的特定 UserRating? 在这种情况下我将拥有用户 ID(来自 asp.net 身份验证会话)、和 itemID(来自 URL)。此外,每个用户或项目可能有 1000 多个评分。

回到老式学校,这就像一个更新查询'其中 x = userID 和 y=itemID 一样简单。简单的。然而,在 NHibernate 中使用适当的对象模型来完成此任务的最佳方法还不是很清楚。

A) 我知道我可以创建一个存储库方法 GetRatingByUserAndItem,并将用户和项目对象同时传递给它,它会对其执行 HQL/标准查询以检索评级 但是要做到这一点,我假设我首先需要从 ORM 中检索用户和项目,然后再将它们传递回查询中的 ORM。然后我会获取 UserRating 对象,更新它,然后让 ORM 持久化更改。与旧学校的方法相比,这对我来说似乎效率低得离谱。

B) 也许我可以新建 UserRating 对象,然后创建或更新类型调用 ORM(不确定确切的语法)。这样会更好,但大概我仍然需要先检索用户和项目,这仍然非常低效。

C) 也许我应该从 ORM 中检索用户(或项目),并从其 UserRatings 集合中找到正确的 UserRating。 但是,如果我这样做了那,我如何确保我没有检索到与该用户(或项目)相关的所有 UserRatings,而只是与特定项目和特定用户相关的一个?

D) 我突然想到,我可以从模型中的 UserRating 中删除对 User 和 Item 的完整引用,而使用原始引用(UserID 和 ItemID)。 这将允许我做一些像老式方法一样简单的事情。非常诱人,但这对我来说似乎不太合适——不是非常面向对象(这肯定是我们首先使用 ORM 的主要原因!)

那么,任何人都可以提供一些明智的建议吗?我是否在正确的轨道上使用上述任何选项?或者有没有更好的方法我没有考虑过?

提前感谢您的帮助! :)

更新:

我刚刚为此发布了赏金,并且更好地理解了这一点,我还想知道,使用类似的方法,如何最好地执行以下查询

  • 检索用户未评分的所有项目。
  • 检索用户评分最低的项目和项目评分。

模型如下:

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>

【问题讨论】:

    标签: nhibernate hibernate orm


    【解决方案1】:

    通常,当您使用 ORM 时,您希望实现面向对象的业务逻辑(例如:更改数据)。这需要从数据库中加载对象。 NH 允许您加载它们一次,并在不参考任何数据库相关内容的情况下更改它,而只是更改属性值。

    这就是说,它并不总是那么容易。有时出于性能原因需要其他方式来更新数据。

    您可以使用 HQL updates 甚至 SQL 更新。

    另一种更经典的方法是只加载UserRatings。这需要使它成为一个独立的实体(它需要一个 id,无论如何都要避免复合 id,用多对一的引用来复制它)。然后按用户和项目过滤UserRatings,将要更改的项目加载到数据库中,并使用面向对象编程进行更改。

    它总是在性能和​​面向对象编程之间进行权衡。你应该尽量做到面向对象,只有在需要的时候才做优化。可维护性很重要。

    我会避免将外键移动到域模型。

    【讨论】:

    • 谢谢。那么我将如何加载 UserRatings 并按用户和项目过滤呢?我不熟悉过滤器,您是指 NHibernate 文件管理器,还是只是使用例如过滤结果集林克?
    • 只是为了添加到我上面的评论中 - 我不想从数据库中获取多条记录,然后以编程方式选择我想要的一条。
    • 但我想,在那种情况下,我首先需要加载用户并加载项目,对吧?
    • 不,您可以直接查询 UserRatings:from UserRatings where User.id = :userId and Item.id = :itemId 或类似的东西。延迟加载将阻止加载 UserRating 时加载用户和项目。此示例假定 User 和 Item 在 UserRating 类中是多对一的引用。
    【解决方案2】:

    我会选择选项 C。您对性能的担忧表明您可能过早地进行了优化。我认为您最好有一个 GetUser(int userId) 方法,然后在其 Ratings 集合中查找适当的项目并更新它。

    然而,这确实带来了 ORM 遭受的一个常见问题,称为 N+1 SELECT 问题。查看每个 UserRating 以找到合适的可能会导致每个 UserRating 有一个 SELECT 语句。有几种方法可以解决这个问题。一种是更改映射文件以禁用 Ratings 集合的延迟加载,或者使用“加入”获取加载它 - 请参阅 NHibernate 文档的this section

    【讨论】:

    • 感谢 Serilla。我不喜欢过早地优化——但我认为将数据库查询的数量保持在最低限度非常重要。问题是,可能有 1000 多个用户评分与用户相关联 - 因此,每次我遍历集合以找到正确的评分时都不能进行查询。不过,连接获取听起来很有趣 - 我会检查一下。
    • +1 表示“过早优化”。我实际上试图在我的回答中表达同样的意思,我只是不确定如果每个用户有数百甚至数千个用户评分,这是否不是一个严重的性能问题。
    • Stefan,你没有看到有 100 次查询来获取一个我可以在没有 ORM 的情况下只用 1 更新的对象的问题吗?
    • @Sosh:为什么你会收到 100 个查询?在更新对象之前,您只需要一个选择来将对象放入内存。与面向对象的业务逻辑相比,这有点开销。
    【解决方案3】:

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

    这是我可以让它工作的最简单的方法。我对嵌入的字符串不满意,但它可以工作。

    【讨论】:

    • 如果 Item 和 User 是多对一引用,则不需要Expression.Sql,也不需要string.Format。只需Expression.IdEq,非常直接。
    • 我仍处于 ICriteria 界面的早期学习阶段。我玩过Expression.IdEq,但它一直在创建无效的 SQL。继续在 where 子句中将 UserId 添加为 @y_0。我最终得到了我知道有效的上述结果。我同意不是很优雅。
    【解决方案4】:
    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 或已集成且现在可通过 session.QueryOver&lt;T&gt; 访问的 NHLambda 内容进行查询。

    要检索用户未评分的所有项目的列表,您将需要使用子查询来识别用户已评分的所有项目,然后将 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 用户邮件列表来解决。

    【讨论】:

      【解决方案5】:
      1. 查询数据库(使用 HQL、Criteria、SQL 查询等)以获取您要修改的 UserRating
      2. 随意更改 UserRating
      3. 提交您的更改

      在伪代码中,这看起来像这样:

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

      这将涉及两个查询(与一个查询“老派”解决方案相反)。第一个查询(发生在 GetUserRating 调用中)将运行 SQL“SELECT”以从数据库中获取 UserRating。第二个查询(发生在 transaction.Commit 上)将更新数据库中的 UserRating。

      GetUserRating(使用 Criteria)看起来像这样:

      public IList<UserRating> GetUserRating(int userId, int itemId)
      {
          session.CreateCriteria(typeof (UserRating))
              .Add(Expression.Eq("UserId", userId))
              .Add(Expression.Eq("ItemId", itemId))
              .List<UserRating>();
      }
      

      【讨论】:

        【解决方案6】:

        我看到这个问题没有被标记为已回答,所以我会试一试。在我看来,您必须大量了解对象的使用方式。在我看来,您将 UserRating 更多地与项目相关联,而不是与用户相关,仅仅是因为您将在 UI 中的项目旁边显示它。始终为用户显示它并不重要。

        这就是为什么我要删除用户的评分列表:

        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 类完全断开。只需将 ID 保留在其中,以便在需要时从存储库中检索项目或用户:

        public class UserRating 
        {
            public virtual int UserId { get; set; }
            public virtual int ItemId { get; set; }
            public virtual Int32 Rating { get; set; }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-11-11
          • 2010-09-30
          • 2020-08-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-13
          • 1970-01-01
          相关资源
          最近更新 更多