使用连接查询动态提取

时间:2017-03-21 14:05:59

标签: c# nhibernate fetch future queryover

我正在尝试使用nhibernate的新查询并找到一个新问题:(

以此为模型:

public class D { int id; }
public class C { int id; }
public class B {
    int id;
    ICollection<C> Cs;
    ICollection<D> Ds;
}
public class A {
    int id;
    ICollection<B> Bs;
}

我想要一个具有特定B对象的对象,并且非常渴望获取所选B的Cs或Ds集合:

public virtual A Read(int idB, params Expression<Func<Attivita, object>>[] eagerFields)

我从

开始
IEnumerable<A> query = _session.QueryOver<A>()
                            .JoinQueryOver(a => a.Bs)
                            .Where(b => b.Id == idB)
                            .Future<A>();

foreach (Expression<Func<A>, object>> field in eagerFields)
    _session.QueryOver<A>()
        .Fetch(field).Eager
        .Future<A>();

return query.First();   

但是没有应用急切的负载:如果我测试了这个:

Read(12, a => a.Bs, a.Bs.First().Cs, a.Bs.First().Ds)

我看到执行了很多查询而CsDs抛出了懒惰的错误判断错误

我发现this并且在没有leftJoin的情况下读到了急切的问题所以请将第一部分切换到此:

B BB= null;
IEnumerable<A> query =_session.QueryOver<A>()
        .Fetch(a => a.Bs).Eager
        .Left.JoinAlias(a => a.Bs, () => BB)
        .Where(() => BB.Id == idB)
        .Future<A>();

但有同样的问题

查看在其他情况下完成的类似提取似乎可能导致a.Bs.First().Ds作为fetch的参数选择

编辑: 只是为了澄清:

这有效:

IEnumerable<A> query = _session.QueryOver<A>()
.Left.JoinAlias(a => a.Bs, () => BB)
.Where(() => BB.Id == IdB)
.Fetch(a => a.Bs).Eager
.Fetch(a => a.Bs.First().Cs).Eager
.Future<A>();
return query.First();

虽然没有:

IEnumerable<A> query = _session.QueryOver<A>()
                        .JoinQueryOver(a => a.Bs)
                        .Where(b => b.Id == idB)
                        .Future<A>();

foreach (Expression<Func<A>, object>> field in eagerFields)
    _session.QueryOver<A>()
        .Fetch(field).Eager
        .Future<A>();

return query.First();   

以这种方式调用:Read(12, a => a.Bs, a.Bs.First().Cs, a.Bs.First().Ds)

1 个答案:

答案 0 :(得分:1)

看着你的实际问题,急切的加载,我不明白为什么你把这种方式放在其中。您当前的代码在逻辑上应该是错误的:它会使用您的过滤条件发出一个查询,然后一堆“加载所有具有急切加载属性的实体”查询......

如果您获取的属性不是集合(或者只有一个是集合),那么您应该这样写:

IQueryOver<A, A> query = _session.QueryOver<A>()
    .JoinQueryOver(a => a.Bs)
    .Where(b => b.Id == idB);

foreach (Expression<Func<A>, object>> field in eagerFields)
    query = query
        .Fetch(field).Eager;

return query.List().First();

这是一个单独的查询,在创建后立即执行:如果您没有等待执行的其他Future查询,则无法用Future调用它。

如果您有很多集合需要加载,这将产生笛卡尔积。为了避免它,您需要在许多查询中拆分它们。 Future可能有用。 (但是再一次如你的问题评论中所述,延迟加载fare better for this:不需要用加载的考虑因素来填充加载逻辑,只需在映射中设置批处理大小,并确保在关闭之前完成使用实体会话。)

var queryBase = _session.QueryOver<A>()
    .JoinQueryOver(a => a.Bs)
    .Where(b => b.Id == idB);

var queries = new List<IEnumerable<A>>();
foreach (Expression<Func<A>, object>> field in eagerFields)
    queries.Add(queryBase
        .Fetch(field).Eager
        .Future());

return queries.Count == 0 ? queryBase.List().First() : queries[0].First();

请注意,对于NHibernate 5及更高版本,如果您的数据提供者实际上不支持将来(单个SQL命令中的多个查询),则未被执行的未来将被丢弃。 (以前的版本在Future调用时立即执行将来的查询,数据提供者实际上并不支持它们。)

即使数据提供者不支持期货也要执行它们,将最后一行更改为:

if (queries.Count == 0)
    return queryBase.List().First();

List<A> result;
foreach (var q in queries)
{
    // Using the IFutureEnumerable directly as an IEnumerable is deprecated.
    result = q.GetEnumerable()
        // Due to a bug, GetEnumerable is not yet enough to trigger execution.
        .ToList();
}

return result.First();