I am trying to update an nHibernate object with a child collection using the .Update() method found on a hibernate session. The only thing that I can do with the current setup is add children, I can not modify them or remove them.
For clarification the objects and their mapping are as follows:
public class Parent {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ISet<Child> Children { get; set; } = new HashSet<Child>();
}
public class ParentMap: ClassMap<Parent>
{
public ParentMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.Children)
.AsSet()
.Inverse()
.Cascade.AllDeleteOrphan();
}
}
public class Child {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Parent Parent { get; set; }
}
public class ChildMap: ClassMap<Child>
{
public ChildMap()
{
Id(x => x.Id);
Map(x => x.Name);
References(x => x.Parent);
}
}
When I get changes from my UI layer and try to update the already existing object using:
using (var tx = _session.BeginTransaction())
_session.Update(newParent);
tx.Commit();
}
Here newParent is a transient object (obtained from the database in an earlier session andd shown in the UI) containing the same identifier as the object I would like to update, but with changes to the child collection. Somehow using this approach I can only add children, but not modify or remove them.
Where is my mistake?
Most likely what is happening to you, is that instead of modifying the set that NHibernate has instantiated in your Parent entity, you are replacing it all together by a new instance of HashSet.
When you save or get an entity from NHibernate, your Children ISet gets loaded with an instance of a PersistentGenericSet (that implements ISet) which has the responsibility of helping with this change tracking for your collection.
In short, do not assign to the Children property. In fact, make the setter protected.
Just Add() or Remove() or Clear() it as required.
Related
In Criteria I do SetFetchMode as Lazy but still fetching all items, how can I fix this?
public class MenuItem : BaseClass<MenuItem>
{
public virtual int MenuItemId { get; set; }
public virtual string Text { get; set; }
public virtual IList<MenuItem> Children { get; set; }
public virtual MenuItem Parent { get; set; }
public MenuItem()
{
Children = new List<MenuItem>();
}
}
class MenuItemMap : ClassMap<MenuItem>
{
public MenuItemMap()
{
Id(x => x.MenuItemId);
Map(x => x.Text);
HasMany(x => x.Children).KeyColumn("ParentId").Not.LazyLoad().Fetch.Select();
References(x => x.Parent).Not.LazyLoad().Fetch.Select();
}
}
using (var session = NHibernateHelper<T>.OpenSession())
{
var CC = session.CreateCriteria(typeof(T));
CC.SetFetchMode("Children", FetchMode.Lazy);
return CC.List<T>();
}
I have to say, that this is not possible. Our JAVA brother Hibernate (from which NHibernate was ported into .NET) seems to have that option:
Hibernate: Enabling lazy fetching in Criteria API
But NHibernate does not support that. Chek also Override lazy loading behavior 'lazy=false'.
What we can do, is to bet on Projections. This way we really assemble exactly one SQL statement and get a list of Transformed objects (semi-filled, dependent on amount of selected properties)
16.6. Projections
Here is an example how to build a deep projections (including References/many-to-one)
Here is how to transform them into originally mapped object Graph.
I am getting this error on return of ByParameter because of KeyColumn I guess, how can I make this work?
could not resolve property: ParentId of: Entity.MenuItem
Entity.MenuItem.READ.ByParameter("ParentId", 3);
Code:
public static IList<T> ByParameter(String Parameter, Object Value)
{
using (var session = NHibernateHelper<T>.OpenSession())
{
var conjunction = new Conjunction();
conjunction.Add(Restrictions.Eq(Parameter, Value));
return session.CreateCriteria(typeof(T)).Add(conjunction).List<T>();
}
}
class MenuItemMap : Mapper<MenuItem>
{
public MenuItemMap()
{
Id(x => x.MenuItemId);
Map(x => x.Text);
HasMany(x => x.Children).KeyColumn("ParentId").Fetch.Select();
References(x => x.Parent).Fetch.Select();
}
}
Based on the Exception I would say, that we can face this issue in case - that the calling side looks like this:
var list = ByParameter<MenuItem>("ParentId", 123);
And because the snippet above does not show that class MenuItem contains ValueType (non-reference) property ParentId:
public class MenuItem
{
// the <id>
public virtual int MenuItemId { get; set; }
// References (many-to-one)
public virtual MenuItem Parent { get; set; }
// this seems to be not presented on MenuItem
// public virtual int ParentId { get; set; }
// HasMany (one-to-many)
public virtual IList<MenuItem> Children { get; set; }
Solution(s)
Extend model
We can add that into model
public virtual int ParentId { get; set; }
And extend the mapping
// many-to-one is using the same column - so this will be WRITABLE
References(x => x.Parent).Fetch.Select();
// just a ValueType property - READONLY
Map(x => x.ParentId)
.Readonly() // or .Update(false).Insert(false)
;
this would work now
var list = ByParameter<MenuItem>("ParentId", 123);
Just change the param
In fact, this would be the most simple solution... Change the calling side to be targeting existing mapping:
var list = ByParameter<MenuItem>("Parent.MenuItemId", 123);
Because Parent's property MenuItemId (the <id> or Id()) is presented (it is the column ParentId) - we do not need JOIN. NHibernate will generate expectable simple query
If you are not using conventions to change the column name in the Reference method. The default will be Parent_id, you must to specify as ParentId:
References(x => x.Parent).Column("ParentId").Fetch.Select();
I have a simple many-to-many relationship using Fluent NHibernate and it is working pretty fine.
Here is my first class mapping:
public LobMapping()
{
...
HasManyToMany(x => x.Commodities)
.Table("PPBSYS.LOB_COMMODITY")
.ParentKeyColumn("LOB_ID")
.ChildKeyColumn("COMMODITY_ID")
.Cascade.SaveUpdate()
.LazyLoad();
...
}
Here is my second class mapping:
public CommodityMapping()
{
...
HasManyToMany(x => x.Lobs)
.Table("PPBSYS.LOB_COMMODITY")
.ParentKeyColumn("COMMODITY_ID")
.ChildKeyColumn("LOB_ID")
.Inverse()
.Cascade.All()
.LazyLoad();
...
}
And finally, my Lob object has a list of commodities:
public class Lob
{
...
public virtual IEnumerable<Commodity> Commodities { get; set; }
...
}
However I am not happy with the fact that I must reference the entire commodity inside the Lob class. I really would like to do:
var lob = new Lob();
lob.Commodities = new [] { new Commodity { CommodityId = 1 }};
repository.SaveLob(lob);
But if I run the code above, NHibernate will try to update the Commodity table setting the columns to null for the commodity with ID = 1.
So actually I must get the entire object, before saving the Lob:
var lob = new Lob();
lob.Commodities = new [] { repostitory.GetCommodit(1) };
repository.SaveLob(lob);
And I know that the commodity exists, because the user just have selected them.
It is possible to accomplish my task?
I assume your repository is calling session.Get<T>(id) under the covers. There is also session.Load<T>(id).
lob.Commodities = new [] { session.Load<Commodity>(1) };
From the NHibernate documentation on Load()...
If the class is mapped with a proxy, Load() returns an object that is an uninitialized proxy and does not actually hit the database until you invoke a method of the object. This behaviour is very useful if you wish to create an association to an object without actually loading it from the database.
Daniel's answer sounds promising, let me know if in Save the proxy won't hit the database to fill all the properties of the entity.
Another answer would be a little forcefully on Nhibernate, i tested it and it's working,
working code :
public class Com
{
public virtual Guid ID { get; set; }
public virtual string Name { get; set; }
public virtual IList<Lob> Lobs { get; set; }
}
public class Lob
{
public virtual Guid ID { get; set; }
public virtual string Name { get; set; }
public virtual IList<Com> Coms { get; set; }
}
class LobMap : ClassMap<Lob>
{
public LobMap()
{
Id(x => x.ID).GeneratedBy.GuidComb();
Map(x => x.Name);
HasManyToMany(x => x.Coms)
.Table("LOB_COM")
.ParentKeyColumn("LOB_ID")
.ChildKeyColumn("COM_ID")
.Cascade.SaveUpdate()
.LazyLoad();
}
}
class ComMap : ClassMap<Com>
{
public ComMap()
{
Id(x => x.ID).GeneratedBy.GuidComb();
Map(x => x.Name);
HasManyToMany(x => x.Lobs)
.Table("LOB_COM")
.ParentKeyColumn("COM_ID")
.ChildKeyColumn("LOB_ID")
.Inverse()
.Cascade.All()
.LazyLoad();
}
}
Now - I set a dummy entity to mimic the connection table and map it :
public class ComLobConnection
{
public virtual Guid ComID { get; set; }
public virtual Guid LobID { get; set; }
}
public class ComLobConnectionMap : ClassMap<ComLobConnection>
{
public ComLobConnectionMap()
{
Table("LOB_COM");
Id();
Map(x => x.ComID,"COM_ID");
Map(x => x.LobID,"LOB_ID");
}
}
Notice that i'm mapping to exact same fields and Table as the ManyToMany, with no ID set up (that's the empty Id() call)
Now all that's left is to save a ComLobConnection and it will be added to the Com.Lobs and Lob.Coms
Saved some test Com,Lob
var testCom = new Com() { Name = "TestCom" };
var testLob = new Lob() { Name = "TestLob" };
session.SaveOrUpdate(testCom);
session.SaveOrUpdate(testLob);
after save - took their ID's and saved a connection to test
var testConnection = new ComLobConnection()
{
ComID = Guid.Parse("D3559F53-8871-45E9-901D-A22800806567"),
LobID = Guid.Parse("D372D430-2E61-44F2-BA89-A228008065F1")
};
session.SaveOrUpdate(testConnection);
This forced a new record in the manytomany table, and worked when getting both Lob and Com after.
I'm not recommending this :), It just interested me if it can be done, and it can.
This way you will not hit the DB for loading Coms or Lobs that you know exist to save their connection.
Hopefully Load() will not hit the DB for that too :)
Edit : can be done with any kind of ID
My question is, is there a possible Fluent NHibernate mapping for Parent and Child objects that does not require the Child object to have a Parent object property? I haven't figured out how to map the reference back to the Parent. When I call Create with the mappings as-is, I get an exception because the Child object does not have the required foreign key (required in the data store) back to the Parent.
I have two POCO classes:
public class Parent
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Child> Childs { get; set; }
}
public class Child
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual int ParentId { get; set; }
}
And some mappings:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
this.Table("Parents");
this.Id(x => x.Id);
this.Map(x => x.Name);
this.HasMany(x => x.Childs).KeyColumn("ChildId").Cascade.AllDeleteOrphan();
}
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
this.Table("Childs");
this.Id(x => x.Id);
this.Map(x => x.Name);
// Needs some sort of mapping back to the Parent for "Child.ParentId"
}
}
And Create method:
public Parent Create(Parent t)
{
using (this.session.BeginTransaction())
{
this.session.Save(t);
this.session.Transaction.Commit();
}
return t;
}
I want to be able to create a Parent object that has a list of Child objects, but not have the Child objects have references back to their Parent (other than the Parent ID). I want to do this to avoid the circular reference from Parent to a list of Childs back to the Parent object, since that is causing issues with JSON serialization.
You can map these entities without problem, try this:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
this.Table("Parents");
this.Id(x => x.Id);
this.Map(x => x.Name);
this.HasMany(x => x.Childs).KeyColumn("ChildId").Cascade.AllDeleteOrphan();
}
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
this.Table("Childs");
this.Id(x => x.Id);
this.Map(x => x.Name);
this.Map(x => x.ParentId);
// if you have a reference of Parent object, you could map as a reference, for sample:
this.References(x => x.Parent).Column("ParentId");
}
}
When you get entities from ISession, do not serialize it to some format because these can be proxies of nhibernate instead entities objects. Try to create DTO (Data Transfer Object) classes and convert these entities to a DTO object, and serialize it. You will avoid circular references. For sample:
public class ParentDTO
{
public int Id { get; set; }
public string Name { get; set; }
public int ParentId { get; set; }
/* here you just have the primitive types or other DTOs,
do not reference any Entity type*/
}
And when you need to read the values to share the serialized value:
var dto = ISession.Query<Parent>()
.Select(x =>
new ParentDTO() {
Id = x.Id,
Name = x.Name,
ParentId = x.ParentId)
.ToList();
Get this result from the Data Access Layer and try to serialize, for sample:
var result = Serialize(dto);
New to FluentNHibernate =D
I have a parent/children classes as follows:
public class Parent
{
public virtual int ID { get; private set; }
public virtual string Name { get; set; }
public virtual IList<Child> Children { get; set; }
}
public class Child
{
public virtual int ID { get; private set; }
public virtual string Name { get; set; }
public virtual Parent ActiveParent { get; set; }
}
With mappings of:
public ParentMap()
{
Id(x => x.ID);
Map(x => x.Name);
HasMany(x => x.Children)
.Inverse();
.Cascade.All();
}
public ChildMap()
{
Id(x => x.ID);
Map(x => x.Name);
//Map(x => x.ActiveParent)
// .Column(ParentID);
}
The commented out area of the child map is the question I'm currently having trouble with. I'd like to be able to create a child object and call its' parent(ie, someChild.ActiveParent), but am unsure on how to map this via the fluent interface.
The table structure for the child table holds a parentid, with the intent of lazy loading the parent object if called. Any help is always greatly appreciated.
References(x => x.Parent);
Adding to mxmissile's answer, you will want to add a LazyLoad() to the end of the References() call, and also you might want to do something like this in your configuration:
.Mappings(m =>
m.FluentMappings.AddFromAssemblyOf<ParentMap>()
.ConventionDiscovery.Add(ForeignKey.EndsWith("ID")))
The last line instructs Fluent NHibernate to expect foreign keys named like ParentID rather than the default (Parent_Id ?), so you no longer need to specify the column name explicitly in every relationship mapping.