Hi I am pretty new to Nhibernate and I am working on a project that is described as follow:
1 "Parents" table containing ParentName and a one to many ChildrenList using IList, with Primary Key set to ParentId
1 "Children" table , with a composite primary key that contains ParentID as one of the key
so I set up a one to many in Parents.hbm.xml like this
<class name="ParentName " table="Parents">
<id name="ParentName " column="ParentName ">
<generator class="assigned"/>
</id>
<property name="ParentAlpha" />
<bag name="ChildrenList" cascade="all">
<key column="ParentName" />
<one-to-many class="Children"/>
</bag>
and the Children.hbm.xml like this
<composite-id>
<key-many-to-one name="ParentName" class="Parent"/>
<key-property name = "ChildrenAlpha" />
<key-property name = "ChildrenAlpha2"/>
</composite-id>
<property name="ChildrenBeta" />
<property name="ChildrenGama" />
And currently im doing test on saving Parent obj with some list of Children in to a MySQL database using the session.SaveOrUpdate method, but it always fails and just said "cannot insert "the Children Obj
Here is my test code:
Parent parent = new Parent(){ParentAlpha= "ABC"};
Children children = new Children(){ChildrenAlpha = "AAA" ,ChildrenAlpha2 ="VBB"};
parent .ChildrenList.add(children); //IList add function
.....session.SaveOrUpdate(parent);
I have tested for one parent to many children, and the children primary key is set to a generated ChildrenId. and this works fine. But somehow I cannot do it using composite key, my guess is that as ParentName in Children is a primary key but will only be filled after ParentName in Parent has been filled, well sth like that.
Another Question is that if the above problem is solved, can i use this to retrieve the whole parent obj with the children list? ( I tried it for simple single PK case but seems not working when children is a composite key thing)
Parent parent= session.CreateCriteria(typeof(Parent))
.Add(Restrictions.Eq("ParentName", ParentName))
.UniqueResult<Parent>();
NHibernateUtil.Initialize(parent.ChildrenList);
Thanks!
Related
I know it has been asked for many times, i also have found a lot of answers on this website, but i just cannot get out this problem.
Can anyone help me with this piece of code?
Many thanks.
Here is my parent mapping file
<set name="ProductPictureList" table="[ProductPicture]" lazy="true" order-by="DateCreated" inverse="true" cascade="all-delete-orphan" >
<key column="ProductID"/>
<one-to-many class="ProductPicture"/>
</set>
Here is my child mapping file
<class name="ProductPicture" table="[ProductPicture]" lazy="true">
<id name="ProductPictureID">
<generator class="identity" />
</id>
<property name="ProductID" type="Int32"></property>
<property name="PictureName" type="String"></property>
<property name="DateCreated" type="DateTime"></property>
</class>
Here is my c# code
var item = _productRepository.Get(productID);
var productPictrue = item.ProductPictureList
.OfType<ProductPicture>()
.Where(x => x.ProductPictureID == productPictureID);
// reomve the finding item
var ok = item.ProductPictureList.Remove(productPictrue);
_productRepository.SaveOrUpdate(item);
ok is false value and this child object is still in my database.
Not 100% sure, but could be because you have defined ProductID as a property of ProductPicture, I assume this is the PK from the Product class. You don't need to add this again, it will be created by the relationship.
I'm not sure that your use of table="[ProductPicture]" in the set tag is right.
The one-to-many tag already establishes the link between ProductPictureList and ProductPicture.
I think the table attribute is generally for using a separate relationship table when modelling many-to-may relationships.
From nhibernate.info Doc:
table (optional - defaults to property name) the name of the
collection table (not used for one-to-many associations)
And:
A collection table is required for any collection of values and any
collection of references to other entities mapped as a many-to-many
association
I have two Nhibernate mappings for two classes, Category and Product. My Category class has two properties that are collections. The Children property is a collection of type Category which represents child categories (represents a category menu, typical parent child scenario). The second property on the Category class is a Products collection which represents all the products under a category.
What I am trying achieve is when I delete a category I want the category to deleted but not the product. So I want the product to be orphaned. i.e have its foreign key (CategoryId) in the Product table set to null. I don't want to delete a product just because I have deleted a category. I want to be able to reassign in at a later time to another category. My mappings representing the mentioned scenario are below.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="naakud.domain" namespace="naakud.domain">
<class name="Category">
<id name="Id">
<generator class="hilo" />
</id>
<version name="Version"/>
<property name="Name" not-null="true" unique="true" />
<set name="Products"
cascade="save-update"
inverse="true"
access="field.camelcase-underscore">
<key column="CategoryId" foreign-key="fk_Category_Product" />
<one-to-many class="Product" />
</set>
<many-to-one name="Parent" class="Category" column="ParentId" />
<set name="Children"
collection-type="naakud.domain.Mappings.Collections.TreeCategoriesCollectionType, naakud.domain"
cascade="all-delete-orphan"
inverse="true"
access="field.camelcase-underscore">
<key column="ParentId" foreign-key="fk_Category_ParentCategory" />
<one-to-many class="Category"/>
</set>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="naakud.domain" namespace="naakud.domain">
<class name="Product">
<id name="Id">
<generator class="hilo" />
</id>
<version name="Version" />
<property name="Name" not-null="true" unique="true" />
<property name="Description" not-null="true" />
<property name="UnitPrice" not-null="true" type="Currency" />
<many-to-one name="Category" column="CategoryId" />
</class>
</hibernate-mapping>
With this mapping, when I delete a category which has products associated with it I get the following constraint error.
The DELETE statement conflicted with the REFERENCE constraint "fk_Category_Product". The conflict occurred in database "naakud", table "dbo.Product", column 'CategoryId'.
The statement has been terminated.
However, when I remove the inverse=true attribute on the Products collection in the Category mapping then it works fine. My CategoryId foreign key in the products table is set to null and thus disassociating a product with a category. Which is what I want.
I have read about the inverse attribute and I understand it signifies the owning side of a relationship and updates/inserts/deletes are done in a different order which is why I think it solves my problem. So my question is, am I solving my problem in the correct way? How does this affect performance? (not much I suspect). Would it be better to have a uni-directional relationship without the many to one side and have the inverse attribute set to true to get better performance? Or am I going crazy and completely missing the point?
Another way of fixing the delete problem is by setting the many-to-one property to null on all the related entities to null before flushing.
I can think of at least two ways to do it:
In the same method that calls session.Delete(category), do:
foreach (var product in category.Products)
product.Category = null;
Using HQL:
session.CreateQuery(
"update Product set Category = null where Category = :category")
.SetParameter("category", category)
.ExecuteUpdate();
Update:
Here's a proof-of-concept implementation using an event listener.
I assume that you read about Inverse Attribute in NHibernate
As the error message says, your DELETE generates a conflict with the foreign key constraint, meaning that the DB cannot delete the Category as long as there are Products referencing that particular Category.
What you could do (if you can alter the DB schema) is applying "ON DELETE SET NULL" to your foreign key constraint. That way, when the DELETE is executed, the DB will automatically set all references in the Product table to NULL.
If you cannot modify the foreign key, then you would have little choice but to remove the inverse attribute. Doing so will result in NHibernate first setting the Product.Category reference to NULL and then deleting the Category.
If you need Product.Category fairly often then you should not get rid of the many-to-one attribute in Product.
Regarding the performance, that depends on how often you insert Products. Each insert will result in an additional update to set the foreign key. That should not be a problem, though.
Let's start with this mapping:
<component name="Location">
...
<property name="Settings" type="JsonUserType,...">
<column name="LocationSettingsType" />
<column name="LocationSettingsData" />
</property>
</component>
This maps to
TABLE Primary (
...
LocationSettingsType,
LocationSettingsData
...
)
and
class Location {
...
object Settings { get; set; }
}
Now, I want to extract settings into a separate table (because they are seldom here).
So I get
TABLE Primary (
...
LocationSettingsId,
...
)
TABLE Settings (
Id,
Type,
Data
)
Can I keep my C# classes the same?
Update: This is not a many-to-one relationship. As before, each location has zero or one settings, and each settings belong to at most one location.
I believe the closest thing that exists to this is the <map> mapping element; details are explained in this article.
If you want a one to many relationship on the Primary and Settings tables, you'll have to set a foreign key constraint first. Then you'll use the bag property in XML to map your tables. You will have an entity for each table.
See also this question on NHibernate/FluentNHibernate Property Bag.
I also recommend you purchase the NHibernate 2 for Beginners book. It helped me alot.
This is an old question but i had the same issue and looking to a solution I came here.
the component element can map several columns to several object models.
the join element can map several tables to an object model.
The main problem is that while the component cannot map columns from a different table where the model belong, the join cannot map the different table columns to a different object model.
The solution I found is to use both to achieve the map column of a different table to several object:
<class name="Primary" table="Primary">
<id name="Id">
<generator class="identity"/>
</id>
<property name="Name" />
...
<join table="Settings">
<key column="PrimaryId"/>
<component name="Location">
...
<property name="Settings" type="JsonUserType,...">
<column name="LocationSettingsType" />
<column name="LocationSettingsData" />
</property>
</component>
</join>
</class>
Reference:
NHibernate Join mapping element
NHibernate Component mapping element
I am working with nHibernate, and trying make sense of bag collections. My data structure is relatively straight-forward...
Entry:
<class name="Entry">
<id name="id" column="EntryId">
<generator type="guid.comb"/>
</id>
<property name="Name" column="Name"/>
<bag name="Results" table="Results" cascade="all">
<key column="EntryId" />
<one-to-many class="Result"/>
</bag>
</class>
Result:
<class name="Result">
<id name="id" column="ResultId">
<generator type="guid.comb"/>
</id>
<property name="Score" column="Score" />
<many-to-one name="Entry" class="Entry" cascade="all" />
</class>
What I would like to do, which doesn't seem to be working, is the following:
Entry entry = new Entry();
entry.Name = "Name";
// have tried saving at this point to:
// dbSession.SaveOrUpdate(entry);
Result result = new Result();
result.Score = 100;
entry.Results.Add(result);
dbSession.SaveOrUpdate(entry);
It seems to be creating the entry record in the database, but not the result record. In my database, I have EntryId as a foreign key in the Result table. Similarly, I would like to be able to remove the result object from the collection, and have it persist to the database. I thought the cascade feature took care of this, but not sure what I have done wrong...
EDIT
I now have it adding the result object into the database, but delete does not seem to work:
Entry entry = Entry.Load(id);
entry.Results.Remove(result);
dbSession.SaveOrUpdate(entry);
I have tried adding cascade="all-delete-orphan", but this seems to remove both parent and children. I just want it to delete the one entry object from the database??
In the end, this came down to my hbm file mappings not being correct.
Entry.hbm.xml
<bag name="Results" table="Result" lazy="false" inverse="true" cascade="all-delete-orphan">
<key column="EntryId"/>
<one-to-many class="Result"/>
</bag>
Result.hbm.xml
<many-to-one name="Entry" class="Entry" column="EntryId"/>
I originally had cascade="all-delete-orphan" on the many-to-one mapping, which was not correct. What happened was that all children and the parent record was being deleted.
I can now add and remove with the following:
Result r = new Result();
Entry entry = new Entry();
// AddResult method sets the Entry object of the Result
// result.Entry = this;
entry.AddResult(r);
session.SaveOrUpdate(entry);
To delete:
entry.Results.Remove(result);
session.SaveOrUpdate(entry);
To add to a collection you need to explicitly save the child object when it is added. Ditto when you delete an object from a collection.
So you would do:
entry.Results.Add(result);
session.Save(result);
session.Save(entry);
session.Flush();
The foreign key also has to be nullable. The reason why you have to do this is NHibernate has to save the child first with no association to the parent. Then when the parent is saved the foreign key column on the child gets updated with the parent's Id, creating the relation. This is because NHibernate may not have the needed parent id key value until the second operation (parent is saved) has completed.
I guess you have this part figured out.
Delete works the same way for different reasons - remove the child from the parent collection, then delete the child explicitly, then update the parent:
entry.Results.Remove(result);
session.Delete(result);
session.Update(entry);
session.Flush();
You removed result from the collection and updated the entry. That only tells Nhibernate to delete the relationship between the entry and the result - you never actually deleted the result object itself.
I notice that, in your collection, you have defined the FK column as:
<key column="EntryId" />
But you are not overriding the column in your many-to-one, which means you have two different columns (Entry and EntryId) for the same relationship.
This might be it or not... but it doesn't hurt to check :-)
if you are using Mapping by Code then use both Cascade.All and Cascade.DeleteOrphans options. unlike the xml mapping, there is no single option for "all-delete-orphan" in Mapping by Code.
Bag(x => x.Results, c =>
{
c.Key(k =>
{
k.Column("EntryId");
});
c.Cascade(Cascade.All | Cascade.DeleteOrphans);
}, r => r.OneToMany())
i have a collection in the mapping:
<bag name="Values" cascade="all-delete-orphan" lazy="false" inverse="true">
<key column="[TemplateId]"/>
<one-to-many class="MyNamespace.Value, MyLib"/>
</bag>
the Value object has a foreign key [TemplateId]. both entities has their generator set to "identity".
when i call session.Save() for the parent Template object, the Value objects has their [TemplateId] (the foreign key) set to zero, so an SQL exception appears.
how do i forse nhibernate to set the FK value for the child items to the value of the inserted parent object?
i've managed it myself:
the only thing i was needed to do is to design child object mapping and persistent the following way:
<many-to-one name="Template" class="MyNamespace.Template, MyLib"
column="[TemplateId]" not-null="true" />
so the child object has a reference to the parent instead of the parent's Id