I'm trying to eager load roles in many to many collection off of my User object.
Role role = null;
IQueryOver<User, User> query = session.QueryOver<User>()
.Fetch( p => p.Roles).Eager
.JoinAlias( q => q.Roles, () => role)
.Where(() => role.Active == true);
leaves me with user objects that have uninitialized roles members. If I remove the joinalias, they are initialized just fine. Is this just an NH3 bug or am I doing something wrong?
Another way to make eager load is to set LeftOuterJoin. It helped to us in a similar scenario
Role role = null;
IQueryOver<User, User> query = session.QueryOver<User>().Fetch( p => p.Roles).Eager
.JoinAlias( q => q.Roles, () => role, JoinType.LeftOuterJoin)
.Where(() => role.Active == true);
That's the expected behavior. If you use JoinAlias, you'll be filtering the collection elements, so it can't be initialized.
You need to use a subquery for filtering if you intend to use eager loading. on the same collection.
Related
I am using NHibernate 3.0 and was comparing Query and QueryOver
var p = _prepo.Query<Party>()
.Where(c => c.Person.LastName == "Bobby")
.FirstOrDefault();
The above works, I get proxy class for p.Person if I view the object graph.
var p = _prepo.QueryOver<Party>()
.Where(c => c.Person.LastName == "Bobby")
.FirstOrDefault();
This one fails with error ==> could not resolve property: Person.LastName of:
Why?
I'm not familiar with the Linq provider but when using QueryOver you have to use a join to do a query like that:
Example 1
IQueryOver<Cat,Kitten> catQuery =
session.QueryOver<Cat>()
.JoinQueryOver(c => c.Kittens)
.Where(k => k.Name == "Tiddles");
Example 2
Cat catAlias = null;
Kitten kittenAlias = null;
IQueryOver<Cat,Cat> catQuery =
session.QueryOver<Cat>(() => catAlias)
.JoinAlias(() => catAlias.Kittens, () => kittenAlias)
.Where(() => catAlias.Age > 5)
.And(() => kittenAlias.Name == "Tiddles");
It works when you use Linq in this case because the filtering is being done on the client, not in the database. So it is actually the IEnumerable version of Where that is running which is not related to NHibernate.
The QueryOver uses Expression<Func<T,object>> which NHibernate tries to translate to SQL but fails. For reasons unknown to me you must explicitly join using JoinQueryOver or JoinAlias.
Some more info on QueryOver here:
http://nhibernate.info/blog/2009/12/17/queryover-in-nh-3-0.html
I am trying to use query over to retrieve a collection of entities based upon a join/subquery as per the example below:
var types = new List<ActivityType>{ActivityType.CommentMedia, ActivityType.KeepMedia};
return _sessionFactory.GetCurrentSession()
.QueryOver<Activity>()
.Where(a.Type.IsIn(types))
.WithSubquery.WhereExists(QueryOver.Of<Resource>()
.Where(k => k.MemberKey == userId)
.Where(k => k.ResourceKey == activity.ResourceId)
)
.Take(take)
.List();
In other words retrieve all activities which are in the resource table matching the user and resource id.
I would do this in raw sql by either joining to Resource, or a subquery:
where a.ResourceId in (select resourceKey from resource where resource.memberkey = a.MemberId)
Not sure how to proceed in nhibernate though. Any suggestions?
(We don't want to use a mapping, as we want to keep the Activity entity very simple for performance reasons)
Thanks in advance
Activity activity = null;
return _sessionFactory.GetCurrentSession()
.QueryOver(() => activity)
.Where(a.Type.IsIn(types))
.WithSubquery.WhereProperty(a => a.ResourceId).In(QueryOver.Of<Resource>()
.Where(k => k.MemberKey == userId)
.Where(k => k.ResourceKey == activity.ResourceId)
)
.Take(take)
.List();
I have an nhibernate issue where I am projecting the sql Coalesce function.
I am comparing two string properties having the same name, from two different entities. In the resulting sql, the same property from only the first entity is being compared thus:
var list = Projections.ProjectionList();
list.Add(
Projections.SqlFunction("Coalesce",
NHibernateUtil.String,
Projections.Property<TranslatedText>(tt => tt.ItemText),
Projections.Property<TextItem>(ti => ti.ItemText)));
var q = Session.QueryOver<TextItem>()
.Left.JoinQueryOver(ti => ti.TranslatedItems);
Evaluating q results in this sql
coalesce(this_.ItemText, this_.ItemText)
the this_ in the RHS needs to be an aliased table
I can use Projections.Alias(Projections.Property<TranslatedText>(tt => tt.ItemText), "ttAlias") but am not sure how to map "ttAlias" in the JoinQueryOver.
I can create an alias there too, but can't see how to name it.
TranslatedText ttAlias = null;
...
JoinQueryOver(ti => ti.TranslatedItems, () => ttAlias)
Aliases are variables in QueryOver, like you showed in the JoinQueryOver call. Alias names (strings) should not be needed in QueryOver, they are for Criteria queries.
To the problem itself: I can't test it right now, but I think this should work:
Projections.Property(() => ttAlias.ItemText)
I used this topic as a resource while writing a unit test. This QueryOver works well and may help others with similar issues. QueryOver still struggles with property mapping to transformers using expressions. It's technically possible to remove "Id" but IMHO it hinders clarity.
The complete example is on GitHub
String LocalizedName = "LocalizedName";
//Build a set of columns with a coalese
ProjectionList plColumns = Projections.ProjectionList();
plColumns.Add(Projections.Property<Entity>(x => x.Id), "Id");
plColumns.Add(Projections.SqlFunction("coalesce",
NHibernateUtil.String,
Projections.Property<Entity>(x => x.EnglishName),
Projections.Property<Entity>(x => x.GermanName))
.WithAlias(() => LocalizedName));
ProjectionList plDistinct = Projections.ProjectionList();
plDistinct.Add(Projections.Distinct(plColumns));
//Make sure we parse and run without error
Assert.DoesNotThrow(() => session.QueryOver<Entity>()
.Select(plDistinct)
.TransformUsing(Transformers.AliasToBean<LocalizedEntity>())
.OrderByAlias(() => LocalizedName).Asc
.Skip(10)
.Take(20)
.List<LocalizedEntity>());
There's sooo much literature about fetching and eager loading when doing the actual query using .Fetch
But, once I have a loaded entity - with an empty collection (because I chose not to eager load at query time due to the cartesian product side-effect), can I choose to load a collection a bit later on, say after I've done some paging and I have a concrete List of items?
something like:
var list = (some linq over Session.Query<Entity>)
.Take(10).Skip(2)
.Fetch(x => x.MyCollection)
.ToList();
Session.Fetch<Entity>(list, l => l.OtherCollection);
Edit
The point is - i'm already fetching 2 child collections in the Query - makes the query and result set quite sizeable already (see nhibernate Cartesian product). I'd like page the results, get a list of 10 then optionally go back to the database to populate child collection properties of the paged (10, say) result. This is a performance consideration.
Issue this query
/*we dont need the result*/Session.QueryOver<Entity>()
.Where(x => x.Id.IsIn(list.Select(l => l.Id)))
.Fetch(l => l.OtherCollection)
.ToList();
then nhibernate should initialize the collections on the Entities
EDIT:
to improve initial loading time see http://ayende.com/blog/4367/eagerly-loading-entity-associations-efficiently-with-nhibernate
then you can do for exmaple
var results = (some linq over Session.Query<Entity>)
.Take(10).Skip(2)
.ToList();
var q = Session.QueryOver<Entity>()
.Where(x => x.Id.IsIn(list.Select(l => l.Id)))
.Fetch(l => l.MyCollection)
.ToFuture();
Session.QueryOver<Entity>()
.Where(x => x.Id.IsIn(list.Select(l => l.Id)))
.Fetch(l => l.OtherCollection)
.ToFuture();
Session.QueryOver<Entity>()
.Where(x => x.Id.IsIn(list.Select(l => l.Id)))
.Fetch(l => l.ThirdCollection)
.ToFuture();
return q.ToList()
I've just gone off to read about futures and projections in NHibernate which look promising as a solution...will post more when I find out about it.
This is a solution: http://ayende.com/blog/4367/eagerly-loading-entity-associations-efficiently-with-nhibernate, but I still do not like it, as my query itself is quite expensive (uses '%like%# + paging), so executing 3 or 4 times just to load collections seems expensive
Edit
This is what I have. Lookign at the sql generated, the correct sql is being run and returning expected results, but the collections on the returned results are null. Can you see what's missing?:
public List<Company> CompaniesForLoggedInUser(int pageSize, int pageNumber)
{
var list =
QueryForCompaniesFor(SecurityHelper.LoggedInUsername)
.Page(pageNumber, pageSize)
.ToList()
.FetchCompanyCollections(Session);
return list;
}
internal static class CompanyListExtensions
{
internal static List<Company> FetchCompanyCollections(this List<Company> companies, ISession session)
{
var ids = companies.Select(l => l.Id).ToArray();
session.QueryOver<Company>()
.Where(x => x.Id.IsIn(ids))
.Fetch(l => l.Properties).Eager()
.Future();
return session.QueryOver<Company>()
.Where(x => x.Id.IsIn(ids))
.Fetch(l => l.UserAccessList).Eager()
.Future()
.ToList();
}
}
I'm trying to load all the collections eagerly, using NHibernate 3 alpha 1. I'm wondering if this the right way of using ThenFetch()?
Properties with plural names are collections. The others are just a single object.
IQueryable<T> milestoneInstances = Db.Find<T, IQueryable<T>>(db =>
from mi in db
where mi.RunDate == runDate
select mi).Fetch(mi => mi.Milestone)
.ThenFetch(m => m.PrimaryOwners)
.Fetch(mi => mi.Milestone)
.ThenFetch(m => m.SecondaryOwners)
.Fetch(mi => mi.Milestone)
.ThenFetch(m => m.Predecessors)
.Fetch(mi => mi.Milestone)
.ThenFetch(m => m.Function)
.Fetch(mi => mi.Milestone)
.ThenFetchMany(m => m.Jobs)
.ThenFetch(j => j.Source)
;
I thought of asking this in the NHibernate forums but unfortunately access to google groups is forbidden from where I am. I know Fabio is here, so maybe the guys from the NHibernate team can shed some light on this?
Thanks
Apparently, there's no "right" way to use ThenFetch in such a case. Your example works fine but SQL produced contains many joins to Milestone, which isn't that right.
Using IQueryOver instead of IQueryable allows you to use complex syntax in Fetch:
Fetch(p => p.B)
Fetch(p => p.B.C) // if B is not a collection ... or
Fetch(p => p.B[0].C) // if B is a collection ... or
Fetch(p => p.B.First().C) // if B is an IEnumerable (using .First() extension method)
So in your case it would be:
query // = session.QueryOver<X>()
.Fetch(mi => mi.Milestone).Eager
.Fetch(mi => mi.Milestone.PrimaryOwners).Eager
.Fetch(mi => mi.Milestone.SecondaryOwners).Eager
.Fetch(mi => mi.Milestone.Predecessors).Eager
.Fetch(mi => mi.Milestone.Function).Eager
.Fetch(mi => mi.Milestone.Jobs).Eager
.Fetch(mi => mi.Milestone.Jobs.First().Source).Eager
The one thing you are missing is that you should use FetchMany() and ThenFetchMany() is the child property is a collection.
IQueryable<T> milestoneInstances = Db.Find<T, IQueryable<T>>(db =>
from mi in db
where mi.RunDate == runDate
select mi);
var fetch = milestoneInstances.Fetch(f => f.Milestone);
fetch.ThenFetch(f => f.PrimaryOwners);
fetch.ThenFetch(f => f.SecondaryOwners);
//...
As leora said, make sure when fetching children collections that you use
FetchMany()
ThenFetchMany()
A new Fetch, should pick up from the root, but this does not always happen. Sometimes you need to create them as separate queries or use Criteria Futures to batch up a multiple fetch.