Category and Subcategory have 1 to Many relationship. In update mode for category, I want to delete all existing subcategories and re-insert the new one (as scenario demands it). Deletion is happening fine, however the new child records are getting duplicated (If I've 2 new records for child, operation is inserting 2 + 2 records)
ISession session = NHibernateHelper.GetCurrentSession().GetSession(EntityMode.Poco);
using (var tx = session.BeginTransaction())
{
List<Vtsubcategory> oovtsc = new List<Vtsubcategory>();
oovtsc = oVtcategory.FkTocategory.Where(e => e.IdvtSubCategory != 0).ToList();
foreach (Vtsubcategory ooVtsubcategory in oovtsc)
{
oVtcategory.FkTocategory.Remove(ooVtsubcategory);
session.Delete(ooVtsubcategory);
}
session.SaveOrUpdateCopy(oVtcategory);
session.Flush();
if (tx.IsActive)
{
tx.Commit();
}
}
I guess it is inserting the child records in Save mode (for Child as they are new) and Inserting 2 more in Update (Parent) mode. Not sure if this is true and how to address it.
mapping is
<class name="Vtcategory" table="`vtcategory`" lazy="false">
<id name="IdvtCategory" column="`idvtCategory`" type="int">
<generator class="native" />
</id>
<property type="string" length="100" name="Catname" column="`catname`" />
<property type="string" length="45" name="Catshortname" column="`catshortname`" />
<property type="DateTime" name="Crdt" column="`crdt`" />
<property type="string" length="45" name="Crby" column="`crby`" />
<bag name="FkTocategory" inverse="false" lazy="true" cascade="all">
<key column="`catid`" />
<one-to-many class="AMSDAL.Vtsubcategory,AMSDAL" />
</bag>
<class name="Vtsubcategory" table="`vtsubcategory`" lazy="false">
<id name="IdvtSubCategory" column="`idvtSubCategory`" type="int">
<generator class="native" />
</id>
<property type="string" length="100" name="Subcatname" column="`subcatname`" />
<property type="string" length="45" name="Subcatshortname" column="`subcatshortname`" />
<property type="DateTime" name="Crdt" column="`crdt`" />
<property type="string" length="45" name="Crby" column="`crby`" />
<many-to-one name="Catid" cascade="none" column="`catid`" />
You need to mark one side of the relationship as the inverse. This is typically the one side on a one-to-many:
<bag name="FkTocategory" inverse="true" lazy="true" cascade="all">
<key column="`catid`" />
<one-to-many class="AMSDAL.Vtsubcategory,AMSDAL" />
</bag>
Mark the cascade on the bag to be all-delete-orphan rather than all. Then in your code you can just assign the new list with the new subcategories to the FkToCategory property. This will ensure that all orphaned subcategories will be deleted automatically by NHibernate.
Related
I'm upgrading an old NHibernate 1.2 solution I've taken over to NHib 3.1. We're having problems with persisting a parent child relationship. Which gives us this error:
NHibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
This code was working in NHib 1.2 but does not work in 3.1
We're saving much like this code below:
Film f = NewFilm();
Recipe r = new Recipe("2", TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(15));
f.Recipe = r;
SaveAndFlush(f, r); //custom code that saves f then saves r then flushes through the session.
However if we save r then f and flush it works.
I'd like to know why this happens, why the change between NHib versions. Is it the way the sesison thinks entities are transient now? Does it handle the foreign key id generator differently?
On a side note, the ID of the recipe doesn't equal the ID of the film, which I would expect it to do.
HMB files. - UPDATED to include full files
Film:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" schema="dbo">
<subclass name="Application.Core.Domain.Film, Application.Core" extends="Application.Core.Domain.VideoContent, Application.Core" discriminator-value="film" lazy="true">
<list inverse="false" lazy="true" name="Resources" access="field.camelcase-underscore" cascade="all-delete-orphan">
<key column="FilmId" />
<index column="PositionInFilm"/>
<one-to-many class="Application.Core.Domain.ContentResource, Application.Core" />
</list>
<list inverse="false" lazy="true" name="Steps" access="field.camelcase-underscore" cascade="all-delete-orphan">
<key column="FilmId" />
<index column="PositionInWebText"/>
<one-to-many class="Application.Core.Domain.WebText, Application.Core" />
</list>
<property name="FilmType" column="FilmType" />
<property name="PosterFrameTimeCode" column="PosterFrameTimeCode" />
<one-to-one name="Recipe" class="Application.Core.Domain.Recipe, Application.Core" cascade="save-update" access="field.camelcase-underscore"/>
<bag lazy="true" name="Shapes" access="field.camelcase-underscore" cascade="save-update" where="Archived=0">
<key column="ContentId"/>
<one-to-many class="Application.Core.Domain.FilmShape, Application.Core"/>
</bag>
<bag lazy="true" name="ArchivedShapes" access="field.camelcase-underscore" cascade="save-update" where="Archived=1">
<key column="ContentId"/>
<one-to-many class="Application.Core.Domain.FilmShape, Application.Core" />
</bag>
<many-to-one name="FilmToReplace" column="ReplacesFilmId" class="Application.Core.Domain.Film, Application.Core" access="field.camelcase-underscore" />
</subclass>
Recipe:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" schema="dbo">
<class name="Application.Core.Domain.Recipe,Application.Core" table="tbl_Recipe" lazy="false">
<id name="Id" column="HeaderId" type="System.Guid" access="field.camelcase-underscore">
<generator class="foreign">
<param name="property">Content</param>
</generator>
</id>
<list inverse="false" lazy="true" name="RecipeIngredients" access="field.camelcase-underscore" cascade="all-delete-orphan">
<key column="RecipeId" />
<index column="PositionInRecipe"/>
<one-to-many class="Application.Core.Domain.RecipeIngredient, Application.Core" />
</list>
<property name="Serves" column="Serves" type="System.String"/>
<property name="PreparationTime" column="PreparationTime" type="TimeSpan"/>
<property name="CookingTime" column="CookingTime" type="TimeSpan"/>
<property name="OvenTemperature" column="OvenTemperature" type="Application.Data.UserTypes.TemperatureType, Application.Data"/>
<one-to-one name="Content" class="Application.Core.Domain.Content, Application.Core" constrained="true" access="field.camelcase-underscore"/>
</class>
</hibernate-mapping>
If you have a cascading save on a "child" entity, you just save the parent. You don't need to save the child.
So here you should try saving only "f".
From the documentation:
http://nhibernate.info/doc/nh/en/index.html#mapping-declaration-onetoone
Primary key associations don't need an
extra table column; if two rows are
related by the association then the
two table rows share the same primary
key value. So if you want two objects
to be related by a primary key
association, you must make sure that
they are assigned the same identifier
value!
For a primary key association, add the
following mappings to Employee and
Person, respectively.
<one-to-one name="Person" class="Person"/>
<one-to-one name="Employee" class="Employee" constrained="true"/>
Now we must ensure that the primary
keys of related rows in the PERSON and
EMPLOYEE tables are equal. We use a
special NHibernate identifier
generation strategy called foreign:
<class name="Person" table="PERSON">
<id name="Id" column="PERSON_ID">
<generator class="foreign">
<param name="property">Employee</param>
</generator>
</id>
...
<one-to-one name="Employee"
class="Employee"
constrained="true"/>
</class>
A newly saved instance of Person is
then assigned the same primar key
value as the Employee instance refered
with the Employee property of that
Person.
I have parent and a child, insertion happens just fine ! if I retrieve the parent (along with child collection) and modify values of child and perform session.SaveOrUpdate(Digikeyset), all values gets updated in the child records, however issue is FK column is getting updated to NULL in child.
<class name="Digikeyset" table="`digikeyset`" lazy="false">
<id name="Iddigikeyset" column="`iddigikeyset`" type="int">
<generator class="native" />
</id>
<property type="string" length="100" name="Mpart" column="`mpart`" />
<property type="int" name="Boardqty" column="`boardqty`" />
<bag name="Fkdigirowset" inverse="false" lazy="false" cascade="all" >
<key column="`iddigiset`" />
<one-to-many class="bomorderDal.Digikeyrow,bomorderDal" />
</bag> </class>
<class name="Digikeyrow" table="`digikeyrow`" lazy="false">
<id name="Iddigikeyrow" column="`iddigikeyrow`" type="int">
<generator class="native" />
</id>
<property type="Boolean" name="Ispartselected" column="`ispartselected`" />
<property type="Boolean" name="Ispartfound" column="`ispartfound`" />
<many-to-one name="Iddigiset" cascade="save-update" column="`iddigiset`" />
</class>
Not sure this is the issue, but if you modify values you should Flush not SaveOrUpdate. Save is for new objects, Update is for existing objects that are not attached to the session.
So instead of:
session.SaveOrUpdate(Digikeyset)
Try:
session.Flush()
Thanks for the suggestion. Had tried with suggested options, but with no luck.
Finally got the result. Removed
<many-to-one name="Iddigiset" cascade="save-update" column="`iddigiset`" />
from the child and that did the trick. By the way I'm using Nhibernate libraries required for medium-trust environment.
I have these 2 objects in NHibernate forming a many to many relationship:
User:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Providers" namespace="Providers.Objects">
<class name="User" table="Users">
<id name="UserId" type="int">
<generator class="native" />
</id>
<many-to-one name="Application" column="ApplicationId" cascade="none" />
<property name="UserName" type="string" />
<property name="LoweredUserName" type="string" />
<property name="MobileAlias" type="string" />
<property name="IsAnonymous" type="bool" />
<property name="LastActivityDate" type="DateTime" />
<bag name="Roles" table="UsersInRoles" lazy="true" cascade="none" >
<key column="UserId"></key>
<many-to-many class="Role" column="RoleId"></many-to-many>
</bag>
</class>
</hibernate-mapping>
And Role:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Providers" namespace="Providers.Objects">
<class name="Role" table="Roles">
<id name="RoleId" type="int">
<generator class="native" />
</id>
<many-to-one name="Application" column="ApplicationId" class="Application" cascade="none" />
<property name="RoleName" type="string" />
<property name="LoweredRoleName" type="string" />
<property name="Description" type="string" />
<bag name="Users" table="UsersInRoles" lazy="true" inverse="true" cascade="none" >
<key column="RoleId"></key>
<many-to-many class="User" column="UserId"></many-to-many>
</bag>
</class>
</hibernate-mapping>
Let's say the role backupoperator has some users in it. If I try to remove one of the users from the role instance, like:
var backupoperator = GetRoleByName(session, app.ApplicationId, "backupoperator");
backupoperator.Users.RemoveAt(0);
session.Update(backupoperator);
transaction.Commit();
It doesn't work :( The association remains unchanged in the database. When I try the opposite (remove a role from a user object and updating the user object), it works.
Is it because of the inverse attribute in the NHibernate mapping?
How to accomplish what I am trying to do? (remove a user from a role, updating the role and having that persisted)?
Thanks
When you write inverse="true" you are telling NHibernate that the other side maintains the relationship.
Therefore, you have to remove the Role from the User's Roles collection if you want your change persisted.
I have a problem with mapping in nhibernate. I am using nhibernate 2.2 version.
Seems like the problem is in mapping but I am not sure this is the cause. Anyway, I have two tables which I would like to map. I created a hbm file for first table and a data transfer object too. All columns were mapped and everything works fine here.
But, now I want to add three bags to this class, which will point to the same table, my second table which I'd like to connect with. I created bags and mapped everything but when I am retrieving my data only one of these bags is filled, and the other ones are left empty, and I get an error "failed to lazily initialize a collection of role: com.organic.mitsu.hib.ModelContent.options - no session or session was closed". And I am 100% sure that my data in database are good. When I remove two bags from my mapping everything works fine, with only one bag left. Here is the hbm file:
<class name="MyFirstClass" table="MyFirstTable">
<id name="ID">
<generator class="native" />
</id>
<property name="ItemOne" />
<property name="ItemTwo" />
<property name="ItemThree" />
<property name="ItemFour" />
<bag name="FirstItems" table="MySecondTable">
<key column="ItemID" property-ref="ItemOne"/>
<one-to-many class="Items" not-found="ignore"/>
</bag>
<bag name="SecondItems" table="MySecondTable">
<key column="ItemID" property-ref="ItemTwo"/>
<one-to-many class="Items" not-found="ignore"/>
</bag>
<bag name="ThirdItems" table="MySecondTable">
<key column="ItemID" property-ref="ItemThree"/>
<one-to-many class="Items" not-found="ignore"/>
</bag>
How should I solve the problem? Is this even possible to do it like this?
And here is the mapping for the MySecondTable:
<class name="Item" table="MySecondTable">
<id name="ID">
<generator class="assigned" />
</id>
<property name="ItemID" />
<property name="Language" />
<property name="Value" />
Actually, the original thing that I was trying to map is with composite element and without the mapping for MySecondTable. I only have a dto class Item, with ItemID and Value columns. I got the same error and the mapping looks like this:
<class name="MyFirstClass" table="MyFirstTable">
<id name="ID">
<generator class="native" />
</id>
<property name="FirstItem" />
<property name="SecondItem" />
<property name="ThirdItem" />
<bag name="FirstItemNames" table="MySecondTable">
<key column="ItemID" property-ref="FirstItem"/>
<composite-element class="Item">
<property name="Value" />
</composite-element>
</bag>
<bag name="SecondItemNames" table="MySecondTable">
<key column="ItemID" property-ref="SecondItem"/>
<composite-element class="Item">
<property name="Value" />
</composite-element>
</bag>
<bag name="ThirdItemNames" table="MySecondTable">
<key column="ItemID" property-ref="ThirdItem"/>
<composite-element class="Item">
<property name="Value" />
</composite-element>
</bag>
Sounds like the SecondItems and ThirdItems are are being fetched lazily after the session was closed, which is not allowed. You need to either force the fetching while the session is active or change the mappings so that lazy fetch (the default) is turned off.
See here for more details.
I have mapping:
<class name="User" table="Users" lazy="false">
<id name="id" type="Int32" column="id">
<generator class="identity" />
</id>
<property name="name" column="name" type="String"/>
<map name="Urls" table="UserUrl" lazy="true" inverse="true" cascade="all">
<key column="user_id"></key>
<index column="url_type_id" type="Int32"/>
<one-to-many class="UserUrl"/>
</map>
</class>
<class name="UserUrl" table="UserUrl" lazy="false">
<id name="id" type="Int32" column="id">
<generator class="identity"/>
</id>
<property name="user_id" column="user_id" type="Int32" not-null="true"/>
<property name="UrlType" column="url_type_id" type="Int32" not-null="true"/>
<property name="Url" column="url" type="String" not-null="true"/>
</class>
Also I get
class User
{
IDictionary<int,UserUrl> Urls;
....
}
User currentUser = FindById(2);
currentUser.Urls.Remove(5);
So I remove one item from assosiation collection of Url. Then I call SaveOrUpdateCopy(...), But url from table UserUrl doesn't delete and there is no error.
Does anybody know how to delete child item from collection and from DB?
Set inverse to false for <map> element.
I try search in google but there is no information about difference between <map><one-to-many > and <map><composite-element> so I use <map><one-to-many >.
<map name="Urls" table="UserUrl" lazy="true" cascade="all-delete-orphans">
<key column="user_id"/>
<index column="url_type_id" type="Int32"/>
<composite-element>
<property name="UrlType" column="url_type_id" type="Int32" not-null="true"/>
<property name="Url" column="url" type="String" not-null="true"/>
</composite-element>
</map>
If the Url is not an independent entity, having an own Id, then you could map itas composite-element. The url is treated as a value type.There is no class UserUrl mapping anymore.
I use UserUrl class in other code
Try this:
<map name="Urls" lazy="true" cascade="all-delete-orphans">
You don't need table, because the table is defined in the class UserUrl mapping
You should not make it inverse, if it is not
You should cascade it all-delete-orphan to tell NH to remove items that are removed from the collection.
Not related to your questions, why do you have this in the url?
<property name="user_id" column="user_id" type="Int32" not-null="true"/>
You are mapping the foreign key there! I would never ever dare to do this.
Actually, I'm not sure if you should not actually map it like this:
<map name="Urls" table="UserUrl" lazy="true" cascade="all-delete-orphans">
<key column="user_id"/>
<index column="url_type_id" type="Int32"/>
<composite-element>
<property name="UrlType" column="url_type_id" type="Int32" not-null="true"/>
<property name="Url" column="url" type="String" not-null="true"/>
</composite-element>
</map>
If the Url is not an independent entity, having an own Id, then you could map it as composite-element. The url is treated as a value type. There is no class UserUrl mapping anymore.
Edit:
See the NH Reference Chapter 7, it explains the components.
If you get not-null problems on references, just remove the not-null
constraint on the foreign key. NH needs to insert some columns to get
primary keys if you use generator class="identity", so it stores
a null temporary.
-Btw, don't answer to your question if you are actually commenting to an
answer. You could be down-voted
-The problem is that I have no stackoverflow account, so only one way to login to site on the same name is to enter name and email under my post. I try to register account on that site with the same name and email, but when I log in it treats me as new user (not as I login fist time).
So I have no choice.
Also I find out that working code is
<map name="Urls" lazy="true" cascade="all-delete-orphans" inverse="true">
without inverse="true" it doesn't work.