How to eager load objects in a list/collection? - nhibernate

I have the following query:
ObjectB objectBAlias = null;
ObjectC objectCAlias = null;
var query = session.QueryOver<ObjectA>();
var results = query
.JoinAlias(x => x.listOfBs, () => objectBAlias)
.JoinAlias(x => objectBAlias.ObjectC, () => objectCAlias)
.TransformUsing(new DistinctRootEntityResultTransformer())
.List<ObjectA>();
class ObjectA
{
IList<ObjectB> listOfBs;
}
class ObjectB
{
ObjectA a;
ObjectC c;
}
class ObjectC
{
int x;
}
ObjectA has a many-to-many relationship to ObjectC with ObjectB being the joining table. ObjectA has a list of ObjectB's, and ObjectB has a proporty of ObjectC. I'm trying to eager load ObjectC but have had no success.
The only way I've gotten all the ObjectCs to eager load was by doing this:
foreach (ObjectA a in results)
{
foreach (var b in a.listOfBs)
{
NHibernateUtil.Initialize(b.c);
}
}
But this doesn't seem like something that would scale very well.

I would suggest - do not try that (eager loading of many-to-many). Instead - use built in feature:
19.1.5. Using batch fetching
NHibernate can make efficient use of batch fetching, that is, NHibernate can load several uninitialized proxies if one proxy is accessed (or collections. Batch fetching is an optimization of the lazy select fetching strategy. There are two ways you can tune batch fetching: on the class and the collection level.
To get more details check these:
How to Eager Load Associations without duplication in NHibernate?
NHibernate Fetch/FetchMany duplication in resultset, how to fix with ToFuture()
Fetching Collections is a difficult operation. It has many side effects (as you realized, when there are fetched more collections). But even with fetching one collection, we are loading many duplicated rows.

Related

EF Core include related ids but not related entities

Before I go creating my own SQL scripts by hand for this, I have a scenario where I want to get the ids of a foreign key, but not the entirety of the foreign entities, using EF Core.
Right now, I'm getting the ids manually by looping through the related entities and extracting the ids one at a time, like so:
List<int> ClientIds = new List<int>();
for (var i = 0; i < Clients.length; i++){
ClientIds.add(Clients.ElementAt(i).Id);
}
To my understanding, this will either cause data returns much larger than needed (my entity + every related entity) or a completely separate query to be run for each related entity I access, which obviously I don't want to do if I can avoid it.
Is there a straightforward way to accomplish this in EF Core, or do I need to head over the SQL side and handle it myself?
Model:
public class UserViewModel {
public UserViewModel(UserModel userModel){
ClientIds = new List<int>();
for (var i = 0; i < UserModel.Clients.length; i++){
ClientIds.add(Clients.ElementAt(i).Id);
}
//...all the other class asignments
}
public IEnumerable<int> ClientIds {get;set;}
//...all the other irrelevant properties
}
Basically, I need my front-end to know which Client to ask for later.
It looks like you are trying to query this from within the parent entity. I.e.
public class Parent
{
public virtual ICollection<Client> Clients { get; set; }
public void SomeMethod()
{
// ...
List<int> ClientIds = new List<int>();
for (var i = 0; i < Clients.length; i++)
{
ClientIds.add(Clients.ElementAt(i).Id);
}
// ...
}
}
This is not ideal because unless your Clients were eager loaded when the Parent was loaded, this would trigger a lazy load to load all of the Clients data when all you want is the IDs. Still, it's not terrible as it would only result in one DB call to load the clients.
If they are already loaded, there is a more succinct way to get the IDs:
List<int> ClientIds = Clients.Select(x => x.Id).ToList();
Otherwise, if you have business logic involving the Parent and Clients where-by you want to be more selective about when and how the data is loaded, it is better to leave the entity definition to just represent the data state and basic rules/logic about the data, and move selective business logic outside of the entity into a business logic container that scopes the DbContext and queries against the entities to fetch what it needs.
For instance, if the calling code went and did this:
var parent = _context.Parents.Single(x => x.ParentId == parentId);
parent.SomeMethod(); // which resulted in checking the Client IDs...
The simplest way to avoid the extra DB call is to ensure the related entities are eager loaded.
var parent = _context.Parents
.Include(x => x.Clients)
.Single(x => x.ParentId == parentId);
parent.SomeMethod(); // which resulted in checking the Client IDs...
The problem with this approach is that it will still load all details about all of the Clients, and you end up in a situation where you end up defaulting to eager loading everything all of the time because the code might call something like that SomeMethod() which expects to find related entity details. This is the use-case for leveraging lazy loading, but that does have the performance overheads of the ad-hoc DB hits and ensuring that the entity's DbContext is always available to perform the read if necessary.
Instead, if you move the logic out of the entity and into the caller or another container that can take the relevant details, so that this caller projects down the data it will need from the entities in an efficient query:
var parentDetails = _context.Parents
.Where(x => x.ParentId == parentId)
.Select(x => new
{
x.ParentId,
// other details from parent or related entities...
ClientIds = x.Clients.Select(c => c.Id).ToList()
}).Single();
// Do logic that SomeMethod() would have done here, or pass these
// loaded details to a method / service to do the work rather than
// embedding it in the Entity.
This doesn't load a Parent entity, but rather executes a query to load just the details about the parent and related entities that we need. In this example it is projected into an anonymous type to hold the information we can later consume, but if you are querying the data to send to a view then you can project it directly into a view model or DTO class to serialize and send.

How to set NHibernate session to eager fetch

I've got a method helper in my tests base class that looks like this:
protected TEntity Fetch<TEntity>(Guid id) where TEntity : Entity
{
using (var session = GetSession())
return session.Get<TEntity>(id);
}
So I can call it from an integration test as such:
var persistedFoo = Fetch<Foo>(foo.Id);
How can I set the session in my Fetch method to eager fetch all properties in TEntity?
According to NHibernate docs here you should use NHibernateUtility class, so change your code into something like this should help:
using(var session = GetSession())
{
var entity = session.Get<TEntity>(id);
NHibernateUtil.Initialize(entity);
}
alternatively, you can use one of nHib's querying APIs (I personally prefer QueryOver), to do something like
session.QueryOver<Cat>().Where(cat => cat.Id == id).Fetch(c => c.Kittens).Eager.
This gives you the added bonus of controlling exactly which properties / collections would be fetched.
Also, it's recommended that you do NOT abstract-away your ISession usage in repositories.
It would prevent you from benefiting from such nHibernate features as batching (see ayende's post here)

Nhibernate lazy loading error on session.Get

I'm receiving on my controller some int which is parameter I'm using for getting entity.
This entity have List Collection which I need to load together with my entity.
I cannot access Fetch method in session.Get so I dont know how to achive.
When in my view I tried to access to my collection like entity.Collection it throws an error, no session or session was closed
Here is my code
public ActionResult Details(int id)
{
MyDomain.Property data = null;
using (//open session)
{
using (//using transaction)
{
data = session.Get<MyDomain.Property>(id);
//I need to load Photo() collection.
transaction.Commit();
}
}
return PartialView("DetailsPartial", data);
}
Your entity has a collection's property with a proxy (not real collection). When you close session you can't use lazy load, so, you need to get real collection objects.
You should get it with query:
Session.QueryOver<Entity>()
.Where(entity => entity.Id == id)
.Fetch(entity => entity.CollectionProperty).Eager
.SingleOrDefault<Entity>();

NHibernate Criteria: howto exclude certain mapped properties/collections?

Here's my (simplified) model: Ticket -> Customer Callback (s)
I have my Ticket mapped so that when it's loaded, the Callbacks are as well.
base.HasMany<TechSupportCallback>(x => x.Callbacks)
.KeyColumn(Fields.TRACKED_ITEM_ID)
.Not.LazyLoad()
.Inverse()
.Cache.ReadWrite();
This is not lazy loading because otherwise I'll get 'no session to load entities' when the web service tries to serialize (and load) the proxy. (Using repositories to fetch data.)
It's also bi-directional .. (in the CallbackMap)
base.References(x => x.Ticket)
.Column(Fields.TRACKED_ITEM_ID)
.Not.Nullable();
Now .. we need to show an agent a list of their callbacks - JUST their callbacks.
-- When I query using Criteria for the Callbacks, I cannot prevent the Ticket (and subsequently it's entire graph, including other collections) from being loaded. I had previously tried to set FetchMode.Lazy, then iterate each resulting Callback and set Ticket to null, but that seems to be ignored.
// open session & transaction in using (..)
var query = session.CreateCriteria<TechSupportCallback>()
.SetCacheable(true)
.SetCacheRegion("CallbacksByUserAndGroups")
.SetFetchMode("Ticket", FetchMode.Lazy) // <-- but this doesn't work!
.SetMaxResults(AegisDataContext.Current.GetMaxRecordCount())
;
rValue = query.List<TechSupportCallback>();
rvalue.ForEach(x => x.Ticket = null;); // <-- since this is already retrieved, doing this merely prevents it from going back across the wire
tx.Commit();
// usings end (..)
Should I be doing this with a projection instead?
The problem with that .. is I've not been able to find an example of projections being used to populate an entity, or a list of them -- only to be used as a subquery on a child entity or something similar to restrict a list of parent entities.
I could really use some guidance on this.
[EDIT]
I tried using a projection as suggested but:
I'm getting the following: (this was because of a bug, and so I've since stopped using the cache and it works. http://nhibernate.jira.com/browse/NH-1090)
System.InvalidCastException occurred
Message=Unable to cast object of type 'AEGISweb.Data.Entities.TechSupportCallback' to type 'System.Object[]'.
Source=NHibernate
StackTrace:
at NHibernate.Cache.StandardQueryCache.Put(QueryKey key, ICacheAssembler[] returnTypes, IList result, Boolean isNaturalKeyLookup, ISessionImplementor session)
InnerException:
at
rValue = query.List<TechSupportCallback>();
with the projection list defined like
// only return the properties we want!
.SetProjection(Projections.ProjectionList()
.Add(Projections.Alias(Projections.Id(), ex.NameOf(x => x.ID))) //
.Add(Projections.Alias(Projections.Property(ex.NameOf(x => x.ContactID)), ex.NameOf(x => x.ContactID)))
// ...
)
.SetResultTra...;
rValue = query.List<TechSupportCallback>();
mapped like
public TechSupportCallbackMap()
{
base.Cache.ReadWrite();
base.Not.LazyLoad();
base.Table("TS_CALLBACKS");
base.Id(x => x.ID, Fields.ID)
.GeneratedBy.Sequence("SEQ_TS_CALLBACKS");
base.References(x => x.Ticket)
.Column(Fields.TRACKED_ITEM_ID)
.Not.Nullable();
base.Map(x => x.TrackedItemID, Fields.TRACKED_ITEM_ID)
.Not.Insert()
.Not.Update()
.Generated.Always()
;
// ...
}
This sounds like it's a job exactly for projections.
var query = session.CreateCriteria<TechSupportCallback>()
.SetCacheable(true)
.SetCacheRegion("CallbacksByUserAndGroups")
.SetFetchMode("Ticket", FetchMode.Lazy)
.SetMaxResults(AegisDataContext.Current.GetMaxRecordCount())
.SetProjection(Projections.ProjectionList().
.Add(Projections.Alias(Projections.Id(), "Id")
.Add(Projections.Alias(Projections.Property("Prop"), "Prop")))
.SetResultTransformer(Transformers.AliasToBean<TechSupportCallback>())
;
Simply list all the properties you want to include and exclude the Ticket entity. You can even create a POCO class simply for encapsulating the results of this query. Rather than using an existing entity class.

Caching not working for associated entities unless lazy loading specified in mapping file

I am having a problem getting associated entities to be cached unless
they are specified (Not.LazyLoad() in mapping). If I build up a query
in Criteria or Linq the Main Entity is cached, but not associated
entities.
IList<NewsItem> news;
using (var session = factory.OpenSession())
{
Console.WriteLine("First Query");
news = session.CreateCriteria(typeof(NewsItem))
.SetCacheable(true)
.SetFetchMode("Author", FetchMode.Eager) // associated entity eager loaded
.List<NewsItem>();
}
foreach (var item in news)
{
Console.WriteLine("Author: " + item.Author.Name); //works fine first time
}
using (var session = factory.OpenSession())
{
Console.WriteLine("");
Console.WriteLine("Second Query");
news = session.CreateCriteria(typeof(NewsItem))
.SetCacheable(true)
.SetFetchMode("Author", FetchMode.Eager)
.List<NewsItem>();
}
foreach (var item in news)
{
Console.WriteLine("Author: " + item.Author.Name); //NHibernate.LazyInitializationException
}
I want to avoid having to eager load the associations via the mapping
files. Anyone else having a similar problem.
Any feedback appreciated.
What query caching (SetCacheable(true)) does is store the IDs retrieved by the query; it then hydrates the objects one by one (hopefully, from the entity cache).
IIRC, when caching a query, FetchModes for related entites are not used, so the Author references are not being initialized.
It's worth noting that you shouldn't be using your entities outside a session, but here's a workaround:
news = session.CreateCriteria(typeof(NewsItem))
.SetCacheable(true)
.SetFetchMode("Author", FetchMode.Eager)
.List<NewsItem>();
foreach (var item in news)
{
var name = item.Author.Name;
}
This will force initialization of the proxies inside the sessions; that way you'll be able to use them afterwards.
Of course, you need to make sure both NewsItem and Author are cached, otherwise you'll actually decrease the performance by caching the query.