nhibernate强制单独查询而不是连接

时间:2017-12-11 09:38:13

标签: c# sql nhibernate linq-to-nhibernate

我是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");

子类配置为similiary;

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....

谢谢。

2 个答案:

答案 0 :(得分:0)

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

<强> 1。多个查询的延迟加载:

这将生成多个查询。延迟加载时,NHibernate首先生成查询以获取所有MotherCollection数据,并仅从任何相关表中获取ID(无数据)。然后它在依赖表上为WHERE生成带有Primary Key子句的新查询。因此,这导致着名的N + 1问题。

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

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

这是NHibernate推荐的。

<强> 2。单个复杂查询的急切加载:

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

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

通过这种方式,将自动填充引用的集合(如果数据库中存在数据)。

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

  

相反,我们保留默认行为,并为a覆盖它   特定交易,使用left join fetch中的HQL。这说明   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://stackoverflow.com/a/30748639/5779732

答案 1 :(得分:0)

您应该发出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个查询。这样当你访问它们时,它们都将被初始化。如果您使用常规延迟加载,您将一次为一个对象初始化一个集合。