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.
Related
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).
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).
I have following class and associated mappings (Fluent NHibernate):
public class Category
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual IList<Category> ChildCategories { get; set; }
}
Mappings:
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Table("Categories");
Id(x => x.Id).Column("Id").CustomType("Int32").Access.Property()
.CustomSqlType("int").Not.Nullable().Precision(10)
.GeneratedBy.Identity();
Map(x => x.Description).Column("Description").Nullable()
.Generated.Never().CustomType(typeof (string)).Access
.Property().Length(250);
Map(x => x.Name).Not.Nullable().Generated.Never().CustomType("string")
.Access.Property().Column("Name").Length(50);
References(x => x.ParentCategory).Column("ParentCategoryId");
HasMany(x => x.ChildCategories).KeyColumn("ParentCategoryId").Inverse()
.AsBag().Fetch.Select();
}
}
I creating two Category object as follows:
var c = new Category
{
Name = "Ebooks",
Description = "Contains awz, mobi, pdf, epub and other
electronic books"
};
var cc = new Category
{
Name = "Kindle Books",
Description = "Contains Kindle ebook reader format books
(awz, mobi)",
ParentCategory = c
};
session.SaveOrUpdate(c);
session.SaveOrUpdate(cc);
When I try to access saved objects:
var c = session.Load<Category>(1);
var cc = c.ChildCategories;
c contains the Category object with Id of 1 but its ChildCategories property is null.
What I am doing wrong?
PS:- This is my first experiment with NHibernate and so with Fluent NHibernate.
EDIT:- The following stuff worked. I needed to close the session before opening it again for reading. Otherwise it read just from memory nad as #Holf has pointed out I needed to add Child Category to Category like:
c.ChilCategories.Add(cc);
I just did as follows:
var session = sf.OpenSession();
CreateCategory(session);//ADDED TWO CATEGORIES EBooks, Kindle Ebooks
session.Close();
session = sf.OpenSession();
FetchCategories(session);//READ CATEGORY EBooks AND ASSOCIATED CHILDREN
session.Close();
Although you've handled one side of the relationship, by assigning 'c' as the ParentCategory of 'cc', I can't see anywhere that you've done the inverse.
I think you'll also need to do
c.ChildCategories.Add(cc);
before doing the SaveOrUpdate.
Can you update your map to the following:
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Table("Categories");
Id(x => x.Id).Column("Id").CustomType("Int32").Access.Property()
.CustomSqlType("int").Not.Nullable().Precision(10)
.GeneratedBy.Identity();
Map(x => x.Description).Column("Description").Nullable()
.Generated.Never().CustomType(typeof (string)).Access
.Property().Length(250);
Map(x => x.Name).Not.Nullable().Generated.Never().CustomType("string")
.Access.Property().Column("Name").Length(50);
References(x => x.ParentCategory).Column("ParentCategoryId");
HasMany(x => x.ChildCategories).KeyColumn("ParentCategoryId").Inverse()
.Cascade.All();
}
}
Also you cannot be sure that the Id with value 1 refers to the Parent Category, use the LInq provider to load the correct object.
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.
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.