How can I eagerly load a child collection in nhibernate - c#

Apologies for such a noob question.
I have two entities: Parent and Child, modeled thusly:
public class Parent()
{
public virtual int ID{ get; protected set; },
public virtual IList<child> Children {get;set;}
}
public class Child()
{
public virtual int ID{ get; protected set; },
public virtual Parent Parent {get;set;}
}
With these mappings:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Id(x => x.Id).GeneratedBy.Identity();
HasMany(x=>x.Children)
.Cascade
.AllDeleteOrphan()
.Inverse();
}
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
Id(x => x.Id).GeneratedBy.Identity();
References(x=>x.Parent)
.Cascade
.SaveUpdate();
}
}
I have left lazy loading on, as a parent can have many children and I don't want to always load the children.
I have tried the following methods:
var query1 = Session.QueryOver<Parent>()
.Where(x => x.Id == parent.Id)
.Fetch(t=>t.Children).Eager().List<Parent>();
var query2 = Session.CreateCriteria<Parent>("d")
.Add(Restrictions.Eq("Id", parent.Id))
.SetFetchMode("d.Children", FetchMode.Eager)
.List<Parent>();
var query3 = Session.CreateCriteria(typeof (Parent))
.Add(Restrictions.Eq("Id", parent.Id))
.SetFetchMode("Children", FetchMode.Eager)
.List<Parent>();
I've simplified the objects, of course, but I've also tried these in their own console app, just to test. The problem is: in none of those queries do I actually get the children.
Looking at SQL Profiler, I cannot see any join being generated by nhibernate.
I know I can just go the other way and get the parent from the children, but I'd rather not do that.
I think I am missing something fundamental in my understanding of Nhibernate.
Thanks for any help

Resolved this, and should really have included more details in the first code example.
I tried to retrieve the child objects of the parent after saving the parent(with cascade) in the same session.
Closing the session and trying any one of the above queries returned the child(ren).

Related

EntityFramework Include Without Guarantee of Existence

I have an issue I am unsure how to solve. I have three models, similar to as follows.
public class Parent : BaseEntity {
[Key]
public string Guid { get; set; }
[NotMapped]
public List<Child> Childs { get; set; }
}
public class Child : BaseEntity {
[Key]
public string Guid { get; set; }
public string ParentGuid { get; set; }
public List<Detail> Details { get; set; }
}
public class Detail : BaseEntity {
[Key]
public string Guid { get; set; }
[ForeignKey(nameof(Child))]
public string ChildGuid { get; set; }
public Child Child { get; set }
}
I'm attempting to include both the children and the details. However, I am not guaranteed the Guid on the child exists in the parent table. That's why I went with NotMapped, but am willing and able to change that if need be. Right now I have this:
query.GroupJoin(context.Parents,
parent => parent.ChildGuid,
child => child.Guid,
(parent, childs) => new
{
Parent = parent,
Childs = childs
}
)
.AsEnumerable()
.Select(combos =>
{
combos.Parent.Childs = combos.Childs.ToList();
return combos.Parent;
})
.AsQueryable();
But that, of course, does not include the Details. Not sure if I'm heading the right direction here or not, but could use some direction if anyone has run into this before.
A typical parent-child relationship has the ParentID on the child table/entity. Your entity definitions seem to reflect that, but then your Linq expression refers to some parent.ChildGuid that isn't mentioned in your entity.
First let's correct your entity definition relationships. Childs shouldn't be excluded. A parent can have 0 or multiple children. Collections should be declared as virtual ICollection<T> rather than concrete List<T>. virtual enables lazy loading and helps ensure that EF proxies are fully functional for tracking changes of items in the collections. We should also initialize those collections with a new concrete list. This helps ensure that any new entity we create is ready to go accepting children.
public class Parent : BaseEntity
{
[Key]
public string Guid { get; set; }
public virtual ICollection<Child> Childs { get; set; } = new List<Child>();
}
public class Child : BaseEntity
{
[Key]
public string Guid { get; set; }
public string ParentGuid { get; set; }
public virtual ICollection<Detail> Details { get; set; } = new List<Detail>();
}
public class Detail : BaseEntity
{
[Key]
public string Guid { get; set; }
[ForeignKey(nameof(Child))]
public string ChildGuid { get; set; }
public virtual Child Child { get; set }
}
EF can automatically map relationships provided you follow supported conventions in your naming. Chances are with the "Guid" syntax for your ID columns, EF won't work these out automatically so you will probably need to give it some help through configuration. For example in the OnModelCreating of the DbContext:
// EF Core
modelBuilder.Entity<Parent>()
.HasMany(x => x.Childs)
.WithOne()
.IsRequired() // If a Child must have a ParentGuid
.HasForeignKey(x => x.ParentGuid);
Since Child has a ParentGuid but no Parent reference declared, we use the WithOne() with no mapping, then use the HasForeignKey to set up the relationship. Similarly we set up the relationship between Child and Details:
// EF Core
modelBuilder.Entity<Child>()
.HasMany(x => x.Details)
.WithOne(x => x.Child)
.IsRequired() // If a Detail must have a Child
.HasForeignKey(x => x.ChildGuid);
In this case since we have a Child reference on Detail so we map to that.
Now regarding this statement:
However, I am not guaranteed the Guid on the child exists in the parent table.
From this it somewhat implies that a ParentGuid on the Child table might not exist in the Parents table. This would be a fairly serious concern from a database normalization and ensuring a valid data state. I would highly recommend to avoid trying to use a non-normalized schema such as attempting to "share" a Child table between multiple Parent tables. A classic example where this is tried is having an Address table (Child) referencing a ParentId that could point at a Customer (Parent) or Site (Parent) and/or other parent-like tables. Instead, you should consider a more normalized approach which would use a CustomerAddress and SiteAddress etc. linking table to ensure these relationships can be mapped out.
If a Child can exist without a Parent then you just need to remove the .IsRequired().
Now when querying, you don't need to worry specifically about joining and grouping, just query through the navigation properties and either eager-load the related data you want when working with the Entity graph, or project the data using Select when querying for details:
var query = context.Parents
.Include(x => x.Childs)
.ThenInclude(x => x.Details)
To get the children with their associated Parent:
var query = context.Parents
.Include(x => x.Childs)
.ThenInclude(x => x.Details)
.SelectMany(x => x.Childs.Select(c => new {Parent = x, Child = c})
.ToList();
If you want to include children that have no parent:
var query = context.Parents
.Include(x => x.Childs)
.ThenInclude(x => x.Details)
.SelectMany(x => x.Childs.Select(c => new {Parent = x, Child = c})
.Union(context.Childs.Where(x => x.ParentGuid == null))
.ToList();
These are very rough guesses as the type of query you want to perform. Again, if the ParentGuid could be referring to a non-existent row in the DB or a different table I would really look at correcting that to ensure the data maintains referential integrity. I don't recommend trying to "break" EF behavior to work with an effectively broken schema. You may get it to have a semblance of working, but it could very easily lead to exceptions or unexpected behaviour.

Fluent NHibernate: One to One Mapping - reverse from primary column to a foreign key

I have a "Group" class and a "GroupSummaryLevel" class, codes are given below. There is a one-to-one relation between these entities in DB. I need the "GroupSummaryLevel" as a property in Groups class. It is supposed to be a very simple join like
(SELECT g.Id FROM GroupSummaryLevel g WHERE g.AcctGroup = GroupID)
Unfortunately, I wasn't able to figure this out how to do with NHibernate. The many answers I saw here is no help to me. I would appreaciate any inputs from the more experienced NHibernate users out there. Thanks in advance.
public class Group : DomainEntity
{
public virtual string GroupId { get; set; }
public virtual string GroupName { get; set; }
public virtual GroupSummaryLevel GroupSummaryLevel { get; set; }
}
public class GroupSummaryLevel : DomainEntity
{
public virtual int Id { get; set; }
public virtual string AcctGroup { get; set; }
public virtual GroupSummaryLevel Parent { get; set; }
public virtual IList<GroupSummaryLevel> Children { get; set; }
public GroupSummaryLevel()
{
Children = new List<GroupSummaryLevel>();
}
}
The mapping I have done did not work so far. My mapping codes are as below:
public GroupMap()
{
Table("Groups");
LazyLoad();
Id(x => x.GroupId).GeneratedBy.Assigned().Column("GroupID").CustomType<TrimmedString>();
Map(x => x.GroupName).Column("GroupName").CustomType<TrimmedString>().Not.Nullable();
HasOne(x => x.GroupSummaryLevel).Cascade.None().ForeignKey("AcctGroup");
}
public GroupSummaryLevelMap()
{
Table("GroupSummaryLevel");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
Map(x => x.AcctGroup).Column("AcctGroup").CustomType<TrimmedString>().Not.Nullable();
//References(x => x.Parent).Column("ParentId");
//HasMany(x => x.Children).Cascade.All().KeyColumn("ParentId");
}
Note: I also need to do a self-join for GroupSummaryLevel, and no success with that either. Any recommendations for that will also be appreciated :)
I would say, that your one-to-one is not driven by primary/foreign keys, but by property-ref. So the Group should map the summary by saying something like this:
...if you want to find related SummaryLevel, pass my <id> into column mapped as AcctGroup
public GroupMap()
{
...
HasOne(x => x.GroupSummaryLevel)
.Cascade.None()
//.ForeignKey("AcctGroup")
.PropertyRef(gsl => gsl.AcctGroup)
;
}
public GroupSummaryLevelMap()
{
...
//References(x => x.Parent).Column("ParentId");
//HasMany(x => x.Children).Cascade.All().KeyColumn("ParentId");
References(x => x.Parent, "AcctGroup");
}
NOTEs for completeness, as discussed in comments:
In this scenario, when the "child" has reference to parent - it really calls for one-to-many/.HasMany() mapping.
The down side is, that child is represented as a colleciton of children: IList<GroupSummaryLevel>. It is not as straighforward to use, but we can create some virtual property, returning the .FirstOrDefault(). The benefit we get - is lazy loading (not in place with one-to-one).

Linq to NHibernate - eager load grandchildren but not children

I am using NHibernate 3.2 with FluentNHibernate and Linq to NHibernate. I want use Linq to NHibernate to eager load all of the grandchildren of a collection without having to load the children. For example, say that I have the following classes:
public class Parent
{
public virtual int Id { get; set; }
public virtual IList<Child> Children { get; set; }
}
public class ParentMap : ClassMap<Parent>
{
Id(x => x.Id);
HasManyToMany(x => x.Children).ExtraLazyLoad();
}
public class Child
{
public virtual int Id { get; set; }
public virtual IList<Parent> Parents { get; set; }
public virtual IList<Grandchild> Grandchildren { get; set; }
public virtual ProhibitivelyLargeType ProhibitivelyLargeField { get; set; }
public virtual ProhibitivelyLargeType RarelyUsedLargeField { get; set; }
}
public class ChildMap : ClassMap<Child>
{
Id(x => x.Id);
HasManyToMany(x => x.Parents).ExtraLazyLoad();
HasManyToMany(x => x.Grandchildren).ExtraLazyLoad();
Map(x => x.ProhibitivelyLargeField);
Map(x => x.RarelyUsedField).LazyLoad();
}
public class Grandchild
{
public virtual int Id { get; set; }
public virtual IList<Child> Children { get; set; }
public virtual int Age { get; set; }
}
public class GrandchildMap : ClassMap<Grandchild>
{
Id(x => x.Id);
HasManyToMany(x => x.Children).ExtraLazyLoad();
Map(x => x.Age);
}
For each Parent, I want to find out the total combined age of all of that parent's grandchildren. I could do so using the following method:
Dictionary<Parent, int> grandchildAges = session.Query<Parent>()
.FetchMany(p => p.Children)
.ThenFetchMany(c => c.Grandchildren)
.AsEnumerable()
.ToDictionary(
p => p,
p => p.Children.SelectMany(c => c.Grandchildren).Sum(g => g.Age)
);
This method produces the correct results. However, it necessitates loading all of the Child objects. Child includes a field of type ProhibitivelyLargeType, which is not lazy loaded, so I would really prefer to not load anything about Child but its ID. If I don't use FetchMany/ThenFetchMany, however, then I have the N + 1 problem and it takes a trip to the database for each Child and Grandchild, which is also unacceptable.
Alternatively, I could make ProhibitivelyLargeField LazyLoad. However, most applications that use the Child class need to use ProhibitivelyLargeField, but they do not want to have to load RarelyUsedLargeField, which is already LazyLoad. As I understand it, loading one LazyLoad property causes all of them to be loaded, so this solution would bog down the normal use case.
Is there a way to get the just the information that I am looking for using Linq to NHibernate, or do I have to use the Criteria Query API?
Thanks!
edited to give an example of why making ProhibitivelyLargeField LazyLoad might be undesirable
the following is QueryOver. its only to show the idea of loading the results in two smaller steps. Maybe you can translate it into LINQ
// inititialize the dictionary
Grandchild grandchild = null;
Dictionary<Parent, int> dict = session.QueryOver<Parent>()
.JoinQueryOver(p => p.Childs)
.JoinAlias(c => c.GrandChilds, () => grandchild)
.Select(Projections.Group<Parent>(p => p.Id), Projections.Sum(() => grandchild.Age))
.AsEnumerable()
.Cast<object[]>()
.ToDictionary(
array => session.Load<Parent>(array[0]),
array => (int)array[1]
);
// initialize all Parent proxies
session.QueryOver<Patient>()
.WhereProperty(p => p.Id).In(dict.Keys.Select(p => p.Id))
.ToList();
I haven't used nhibernate, but I have used linq to entities, and from what I see you are doing tons of database queries. You should instead do a single line query which returns only the data you want:
from parent in session.Parents
let children = parent.Children
select new {parent = parent, children.SelectMany(c => c.Grandchildren).Sum(gc => gc.Age)}
Apologies if I got something wrong. Haven't done C# in a while and I'm on my phone.
If this approach doesn't work someone just tell me and I'll delete it.

Nhibernate self reference performance

I have a model that looks like this:
public class ComponentAttributeDto
{
public virtual long ComponentAttributeId { get; set; }
public virtual ComponentAttributeDto ParentComponentAttributeDto { get; set; }
public virtual string ComponentAttributeName { get; set; }
public virtual string Value { get; set; }
public virtual DataType DataType { get; set; }
public virtual IList<ComponentAttributeDto> ChildComponentAttributes { get; set; }
}
With a mapping file of:
public class ComponentAttributeMapping : ClassMap<ComponentAttributeDto>
{
public ComponentAttributeMapping()
{
Table("ComponentAttributes");
Id(x => x.ComponentAttributeId)
.GeneratedBy.Identity();
References(x => x.ParentComponentAttributeDto)
.Column("ParentComponentAttributeId");
HasMany(x => x.ChildComponentAttributes)
.Fetch.Select()
.Inverse()
.Cascade.AllDeleteOrphan()
.KeyColumn("ParentComponentAttributeId");
Map(x => x.ComponentAttributeName)
.Length(50);
Map(x => x.Value)
.Length(1500);
Map(x => x.DataType)
.Length(20);
}
}
When loading this with a large dataset that goes about 4 levels deep the performance is terrible. When running profiler I noticed that is it executing a select statement for every single value in the table for the data I want to lookup. Is there a way I can improve the performance to do some type of join on the table or something else?
You could use batch-size to pre-fetch instances, which reduces the number of queries considerably.
mapping (not sure if it is supported by Fluent in the meanwhile):
HasMany(x => x.ChildComponentAttributes)
.Fetch.Select()
.SetAttribute("batch-size", "20")
.Inverse()
.Cascade.AllDeleteOrphan()
.KeyColumn("ParentComponentAttributeId");
If you had a Root property, you could make a query for the whole tree at once.
public class ComponentAttributeDto
{
public virtual ComponentAttributeDto ParentComponentAttributeDto { get; private set; }
public virtual ComponentAttributeDto Root
{
get
{
if (ParentComponentAttributeDto == null)
{
return this;
}
else
{
return ParentComponentAttributeDto.Root;
}
}
private set
{ /* just for NH to call it */ }
}
// ....
}
HasMany(x => x.Children).AsSet().SetAttribute("batch-size", "20")
the query
session.CreateQuery(
#"from ComponentAttributeDto
where Root = :root"
.SetEntity(root);
should actually result in only a single query. Not sure if NH doesn't actually perform queries for the lists (ChildComponentAttributes), but it is worth a try.
Do you need the entire data structure all at once? Usually when I run into this problem I just take away the mapping handling from nHibernate and deal with it myself. Create a method for the class called getChildren() and have it run the query when called. If you want to add a child record then add another method called addChild() and have it instantiate with it's own parent ID.
You could eagerly fetch the hierarchy when you query. You can do this by using the eager fetch option in your query:
Session.QueryOver<ComponentAttributeDto>
.Fetch(a => a.ChildComponentAttributes).Eager
Down to the level you want to fetch.

Fluent / NHibernate Collections of the same class

I am new to NHibernate and I am having trouble mapping the following relationships within this class.
public class Category : IAuditable
{
public virtual int Id { get; set; }
public virtual string Name{ get; set; }
public virtual Category ParentCategory { get; set; }
public virtual IList<Category> SubCategories { get; set; }
public Category()
{
this.Name = string.Empty;
this.SubCategories = new List<Category>();
}
}
Class Maps (although, these are practically guesses)
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Id(x => x.Id);
Map(x => x.Name);
References(x => x.ParentCategory)
.Nullable()
.Not.LazyLoad();
HasMany(x => x.SubCategories)
.Cascade.All();
}
}
Each Category may have a parent category, some Categories have many subCategories, etc, etc
I can get the Category to Save correctly (correct subcategories and parent category fk exist in the database) but when loading, it returns itself as the parent category.
I am using Fluent for the class mapping, but if someone could point me in the right direction for just plain NHibernate that would work as well.
By convention, Fluent NHibernate will look at "Category_Id" as foreign key column. It won't figure out your "ParentCategoryId" column. Unless you rename your self-referencing column to "Category_Id", you have to clarify the column name for both parent and child relationships.
For category without parent (absolute-parent category), whose reference column is null, it's reasonable to return itself as parent or null depending on how NHibernate handles it since you choose eager loading.
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Id(x => x.Id);
Map(x => x.Name);
References(x => x.ParentCategory)
.Column("ParentCategoryId") // Many-To-One : parent
.Nullable()
.Not.LazyLoad();
HasMany(x => x.SubCategories)
.Cascade.All().Inverse().KeyColumn("ParentCategoryId"); //One-To-Many : chidren
}
}
Ok so on the HasMany(x=>x.SubCategories) you need to add Inverse() to the call chain and also give it the column name which I'm assuming is "ParentCategoryId" given the mapping of the parent category, of course this depends on your conventions too.
If you were to post your table stucture I can give you a complete solution.

Categories