NHibernate mapping - self referencing: parent and children - nhibernate

I am trying to have this kind of Model:
public class Activity
{
public virtual int ID { get; set; }
public virtual int? ParentID { get; set; }
public virtual int? RootID { get; set; }
public virtual Activity Parent { get; set; }
public virtual Activity Root { get; set; }
public virtual IList<Activity> Children { get; set; }
}
If you are looking at it from a structure point of view, it is a tree.
The root element does not have a parent or a root, but may have children.
Any of its children must have a parent and a root (for the first level children root = parent)
The mapper is like this:
public class ActivityMap : ClassMapping<Activity>
{
public ActivityMap()
{
Table("activity");
Lazy(true);
Id(x => x.ID, map => map.Generator(Generators.Identity));
ManyToOne(x => x.Root, map => { map.Column("RootID"); map.Cascade(Cascade.All); });
Bag(x => x.Children,
mapping =>
{
mapping.Inverse(false);
mapping.Lazy(CollectionLazy.Lazy);
mapping.Key(k => k.Column("ParentID"));
mapping.Cascade(Cascade.All);
},
mapping => mapping.ManyToMany(map=>map.Class(typeof(Activity)))
);
}
}
The problem is when I try to fetch the children, the sql statement looks like:
SELECT children0_.ParentID as ParentID1_,
children0_.elt as elt1_,
activity1_.ID as ID55_0_,
activity1_.TaskID as TaskID55_0_,
activity1_.ActivityTypeID as Activity3_55_0_,
activity1_.StateID as StateID55_0_,
activity1_.Continueforward as Continue5_55_0_,
activity1_.Ordernumber as Ordernum6_55_0_,
activity1_.IsDeleted as IsDeleted55_0_,
activity1_.Created as Created55_0_,
activity1_.Modified as Modified55_0_,
activity1_.StartTime as StartTime55_0_,
activity1_.EndTime as EndTime55_0_,
activity1_.Progress as Progress55_0_,
activity1_.RootID as RootID55_0_
FROM Children children0_ left outer join activity activity1_ on children0_.elt=activity1_.ID WHERE children0_.ParentID=?
First of all it seems that it is looking for the Children table which does not exist. Should be Activity table
Second: I am not sure what is with that "elt" column... it does not exist anywhere
Anyone has an idea how to make this mapping?
Later Edit:
found in answer to the second question:
NHibernate elt field

In my later edit I have the answer to my second question.
And for the second question, the solution that I found is to give up to the relation with the Root entity
ManyToOne(x => x.Root, map => { map.Column("RootID"); map.Cascade(Cascade.All); });
and replace it with
Property(x => x.RootID);
because I have no need to have the entire entity for the Root.
Also I have changed the bag for Children like this:
Bag(x => x.Children,
mapping =>
{
mapping.Inverse(false);
mapping.Lazy(CollectionLazy.Lazy);
mapping.Key(k => k.Column("ParentID"));
mapping.Cascade(Cascade.All);
},
mapping => mapping.OneToMany()
);

Related

NHibernate map by code with OneToMany association: Select is inefficient and Inserts fail (NHibernate.StaleStateException)

I have a parent class and a child class. One Child is always related to just one parent, but a parent can have multiple children:
public class Parent
{
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Child> Children { get; set; } = new List<Child>();
}
public class Child
{
public virtual int Id { get; set; }
public virtual string ParentId { get; set; }
public virtual string Name { get; set; }
}
I'm using the latest version of NHibernate 5.1.3 and mapping by code:
internal class ParentMapping : ClassMapping<Parent>
{
public ParentMapping()
{
Table("Parent");
Id(x => x.Id);
Property(x => x.Name);
Bag(
x => x.Children,
map =>
{
map.Key(km => km.Column("ParentId"));
map.Lazy(CollectionLazy.NoLazy);
map.Cascade(Cascade.Persist);
map.Inverse(true);
},
x => x.OneToMany());
}
}
internal class ChildMapping : ClassMapping<Child>
{
public ChildMapping()
{
Table("Child");
Id(x => x.Id, x => x.Generator(Generators.Identity));
Property(x => x.ParentId);
Property(x => x.Name);
}
}
Queries work but are quite inefficient. Instead of creating a single JOIN statement to query the children together with their parent, an explicit SELECT is made to retrieve the Child objects.
Even worse, inserts result in the following error:
NHibernate.StaleStateException: 'Batch update returned unexpected row count from update; actual row count: 0; expected: 3'
Here's a sample of a query:
using (var session = _sessionProvider.GetSession())
return session.Query<T>().ToList();
And that's the code to save a new item:
using (var session = _sessionProvider.GetSession())
{
session.Transaction.Begin();
session.Save(newEntity);
session.Transaction.Commit();
}
So everything's pretty easy.
I assume, the Bag() configuration in ParentMapping needs to be fixed. What am I doing wrong?
Change the fetch strategy of the bag mapping to join will generate a join query, like this:
Bag(
e => e.Children,
map => {
map.Key(km => km.Column("ParentId"));
// map.Lazy(CollectionLazy.NoLazy);
// change fetch str
map.Fetch(CollectionFetchMode.Join);
map.Cascade(Cascade.Persist);
map.Inverse(true);
},
x => x.OneToMany()
);
And you have the collection persist as inverse (map.Inverse(true);), you should have a many to one mapping for Parent in your Child class like this:
public class Child {
public virtual int Id { get; set; }
// public virtual string ParentId { get; set; }
public virtual Parent Parent { get; set; }
public virtual string Name { get; set; }
}
Then map the Parent property as ManyToOne like this:
public ChildMapping() {
// other mapping goes here
ManyToOne(
x => x.Parent,
map => {
map.Column("ParentId");
map.Class(typeof(Parent));
map.Fetch(FetchKind.Join);
}
);
}
But nhibernate do not include children of parent by default (maybe it is too heavy), and if you want to query children with parent instance, you can query like this:
using (var session = OpenSession()) {
var query = session.Query<Parent>().Select(p => new {
Parent = p, Children = p.Children
});
var data = query.ToList();
}
For saving entities to database, should do like this:
try {
// save parent first
var parent = new Parent();
parent.Name = "Parent object";
session.Save(parent);
// then save child
var child = new Child();
child.Name = "Child object";
child.Parent = parent;
session.Save(child);
session.Flush();
tx.Commit();
}
catch (Exception) {
tx.Rollback();
throw;
}

NHibernate intermediate table mapping by code

I know that there are similar questions to this topic on stackoverflow, but none of them could solve my problem. I'm stuck with it, for at least two days now. So here is my question:
I have two tables with a Many-To-Many relationship, like this:
Department * - * Person
I don't want to use a Many-To-Many relationship, but two Many-To-One and define the link table as entity.
The link table should be called "Lam".
My entities
public class Department
{
public virtual int Id { get; set; }
public virtual IList<Lam> Lams { get; set; }
}
public class Person
{
public virtual int Id { get; set; }
public virtual IList<Lam> Lams { get; set; }
}
public class Lam
{
public virtual Department Department { get; set; }
public virtual Person Person { get; set; }
}
My mappings
public class DepartmentMapping : ClassMapping<Department>
{
public DepartmentMapping()
{
Id(x => x.Id, map => map.Generator(Generators.Native));
Bag(x => x.Lams, col =>
{
col.Key(k => k.Column("DepartmentId"));
col.Inverse(true);
}, r => r.OneToMany());
}
}
public class PersonMapping : ClassMapping<Person>
{
public PersonMapping()
{
Id(x => x.Id, map => map.Generator(Generators.Native));
Bag(x => x.Lams, col =>
{
col.Key(k => k.Column("PersonId"));
col.Inverse(true);
}, r => r.OneToMany());
}
}
Pairing mapping:
public class LamMapping : ClassMapping<Lam>
{
public LamMapping()
{
ManyToOne(x => x.Department, map =>
{
map.Column("DepartmentId");
});
ManyToOne(x => x.Person, map =>
{
map.Column("PersonId");
});
}
}
If I try to run my application, I get the following error message:
Incorrect syntax near 'Index'. If this is intended as a part of a
table hint, A WITH keyword and parenthesis are now required. See SQL
Server Books Online for proper syntax.
Could someone please just tell me, whats wrong with my code?
The mapping as is is correct (at least the same was working for me). So, the question is from where is coming your exception?.
Let's have a SQL SELECT query over your simplified table/entity Person:
SELECT id FROM Person
That would work.
But if - in a not shown part of your mapping - exists Person's some property, let's say Index like this:
// entity
public class Person
{
...
// C# property named Index
public virtual int Index { get; set; }
...
// mapping
public class PersonMapping : ClassMapping<Person>
{
public PersonMapping()
{
...
// that by default would be column Index
Property(x => x.Index)
That would lead to SELECT like this
SELECT id, Index FROM Person
And in SQL Server worlds, that will cause error:
Msg 1018, Level 15, State 1, Line 1
Incorrect syntax near 'Index'. If this is intended as a part of a table hint, A WITH keyword and parenthesis are now required.
So, because your mapping shown above is correct, I would suspect some part like this
If this is the case, we can use
...
Property(x => x.Index, x => { x.Column("[Index]"); });

NHibernate mapping by code: How to map IDictionary?

How can I map these Entities using mapping-by-code:
public class Foo
{
public virtual IDictionary<Bar, string> Bars { get; set; }
}
public class Bar
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
I found this thread, but it does not map an entity, only simple types. I tried many mappings, including automapping:
Map(x => x.Bars,
m =>
{
m.Key(k => k.NotNullable(true));
m.Cascade(Cascade.All);
},
But most of them throw these two errors:
Foreign key (Bars [idx])) must have same number of columns as the referenced primary key (Bars [FooId, idx]).
An association from the table FoosToStrings refers to an unmapped class: System.String.
Any help will be highly appreciated. Thanks. :)
i think this should work
Map(x => x.Bars,
entryMap => entryMap.Key(k => k.Column("foo_id")),
keymap => keymap.ManyToMany(m => m.Column("bar_Id")),
elementMap => elementMap.Element(m => m.Column("value")));

Hierarchical entity parent key in NHibernate ByCode

Using NHibernate 3.2 ByCode configuration, I am attempting to map the following hierarchical entity:
public class BusinessType
{
public virtual Guid BusinessTypeId { get; set; }
public virtual Guid? ParentBusinessTypeId { get; set; }
public virtual String BusinessTypeName { get; set; }
public virtual ICollection<BusinessType> Children { get; set; }
}
with this ClassMapping:
public class BusinessTypeMapper : ClassMapping<BusinessType>
{
public BusinessTypeMapper()
{
Id(x => x.BusinessTypeId, x => x.Type(new GuidType()));
Property(x => x.ParentBusinessTypeId, x => x.Type(new GuidType()));
Property(x => x.BusinessTypeName);
Set(x => x.Children,
cm =>
{
// This works, but there is an ugly string in here
cm.Key(y => y.Column("ParentBusinessTypeId"));
cm.Inverse(true);
cm.OrderBy(bt => bt.BusinessTypeName);
cm.Lazy(CollectionLazy.NoLazy);
},
m => m.OneToMany());
}
}
This works fine, but I'd rather be able to specify the key of the relation using a lambda so that refactoring works. This seems to be available, as follows:
public class BusinessTypeMapper : ClassMapping<BusinessType>
{
public BusinessTypeMapper()
{
Id(x => x.BusinessTypeId, x => x.Type(new GuidType()));
Property(x => x.ParentBusinessTypeId, x => x.Type(new GuidType()));
Property(x => x.BusinessTypeName);
Set(x => x.Children,
cm =>
{
// This compiles and runs, but generates some other column
cm.Key(y => y.PropertyRef(bt => bt.ParentBusinessTypeId));
cm.Inverse(true);
cm.OrderBy(bt => bt.BusinessTypeName);
cm.Lazy(CollectionLazy.NoLazy);
},
m => m.OneToMany());
}
}
The problem is that this causes NHibernate to generate a column called businesstype_key, ignoring the already-configured ParentBusinessTypeId. Is there any way to make NHibernate use a lambda to specify the relation, rather than a string?
I never need to navigate from children to parents, only from parents
to children, so I hadn't thought it necessary
then remove public virtual Guid? ParentBusinessTypeId { get; set; } completly. NH will then only create "businesstype_key" (convention) and no "ParentBusinessTypeId". if you want to change that then you have to specify your prefered columnname with cm.Key(y => y.Column("yourpreferredColumnName"));

Fluent nHIbernated - HasMany relationship in same table

I'm creating a model for a website top-menu structure -
I have a MenuObject model:
public class MenuObject
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
public virtual List<MenuObject> Children { get; set; }
}
and a mapping:
public mapMenu()
{
Id(x => x.Id)
.Not.Nullable();
Map(x => x.Title)
.Not.Nullable();
HasMany<MenuObject>(x => x.Children)
.AsList();
}
Basically i want to be able to create a "Top Level" Menu item then add some child items to it - in database terms, there should be a ParentId field that contains the ID of the parent Menu Item (if any - this could be null)
I'm struggling to get my head around how this should be defined in my object model. Also once i hav this configured, how would i go about saving children? Would it be something like
public void InsertChild(MenuObject parent, MenuObject child)
{
parent.Children.add(child)
session.SAve(parent)
.....
}
or would I have to save the child independantly and link it to the parent explicitly?
Edit *****
Thanks - so now i have the following in my model:
public virtual MenuObject Parent { get; set; }
public virtual List<MenuObject> Children { get; set; }
and this in the mapping:
HasMany(x => x.Children)
.AsList()
.Inverse()
.Cascade.All()
.KeyColumn("ParentId");
References(x => x.Parent, "ParentId");
I can now add children to parent itms in the following way:
oChild.Parent = oParent;
session.SaveOrUpdate(oParent);
session.Save(oChild);
transaction.Commit();
I think i'm onto a winner! Is that the best way to do it? THanks gdoron
or would i have to save the child independantly and link it to the parent explicitly?
Anyway you have to "link' it to it's parent, If don't want to get exceptions...
You will have to save the child independently only if you don't specify Cascade.Save\All() in the mapping:
HasMany<MenuObject>(x => x.Children)
.AsList().Inverse.Cascade.All(); // Inverse will increase performance.
You have to add a Parent property to connect the "child" to it's "Parent".
It's mapping is:
References(x => x.Parent);
P.S.
You don't have to write Not.Nullable on Id, It's the defaults.