nhibernate 强制分离查询而不是加入

Posted

技术标签:

【中文标题】nhibernate 强制分离查询而不是加入【英文标题】:nhibernate force separate queries instead of join 【发布时间】:2018-05-24 19:11:27 【问题描述】:

我是 nhibernate 的新手,我无法弄清楚这一点。 我有一个类似于下级的实体;

public class MotherCollection

    public virtual int Id  get; set; 
    public virtual string Name  get; set; 

    public virtual ISet<Class1> Collection1  get; set; 
    public virtual ISet<Class2> Collection2  get; set; 
    public virtual ISet<Class3> Collection3  get; set; 
    public virtual ISet<Class4> Collection4  get; set; 

与其他实体有许多一对多的关系。 我使用以下映射配置此关系;

HasMany(d => d.Collection1).KeyColumn("McId");
HasMany(d => d.Collection2).KeyColumn("McId");
HasMany(d => d.Collection3).KeyColumn("McId");
HasMany(d => d.Collection4).KeyColumn("McId");

子类配置类似;

References(c1=>c1.MotherCollection).Column("McId");

等等。

当我从 db 查询这个实体,获取所有关系时,我得到一个类似于这个的巨大查询:

SELECT * FROM MotherCollection mc 
JOIN c1 on mc.Id=c1.mcId
JOIN c2 on mc.Id=c2.mcId
JOIN c3 on mc.Id=c3.mcId
JOIN c4 on mc.Id=c4.mcId

这个查询会导致很多重复的行并且需要很长时间来执行。

我希望 nhibernate 以某种方式将此查询分离为单个 SELECT 查询,如下所示

SELECT * FROM MotherCollection Where Id = @Id
SELECT * FROM c1 Where mcId = @Id

等等。有点类似于延迟加载集合时发生的情况。 我设法通过将我想要的集合设置为惰性集合并在它退出我的数据层之前访问它们的 First() 属性来实现这种行为。不过,我猜想在 Nhibernate 中一定有更优雅的方式来做到这一点。

我尝试过类似这样的查询:

var data = session.QueryOver<DataSet>().Fetch(d=>d.Collection1).Eager.Fetch(d=>d.Collection2).Eager....

谢谢。

【问题讨论】:

【参考方案1】:

您应该发出 4 个单独的查询,每个查询获取一个集合。 你应该使用session.Query。 QueryOver 是一种较旧的方法。要使用它,请添加using NHibernate.Linq。我通常使用以下扩展方法来预取集合:

static public void Prefetch<T>(this IQueryable<T> query)

    // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
    query.AsEnumerable().FirstOrDefault();

然后使用:

var data = session.Query<DataSet>().Fetch(d=>d.Collection1).ToList();
session.Query<DataSet>().Fetch(d=>d.Collection2).Prefetch();
session.Query<DataSet>().Fetch(d=>d.Collection3).Prefetch();
session.Query<DataSet>().Fetch(d=>d.Collection4).Prefetch();

确保在访问集合之前运行 4 个查询。这样,当您访问它们时,它们都将被初始化。如果您使用常规延迟加载,您将一次为一个对象初始化一个集合。

【讨论】:

这实际上是我试图避免做的事情。访问集合只是为了初始化其延迟加载似乎是一种 hack。必须有更优雅的方式来做到这一点 除了 QueryOver 是一种较旧的方式之外,这与在 OP 已经在执行的每个集合上调用 First() 有何不同/更好? @AmitJoshi 不清楚 OP 在哪里调用 First()。如果它在集合上,这将只初始化一个对象的集合。我在获取集合的查询中调用它,这将为每个对象初始化集合。另一个区别是调用AsEnumerable(),它可以防止查询被写成select top 1... 另一个小区别是如果集合恰好为空First() 将产生一个异常。 @Rudithus 就像我在之前的评论中所说的那样,我不会访问集合来触发延迟加载。我正在运行一个查询,该查询为所有对象获取该集合。我曾经使用的另一个选项是使用Fetch.Subselect()BatchSize(x) 进行映射,但我更喜欢这个解决方案。它可能看起来不那么优雅,但它可以让您完全控制正在发生的事情。【参考方案2】:

这称为延迟/急切加载。您有两种选择:

1.多个查询的延迟加载:

这将生成多个查询。在延迟加载时,NHibernate 首先生成查询以从任何依赖表中获取所有 MotherCollection 数据和唯一的 ID(无数据)。然后它为依赖表上的Primary Key 生成带有WHERE 子句的新查询。所以,这就引出了著名的 N+1 问题。

这样,默认情况下不会填充引用的集合。当您第一次访问它们时,这些将被填满,而 ISession 仍然有效。这类似于您在问题中提到的调用First()

查看您的HasMany 配置;你没有提到LazyLoad,但它是默认的。因此,根据您当前的映射,这就是正在发生的事情。

这是 NHibernate 推荐的。

2。单个复杂查询的急切负载:

如果您想避免多次查询并一次性检索所有数据,请尝试以下操作:

HasMany(d => d.Collection1).KeyColumn("McId").Inverse().Not.LazyLoad().Fetch.Join();

这样,引用的集合将被自动填充(如果数据库中存在数据)。

请注意,这违反了 NHibernate 的建议。参考this链接。

相反,我们保留默认行为,并在一段时间内覆盖它 特定事务,在HQL 中使用left join fetch。这告诉 NHibernate 在第一个选择中急切地获取关联,使用 外连接。在 ICriteria 查询 API 中,您可以使用 SetFetchMode(FetchMode.Join).

如果您希望更改抓取策略 由Get()Load() 使用,只需使用ICriteria 查询,对于 示例:

User user = (User) session.CreateCriteria<User>()
                .SetFetchMode("Permissions", FetchMode.Join)
                .Add( Expression.Eq("Id", userId) )
                .UniqueResult();

避免 N+1 选择问题的一种完全不同的方法是 使用二级缓存。

重复行和性能

这实际上是一个不同的问题。有多种方法可以处理这个问题;但它需要您提供额外的输入。在此之前,您应该从以上两个选项中选择一个。 因此值得提出一个新问题。

参考这个答案:https://***.com/a/30748639/5779732

【讨论】:

以上是关于nhibernate 强制分离查询而不是加入的主要内容,如果未能解决你的问题,请参考以下文章

如何配置 Fluent NHibernate 以将查询输出到 Trace 或 Debug 而不是 Console?

在不加入的情况下运行 NHibernate 查询

NHibernate查询问题

如何使用 DISTINCT 在 NHibernate SQL 查询中进行分页

使用 WCF/OData 作为访问层而不是直接使用 EF/L2S/nHibernate 的论点

在 nhibernate 中使用参数值打印查询字符串?