NHibernate ThenFetchMany is retrieving duplicate children - nhibernate

I have a parent object with a child collection containing one element, the child collection contains a "grandchild" collection containing 3 elements.
I am loading the parent object from the database using NHibernate as follows
Parent parentObject = session.Query<Parent>()
.FetchMany(x => x.Children)
.ThenFetchMany(x => x.GrandChildren)
.Where(x => x.Id = "someparentid")
.Single();
What I'm finding is that there are duplicate children objects (3 in total) attached to the parent object when there should be only one. (There are 3 grandchild objects correctly attached to each child.) Eager loading the children collection only works correctly.
Do you know how I can achieve loading of the full parent object without duplicate children?

If you map Children and GrandChildren as set, you can avoid the cartesian product. You need to define Children and Grandchildren as collections:
public class Parent
{
...
public virtual ICollection<Child> Children { get; set; }
...
}
public class Child
{
...
public virtual ICollection<GrandChild> GrandChildren { get; set; }
...
}
And in the mapping (using FluentNHibernate):
public class ParentMapping : ClassMap<Parent>
{
public ParentMapping()
{
...
HasMany(x => x.Children)
.KeyColumn("ParentID")
.Inverse
.AsSet()
...
}
}
public class ChildMapping : ClassMap<Child>
{
public ChildMapping()
{
...
HasMany(x => x.GrandChildren)
.KeyColumn("ChildID")
.Inverse
.AsSet()
...
}
}

I was able to use the answer here using QueryOver, it correctly loads the objects while generating efficient SQL (selects per table instead of one huge join).

If you are using Linq, you can simplify it with this:
int parentId = 1;
var p1 = session.Query<Parent>().Where(x => x.ParentId == parentId);
p1
.FetchMany(x => x.Children)
.ToFuture();
sess.Query<Child>()
.Where(x => x.Parent.ParentId == parentId);
.FetchMany(x => x.GrandChildren)
.ToFuture();
Parent p = p1.ToFuture().Single();
Detailed explanation here: http://www.ienablemuch.com/2012/08/solving-nhibernate-thenfetchmany.html

You can't do it with NHibernate (I don't think you can do it with EF4 either) since your result is a Cartesian product. You're getting all results from all tables.
NHibernate doesn't know how to map the results from both collections back to the root. So for each Children, you get the same number of GrandChildren, and for each GrandChildren you end up with the same number of Children.

Related

How to get grandchild in Entity Framework Core

How to display children and grandchildren when 'parentItemNo' is given.
Below is my model: Any record (itemNo) can become a parent, child, or grandchild.
public partial class item
{
public string ItemNo { get; set; }
public string ParentItemNo { get; set; }
}
Below query returns children: Would like to display grandchildren as well.
var result = Context.Items.Where(p => p.ParentItemNo == parentItemNo).Select(p => p.ItemNo).ToListAsync(); ;
Navigation properties.
Given an Item class which can have multiple levels where each item can have an optional Parent:
public class Item
{
[Key]
public string ItemNo { get; set; }
[ForeignKey("Parent")]
public string ParentItemNo { get; set; }
public virtual Item Parent { get; set; }
public virtual ICollection<Item> Children { get; set; } = new List<Item>();
}
From here I would recommend setting up the mapping explicitly, either in the DbContext OnModelCreating event or using an EntityTypeConfiguration implementation:
modelBuilder.Entity<Item>()
.HasOptional(x => x.Parent)
.WithMany(x => x.Children);
When you want to get the children and grand-children for an item:
var children = await context.Items
.Include(x => x.Children) // eager load children
.ThenInclude(x => x.Children) // eager load grandchildren
.Where(x => x.ItemNo == parentItemNo)
.SelectMany(x => x.Children)
.ToListAsync();
Normally though you'd load the parent with it's children and grandchildren eager loaded:
var parent = await context.Items
.Include(x => x.Children)
.ThenInclude(x => x.Children)
.SingleAsync(x => x.ItemNo == parentItemNo);
For eager loading you need to plan ahead with what data you expect to need, otherwise you should ensure lazy loading is enabled and available if it's possible that a grandchild could itself have children items. Iterating down to this level would either attempt a lazy load (possibly resulting in errors if the object is orphaned from it's DbContext) or return no data or a partial representation of data if the DbContext was already tracking some of the great grandchildren that it could fill in.
Edit: if you just want to get the Item Numbers of the children and grandchildren then you don't need to use the eager loading with Include as that would be to return entities and their related entities. Projection with Select / SelectMany does not need this.
To select the ItemNos of any Children and Grandchildren:
var childDetails = await context.Items
.Where(x => x.ItemNo == parentItemNo)
.SelectMany(x => x.Children.Select(c => new
{
c.itemNo,
GrandChildItemNos = c.Children.Select(gc => gc.itemNo).ToList()
}).ToListAsync();
What this will give you is a list of anonymous types containing each child's Item #, and the list of that child's grandchild item #s. From there you can combine all of the item #s:
List<string> itemNumbers = childDetails.Select(x => x.itemNo).ToList();
foreach(var child in childDetails)
itemNumbers.AddRange(child.GrandChildItemNos);
This would combine all of the item numbers for the children and their grandchildren into a single list. From there if relationships to a particular child could be doubled up (2 children share the same grandchild) you can add a Distinct() to the end to remove duplicates.
It may be possible to select both child and grand child in the EF Linq operation using something like a Union but if possible I'd expect it to be a fair bit more complex and might be difficult to modify and understand later on.

Nhibernate query for items that have a Dictionary Property containing value

I need a way to query in Nhibernate for items that have a Dictionary Property containing value.
Assume:
public class Item
{
public virtual IDictionary<int, string> DictionaryProperty {get; set;}
}
and mapping:
public ItemMap()
{
HasMany(x => x.DictionaryProperty)
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore)
.AsMap<string>(
index => index.Column("IDNumber").Type<int>(),
element => element.Column("TextField").Type<string>().Length(666)
)
.Cascade.AllDeleteOrphan()
.Fetch.Join();
}
I want to query all Items that have a dictionary value of "SomeText". The following example in Linq fails:
session.Query<Item>().Where(r => r.DictionaryProperty.Any(g => g.Value == "SomeText"))
with error
cannot dereference scalar collection element: Value
So is there any way to achieve that in NHibernate? Linq is not an exclusive requirement but its preffered. Not that I'm not interested to query over dictionary keys that can be achieved using .ContainsKey . Φορ this is similar but not the same
Handling IDictionary<TValueType, TValueType> would usually bring more issues than advantages. One way, workaround, is to introduce a new object (I will call it MyWrapper) with properties Key and Value (just an example naming).
This way we have to 1) create new object (MyWrapper), 2) adjust the mapping and that's it. No other changes... so the original stuff (mapping, properties) will work, because we would use different (readonly) property for querying
public class MyWrapper
{
public virtual int Key { get; set; }
public virtual string Value { get; set; }
}
The Item now has
public class Item
{
// keep the existing for Insert/Updae
public virtual IDictionary<int, string> DictionaryProperty {get; set;}
// map it
private IList<MyWrapper> _properties = new List<MyWrapper>();
// publish it as readonly
public virtual IEnumerable<MyWrapper> Properties
{
get { return new ReadOnlyCollection<MyWrapper>(_properties); }
}
}
Now we will extend the mapping:
HasMany(x => x.Properties)
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore)
.Component(c =>
{
c.Map(x => x.Key).Column("IDNumber")
c.Map(x => x.Value).Column("TextField")
})
...
;
And the Query, which will work as expected:
session
.Query<Item>()
.Where(r =>
r.Properties.Any(g => g.Value == "SomeText")
)
NOTE: From my experience, this workaround should not be workaround. It is preferred way. NHibernate supports lot of features, but working with Objects brings more profit

Nhibernate Updating - Solutions for updating children on an entity?

Looking for some advice on how to update a collection on an entity. In a web app - we have a multiselect listbox allowing a user to assign and remove child entities from a list of available entities. The user would select the relevant children to associate with the parent. i.e. Product with multiple Categories it could belong to. Once the user is satisfied, they submit and we update the entities.
What is the preferred way to update(delete removed children, add new children) to the collection taking performance into account. I would not want to run several sql statements to fetch each child and add it to the parent.
Cheers
Mappings Attached:
public class ParentMap : EntityMapping<Parent>
{
public ParentMap()
{
Map(x => x.Name);
HasMany(x => x.Children)
.Cascade.AllDeleteOrphan()
.Access.LowerCaseField(Prefix.Underscore);
}
}
public class ChildMap : EntityMapping<Child>
{
public ChildMap()
{
References(x => x.Parent);
}
}
public abstract class EntityMapping<TEntity> : ClassMap<TEntity> where TEntity : EntityBase
{
protected EntityMapping()
{
Id(x => x.Id, "Id")
.UnsavedValue("00000000-0000-0000-0000-000000000000")
.GeneratedBy.GuidComb();
OptimisticLock.Version();
Version(entity => entity.Version);
}
}
Establish a cascade relation between parent and child entities and force it to act on all operations like update, delete, etc. You must define cascade behavior in you HBM mapping files. For more info: http://nhibernate.info/doc/nh/en/index.html#mapping-declaration-mapping

NHibernate Criteria Collection Contains

I have a parent/child relationship mapped with a many-to-many set.
public class Parent
{
public ISet<Child> Children { get; set; }
}
public class Child {}
public class ParentMap : ClassMap<Parent>
{
HasManyToMany(x => x.Children)
.AsSet();
}
How can I write a query to select all of the parents that contain a given child? I would have guessed that it would be something like this, but this API does not exist:
Session.CreateCriteria<Parent>()
.Add(Expression.Contains("Children", child)
.List<Parent>();
I can't for the life of me find the answer anywhere. My brain is not fully functioning today and Google has so far failed me.
How about something like this?
Session.CreateCriteria<Parent>()
.CreateCriteria("Children")
.Add(Expression.Eq("Id", child.Id)
.List<Parent>();
or
Session.CreateCriteria<Parent>()
.CreateCriteria("Children")
.Add(Expression.In("Id", child.Id)
.List<Parent>();
so you can pass in an array of Ids.

HasMany<MySubtype> mapping returns supertype and all descendants?

I'm having a problem getting FluentNHibernate's HasMany<> mapping to behave as I would expect.
I have a class hierarchy of Child is a descendant of Parent, defined using table-per-hierarchy, discriminated by the class name in a column named 'TYPE'. An unrelated class Group contains a collection of Child elements.
So, the Group class is defined as:
public class Group
{
public virtual IList<Child> Children {get; protected set;}
public Group()
{
Children = new List<Children>();
}
}
My map in Fluent NHibernate looks like this:
public class GroupMap : SubclassMap<Group>
{
public GroupMap()
{
HasMany<Child>(x => x.Children)
.Not.LazyLoad()
.Inverse()
.Cascade.All();
}
}
I would expect that using Group.Children would result in a collection of objects of type Child, but it is returning a collection that contains objects of either type Parent or Child.
Looking at the SQL generated, the select statement is not differentiating based on the TYPE column in the table that holds the Parent and Child objects.
Changing my mapping to:
public class GroupMap : SubclassMap<Group>
{
public GroupMap()
{
HasMany<Child>(x => x.Children)
.Inverse()
.Not.LazyLoad()
.Where("Type = 'Child'")
.Cascade.All();
}
}
solves the problem, but it seems like a hack to me. Shouldn't the declaration "HasMany' infer that the results should be only of type Child?
Am I mapping my collections incorrectly?
The short answer is I've moved away from using FluentNHibernate mappings.
I'm back to using plain .hbm.xml mappings and much happier as a result.