Queryover dynamic fetch with joins

点点圈 提交于 2019-12-10 10:37:28

问题


iam trying a new query with nhibernate and find a new problem :(

take this as model:

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;
}

i want A object that have a particular B object and dinamically eager fetch Cs or Ds collection of selected B:

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

i start with

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

but eager load is not applyed: if i test this:

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

i see many query executed and Cs and Ds throw lazy inizializazion error

i found this and read that eager have problem without leftJoin so switch first part to this:

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

but have same problem

looking at similar fetch done in other case seem that possible cause can be a.Bs.First().Ds as parameter selection for fetch

EDIT: just to clarify:

this works:

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

while this no:

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

called in this way: Read(12, a => a.Bs, a.Bs.First().Cs, a.Bs.First().Ds)


回答1:


Looking at your actual trouble, eager loading, I do not see why you put future in it that way. Your current code should logically be wrong: it issues a query with your filtering criteria then a bunch of "load all A entities with an eager loaded property" queries...

If your fetched properties are not collections (or only one is a collection), you should write it that way:

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

This is a single query, immediately executed after being created: if you do not have other Future query awaiting execution, there is no point calling it with Future.

If you have many collections to eager load, this would result in a Cartesian product. To avoid it you then need to split them in many queries. There Future could be useful. (But once again as said in your question comments, lazy-loading fare better for this: no need to bloat the loading logic with eager loading considerations, just setup batching size in mappings, and ensure you are done using your entities before closing the session.)

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

Please note that with NHibernate 5 and above, if your data provider does not actually support future (multiple queries in a single SQL command), futures which have not been explicitly executed will be discarded without being executed. (Previous versions were executing future queries immediately at the Future call with data providers not actually supporting them.)

For having them executed even with data provider not supporting futures, change the last line to:

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


来源:https://stackoverflow.com/questions/42929449/queryover-dynamic-fetch-with-joins

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!