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.
Related
This is supposed to be a simple 1-N relationship, however I am not able to save the children as NHibernate uses null as a value in the insert statement of the child.
public class Event
{
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<EventParameter> Parameters{ get; set; }
public Event() { Parameters = new List<EventParameter>(); }
}
[Serializable]
public class EventParameter : Parameter
{
public virtual Event Event { get; set; }
public virtual string ComparisonMethod { get; set; }
public override bool Equals(object obj) {}
public override int GetHashCode() {}
}
The mappings are like this
public EventMapping()
{
Table(...);
Id(x => x.Id)
.Column(...)
.GeneratedBy
.Custom<global::NHibernate.Id.SequenceGenerator>(builder => builder.AddParam("sequence", "..."));
Map(x => x.Name).Column("Name").Length(100);
HasMany<EventParameter>(x => x.Parameters)
.KeyColumns.Add(...)
.Inverse()
.Cascade.All();
}
public EventParameterMapping()
{
Table(....);
CompositeId()
.KeyProperty(x => x.Event.Id, "...")
.KeyProperty(x => x.ParameterId, "...");
References(x => x.Event);
}
The Insert statement for the parent is correct, however for the child it is not.
INSERT INTO ...
(...columns...)
VALUES (..., null, ...)
Before this happens I get the following warning: Unable to determine if entity is transient or detached; querying the database. Use explicit Save() or Update() in session to prevent this.
I do use Save() in the transaction. Any ideas why this happens?
Solution here is suprisingly simply. We have to use inverse="true" mapping:
HasMany<EventParameter>(x => x.Children)
.KeyColumns.Add("...")
.Inverse()
.Cascade.All();
But, this is kind of optimization, which would require both sides of relation to be always properly set in our C# code:
var parent = ...;
var child = new Child();
// both sides MUST be set
parent.Children.Add(child);
child.Parent = parent;
Please, observe this for more details
Inverse = “true” example and explanation
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()
);
I am trying to create a parent object that has multiple children in a 1 to many relationship. I am not referencing the Parent object on the child object, instead I am mapping its keycolumn as a field.
When I try to save this object for the first time, it works as expected without any issues (cascading all the Id's and children). When I try to get the object from the database, update some properties on it and re-save it again, it fails. The actual error message I am getting is "Could not delete collection".
The error above is due to the fact that it is trying to set the "ParentId" field on the child objects to NULL (which violates the FK constraint I have in the db). If I remove this constraint from the DB, the end result is what I want; however, I do not want it to perform this update (setting parent id to null) at all and I'm not sure why it is. From what I can tell in the SQL code it is generating and sending to the DB, everything else appears to be correct and it would all work if it wasnt for that last update statement.
Obviously, I must have something wrong with my mapping but I cannot figure out what. I tried adding Not.KeyUpdate() but that simply made it not generate a key at all. Does anyone have any ideas what I am doing wrong??
Thanks in advance, I really appreciate it!!!
Please see below for my mapping:
public class Parent
{
public Parent()
{
Children = new List<Child>();
}
public virtual Guid Id { get; set; }
public virtual IList<Child> Children { get; set; }
}
public class Child
{
public virtual Guid Id { get; set; }
public virtual Guid ParentId { get; set; }
}
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Table("Parent");
Id(x => x.Id);
HasMany(x => x.Children).KeyColumn("ParentId").Cascade.SaveUpdate().Not.LazyLoad();
}
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
Table("Child");
Id(x => x.Id);
Map(x => x.ParentId);
}
}
This is caused by the fact, that the Collection of children is not marked as inverse="true".
What you can do is: I. to remove the constraint from DB. NHiberante simply must (without the inverse setting) do 2 steps. Firstly update record to break relation, secondly (due to cascades) alse delete the item
II. Change the mapping, and entities. Like this:
A Child must have reference to the Parent:
public class Child
{
public virtual Guid Id { get; set; }
public virtual Guid ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
Mapping then will be like this:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Table("Parent");
Id(x => x.Id);
HasMany(x => x.Children)
.KeyColumn("ParentId")
.Cascade.SaveUpdate()
.Not.LazyLoad()
.Inverse() // here we do have the inverse setting
;
}
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
Table("Child");
Id(x => x.Id);
Map(x => x.ParentId).Not.Insert().Not.Update(); // this is readonly now
References(x => x.Parent).Column("ParentId"); // just a Parent is writable
}
}
Now, you have to all the time properly set the relation in the C# code. I.e. if child is added to Parents collection, it should also get set the Parent reference
parent.Children.Add(child);
child.Parent = parent;
NHibernate will now issue only one statement, to delete child from its table
I have the following classes:
public class Track
{
public virtual int Id { get; set; }
public virtual Track MainMix { get; set; }
public virtual IEnumerable<Track> SubMixes { get; set; }
public virtual IList<FileVersion> Files { get; set; }
}
public class FileVersion
{
public virtual int Id { get; set; }
public virtual Track Track { get; set; }
}
And the following mappings:
public class TrackMap : ClassMap<Track>
{
public TrackMap()
{
Id(x=>x.Id);
References(x => x.MainMix);
HasMany(x => x.SubMixes)
.Inverse()
.Cascade.All()
.KeyColumn("MainMix_id");
HasMany(a => a.Files)
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.All();
}
}
public class FileVersionMap : ClassMap<FileVersion>
{
public FileVersionMap()
{
Id(x => x.Id);
References(x => x.Track);
}
}
There is omitted code for the sake of simplicity. The Track table has a "MainMix_id" column that is a self referencing column for a parent/child relationship among Track records.
When I try to fetch a track from the database the NHProfiler tells me that Nhibernate tries to fetch the fileversions of that track with the following query:
SELECT files0_.MainMix_id as MainMix9_1_,
files0_.Id as Id1_,
files0_.Id as Id9_0_,
files0_.Track_id as Track8_9_0_
FROM [FileVersion] files0_
WHERE files0_.MainMix_id = 3 /* #p0 */
It seems like it has confused the parent id column of the Track table with its primary key column. When I remove References(x => x.MainMix) from the Track mapping the query is correct, but I don't have the parent track record returned.
Let me know if I can clarify this any more and thanks in advance for your help!
Does this make a difference?
TrackMap :
References(x => x.MainMix).Column("MainMix_id");
FileVersionMap :
References(x => x.Track).Column("Track_id");
I have a class MoneyCompositeUserType : ICompositeUserType
Which I use like so in a mapping:
public InvoiceMap()
{
Table("Invoices");
Id(x => x.Id);
Map(x => x.Customer);
Map(x => x.Number);
Map(x => x.TotalValue)
.CustomType(typeof(MoneyCompositeUserType))
.Columns.Clear()
.Columns.Add("TotalValue_Amount", "TotalValue_Currency");
}
And here is the class:
public class Invoice
{
public virtual int Id { get; set; }
public virtual int Number { get; set; }
public virtual string Customer { get; set; }
public virtual Money TotalValue { get; set; }
}
I thought that the value would be lazy loaded, that's the point of the virtual right? But the NullSafeGet method of the composite user type is called when the item is loaded. Here is my failing test:
using (var session = NHibernateHelper.OpenSession())
{
var fromDb = session.Get<Invoice>(invoice.Id);
Assert.IsFalse(NHibernate.NHibernateUtil.IsPropertyInitialized(fromDb, "TotalValue"));
}
Why is that property not being lazy loaded?
I thought that the value would be lazy loaded, that's the point of the virtual right?
Not exactly -- NHibernate needs your properties to be virtual so that it can use a proxy class in place of your class to enable lazy loading. Lazy loading is not enabled just because a property is marked virtual.
I believe all you should have to do is mark the individual property with .LazyLoad in your mapping (see lazy properties for more information):
Map(x => x.TotalValue)
.LazyLoad() // <-----
.CustomType(typeof(MoneyCompositeUserType))
.Columns.Clear()
.Columns.Add("TotalValue_Amount", "TotalValue_Currency");