【问题标题】:Performing a query inside NHibernate entities在 NHibernate 实体中执行查询
【发布时间】:2014-10-20 06:49:52
【问题描述】:

我有一个 PinballMachines 存储库,它返回一个水合的 PinballMachine 实体。它有一个私有属性,即在那台机器上玩的游戏列表。

弹球机可能会记录数以百万计的游戏。来自PinballMachine,我想拿高分来展示,这是前10名的游戏玩家。

public class PinballMachine
{
    private IList<Game> _games = new List<Game>();  

    public virtual int ID { get; protected set; }

    public virtual IEnumerable<Game> GetTop10Games()
    {
        return _games
            .AsQueryable()
            .OrderByDescending(g => g.Score)
            .Take(10)
            .ToList();
    }
}

public class Game
{
    public virtual Guid ID { get; protected set; }
    public virtual string Name { get; set; }
    public virtual int Score { get; set; }
    public virtual decimal AmountPaid { get; set; }
}

PinballMachine_games 属性映射为Bag

Bag<Game>("_games", m =>
{
    m.Key(k => k.Column("PinballMachineID"));
    m.Access(Accessor.Field);
    m.Cascade(Cascade.All);
}, r => r.OneToMany()); 

以下代码运行正常,然而,NHibernate 在游戏表上执行一个非常简单的谓词,并在内存中执行排序和过滤。

-- SLOW! 1,000,000 records

SELECT ...
FROM Games
WHERE PinballMachineID = 123

这是非常不理想的,因为当我只需要 10 条记录时,数据库正在传输数百万条记录。

理想情况下,我希望 NHibernate 生成如下所示的查询:

-- FAST! 10 records

SELECT TOP 10 ...
FROM Games
WHERE PinballMachineID = 123
ORDER BY Score DESC

是否可以配置我的映射,以便我可以对水合对象执行额外的查询(在数据库上)。

我知道我可以使用 NHibernate 会话来执行 linq 查询,但是我希望这个逻辑成为我实体的一部分

【问题讨论】:

标签: c# sql .net linq nhibernate


【解决方案1】:

不幸的是,NHibernate 不支持这一点。

当您为 PinballMachine 创建映射时,您在 ID 列上定义了一对多关系,它会(懒惰地或急切地)获取所有匹配的 Game 实体。

我建议的一件事是GetTop10Games 看起来应该属于存储库类,而不是实体的成员。这就是使用存储库模式背后的原因之一——它封装了所有数据访问逻辑,并且反过来甚至允许您在真正需要时编写特定的高性能查询,每隔一段时间。这(不幸与否)是大多数 ORM 框架的问题。您永远不知道某个 LINQ 提供程序何时会表现不佳,甚至根本无法转换为 SQL,因此您希望保留您的选择。

我当然会让这个方法成为IGameRepositoryIPinballMachineRepository 的成员,并像这样实现它:

public IList<Games> GetTopGamesForMachine(PinballMachine machine, int maxItems)
{
     return Session
         .Query<Games>()
         .Where(g => g.PinballMachine == machine)
         .OrderByDescending(g => g.Score)
         .Take(maxItems)
         .ToList();
}

【讨论】:

  • 我认为你是对的,我正在尝试通过让我的实体在其中有一些行为来执行 DDD 方法,我认为我目前采取的一种不好的方法是我的域实体是NHibernate 返回的相同实体。
  • 是的,即使使用 ORM,也不能完全忽略数据层及其机制的存在。您仍然需要打开和关闭会话(就像在 ADO.NET 中所做的那样),确保它们打开足够长的时间(在会话关闭后访问惰性属性将失败),注意瞬态实体和并发性因此,虽然您可以抽象事物的实际持久化方式,或者具有与域模型完全不同的数据库模式,甚至抽象您正在使用的 ORM,但根据我的经验,您永远不应该让这种泄漏您的实体。
  • 您是否建议将域实体和 NHibernate 实体完全分开?虽然这超出了原始问题的范围,但我还没有一个可以解决我的问题的架构的明确示例。
  • @Matthew:很难说,但我不会让领域驱动的方法强迫你做出糟糕的决定,只是为了它。 PinballMachine 是否真的需要收集所有游戏(如果有很多游戏)?可能不会,您不会从中获得任何真正的好处。请参阅this article:有时实体是组织单位,而不是容器。
  • this MSDN article,作者 Julie Lerman,即“数据访问发生在存储库中,而不是聚合根”部分,她在其中展示了一种这样的存储库方法。我个人在进行设计时并没有完全忘记持久性,并且通常会尝试根据我的领域问题的需要组织我的存储库,但仍然保持域模型之外的数据访问。但是,您可以尝试提出不同的问题,并可能从具有不同方法和经验的人那里得到更好的答案。
【解决方案2】:

似乎从 NHibernate 5 开始,接受的答案“不幸的是,NHibernate 不支持这一点。”不再正确:

从 NHibernate 5.0 开始,查询也可以从实体集合创建,标准 Linq 扩展 AsQueryable 可从 System.Linq 命名空间获得。

IList<Cat> whiteKittens =
    cat.Kittens.AsQueryable()
        .Where(k => k.Color == "white")
        .ToList();

https://nhibernate.info/doc/nhibernate-reference/querylinq.html

无法明确谈论 2019 年与 2014 年的其余答案和 cmets,关于 NHibernate ORM 的持久性无知的实用性,尽管我正在努力真的让它发挥作用尽可能...我们会看到的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多