Automatically removing associations when deleting entities in NHibernate - nhibernate

I have the following entities:
namespace NhLists {
public class Lesson {
public virtual int Id { get; set; }
public virtual string Title { get; set; }
}
public class Module {
public virtual int Id { get; set; }
public virtual IList<Lesson> Lessons { get; set; }
public Module() {
Lessons = new List<Lesson>();
}
}
}
And the following mappings:
<class name="Module" table="Modules">
<id name="Id">
<generator class="identity"/>
</id>
<list name="Lessons" table="ModuleToLesson"
cascade="save-update">
<key column="moduleId"/>
<index column="position"/>
<many-to-many
column="lessonId"
class="NhLists.Lesson, NhLists"/>
</list>
</class>
<class name="Lesson" table="Lessons">
<id name="Id">
<generator class="identity"/>
</id>
<property name="Title">
<column name="Title" length="16" not-null="true" />
</property>
</class>
When I delete a lesson by session.Delete(lesson), is there anyway I can have NHibernate automatically update the association in Module.Lessons to remove the entry from the set? Or am I forced to go through all Modules and look for the lesson and remove that by hand?
Edit: Fixed ICollection and <set> in mappings to IList<> and <list> like I want and tested it.

You have false idea. If you want to delete the Lesson object from Module you do that manually. NHibernate just tracks such your action and when session.Commit() is called then the reference between Module and Lesson is deleted in the database.
Calling session.Delete(lesson) deletes the lesson object from database (if foreign keys are set properly then reference between Module and Lesson is deleted of course but it is not responsibility for NHibernate).
In conclusion, it is not possible to delete the lesson object from the Module.Lessons list automatically by calling session.Delete(lesson). NHibernate does not track such entity references.

Turns out that if we do not need IList semantics and can make do with ICollection the update problem can be solved by adding a reference back from Lesson to Module, such as:
public class Lesson {
...
protected virtual ICollection<Module> InModules { get; set; }
...
}
And to the mapping files add:
<class name="Lesson" table="Lessons">
...
<set name="InModules" table="ModuleToLesson">
<key column="lessonId"/>
<many-to-many column="moduleId" class="NhLists.Module, NhLists"/>
</set>
</class>
Then a Lesson deleted is also removed from the collection in Module automatically. This also works for lists but the list index is not properly updated and causes "holes" in the list.

Related

Mapping a Reflexive Many-to-Many relationship with NHibernate?

I am trying to use NHibernate with an existing database. The database records unidirectional relationships between users:
Users
UserId PK
Name
Relationships
RelationshipId PK
ParentId FK:Users_UserId
ChildId FK:Users_UserId
I want to represent this using NHibernate. At the moment, I have the following POCO object:
class User {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public ICollection<User> ParentUsers {get; set;}
public ICollection<User> ChildUsers {get; set;}
}
I also have this mapping file:
<class name="User" table="Users">
<id name="Id"></id>
<property name="Name"></property>
</class>
I've looked at guides on the web, but I can't work out what I need to put in the mapping file to wire up my two ICollection properties.
How should I map this data structure? Is my current approach correct, or is it better to create a second POCO class, and just use two many-to-one relationships?
I'm probably missing something, but this should get you started:
<idbag name="ParentUsers" table="Relationships">
<collection-id column="RelationshipId" type="...">
<generator class="..."/>
</collection-id>
<key column="ChildId"/>
<many-to-many column="ParentId" class="User"/>
</idbag>
<idbag name="ChildUsers" table="Relationships">
<collection-id column="RelationshipId" type="...">
<generator class="..."/>
</collection-id>
<key column="ParentId"/>
<many-to-many column="ChildId" class="User"/>
</idbag>
Also, one of the collections should be marked as inverse.

NHibernate one-to-many foreign key is NULL

I have a problem with NHibernate. What i am trying to to is very simple:
I have two Classes. UserTicket and UserData. A UsertTicket has some UserData and a UserData belongs to one UserTicket:
public class UserData{
public virtual int Id { get; set; }
public virtual String PDF_Path { get; set; }
}
public class UserTicket
{
public virtual int Ticketnr { get; set; }
public virtual IList<UserData> UserData { get; set; }
}
And here the mappig xml:
<class name="UserTicket" table="UserTicket">
<id name="Ticketnr">
<generator class="identity"/>
</id>
<bag name="UserData" inverse="true" cascade="all-delete-orphan" lazy="false">
<key column="FK_Ticketnr" not-null="false"/>
<one-to-many class="UserData" />
</bag>
</class>
<class name="UserData" table="UserData">
<id name="Id">
<generator class="identity"/>
</id>
<property name="PDF_Path" />
</class>
When i run it, it works, but the column FK_Ticketnr in the DB-Table of UserData is always = NULL.
Someone told me i have to make a back link from my Child (UserData) to the Parent-Class. But i can not figure out how to do so. How do i get Hibernate to write the value of the Primary-Key (Ticketnr) of UserTicket into FK_Ticketnr?
Thanks in advance
Remove inverse="true" on your bag. That is telling NHibernate that the relationship is managed from the UserData mapping. Since you don't have a back reference, it is never persisted. The other option is to put a parent reference on UserData, but I wouldn't recommend it if you don't need it.
The alternative is the following...
public class UserData
{
// Keep your other properties, add this one too...
public virtual UserTicket Ticket { get; set; }
}
Modify your mapping file like this...
<class name="UserData" table="UserData">
<id name="Id">
<generator class="identity" />
</id>
<property name="PDF_Path" />
<many-to-one name="Ticket" column="FK_Ticketnr" />
</class>
Keep the inverse="true" on the bag.
If you go with this approach, you will need to set Ticket on your UserData objects when you add them to the UserData bag. In other words, you need to maintain both sides of the relationship. You could do this manually or you could try to automate it a little with with methods or constructors.

nhibernate to save only required properties

I'm using NHibernate 2.2 for my database work and I've faced an issue recently. I have a class called PrescDrugItem which is shown below
public class PrescDrugItem
{
public virtual int ItemNumber { get; set; }
[DataMember]
public virtual int AmountIssued { get; set; }
[DataMember]
public virtual string TimePeriod { get; set; }
}
following is the mapping file
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly ="DataContractsLib"
namespace="DataContractsLib.Prescription" >
<class name="PrescDrugItem">
<id name="ItemNumber" type="Int32">
<generator class="native" />
</id>
<property name="AmountIssued" type="Int32" />
<property name="TimePeriod" type="String" length="30" />
</class>
my problem is, now I need to add another property to the class Item (say ItemTradeName etc), but I dont want it to be saved to the database( because I want to use this new property to store some data temporary). I tried update=false and insert=false in the mapping file but no success yet. Could you guys please tell me is this possible thing to do . Thank you.
If it's not to be fetched from the database either, just add it as a normal property of your class and don't map it.

nHibernate - Complicated many-to-many class mapping

I'm new to nhibernate, and I'm sorry if this is answered elsewhere, but I've been looking for the last couple of hours, and can't find a solution that works.
A bit of background:
I'm trying to write an Admin area where there are users and sites, and a user can have access to multiple sites - but at various permission levels for each site.
Ideally I would like my classes look like this.
namespace MyApp.Users
{
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Password { get; set; }
public virtual IList<AdminUserSite> Sites { get; set; }
}
public class AdminUserSite
{
public virtual int UserTypeId { get; set; }
public virtual Site AdminSite { get; set; }
public virtual IList<Permission> Permissions { get; set; }
}
public class Permission
{
public virtual int Id { get; set; }
public virtual int AreaID { get; set; }
public virtual bool CanView { get; set }
public virtual bool CanEdit { get; set }
}
}
namespace MyApp.Sites
{
public class Site
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
}
}
And my database schema looks like this
f_user
{
f_user_id (int, PK)
name (nvarchar(50))
password (nvarchar(25))
}
f_user_site
{
f_user_id (int, PK)
f_site_id (int, PK)
d_user_type_id (int)
}
f_perm
{
f_perm_id (int, PK)
f_site_id (int)
f_user_id (int)
d_area_id (int)
can_read (bit)
can_write (bit)
}
f_site
{
f_site_id (int, PK)
title (nvarchar(50))
}
And the hibernate mapping files currently look like:
Users.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyApp"
namespace="MyApp.Users"
default-lazy="true">
<class name="User" table="f_user">
<id name="Id" column="f_user_id">
<generator class="identity" />
</id>
<property name="Name" column="name" />
<property name="Password" column="password" />
<bag name="Sites" table="f_user_site" inverse="true" cascade="all-delete-orphan">
<key column="f_user_id"/>
<one-to-many class="AdminUserSite"/>
</bag>
</class>
<class name="Permission" table="f_perm">
<id name="Id" column="f_perm_id">
<generator class="identity" />
</id>
<property name="AreaId" column="d_area_id" />
<property name="CanView" column="can_read" />
<property name="CanEdit" column="can_write" />
</class>
<class name="AdminUserSite" table="f_user_site">
<property name="UserTypeId" column="d_user_type_id" />
<many-to-one name="Site" class="MyApp.Sites.Site, MyApp.Sites" foreign-key="f_site_id"></many-to-one>
</class>
</hibernate-mapping>
and Sites.hbm.xml is
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyApp"
namespace="MyApp.Sites"
default-lazy="true">
<class name="Site" table="f_site">
<id name="Id" column="f_site_id">
<generator class="identity" />
</id>
<property name="Title" column="title" />
</class>
</hibernate-mapping>
Individually the User, Permission and Site classes all map fine - but i just can't figure out what AdminUserSite should be, and I haven't even attempted to put the permissions list in there yet.
Does anyone have any ideas?
Any help would be very appreciated.
Saan
I think you need to work on the database schema a bit. I think the AdminUserSite table is your problem.
Firstly, work out your entities (these should be simple nouns, which is why AdminUserSite seems out of place to me).
Entities: User, Permission, Site, SiteArea
Next, work out the relationships:
1 User has many Permissions
1 SiteArea has many Permissions
1 Site has many SiteAreas
(hope I got that right :) )
After that, your hbm and tables should flow more naturally.
Remember that normally you will have 1 table per entity, unless you have a many-many relationship (in which case you will need a joining table).
Generally a relation table mapping look like this :
<class name="AdminUserSite" table="f_user_site">
<composite-id >
<key-many-to-one name="Site" column="f_site_id" class="Site" />
<key-many-to-one name="User" column="f_user_id" class="User"/>
</composite-id>
</class>
In that way you can access either Site or User by its relation object or load relation object by criteria based on Site or User.
By the way, if your Permission object contains reference to user and site you may not need the AdminUserSite relation, the permission mapping already do it.
Edit about your comment Kind of replicating the same information in two spots.
Since NHibernate -like all ORM- has a first level cache you don't have to matter if your object can be accessed in two ways. You just have to ensure that your mappings are usefull to optimize and design your application well.
In this case, it's not a 'replication' but a reference. The object will be the same in the two spots if loaded by/retrieved from the same NHibernate session.
Accessing an object in two ways if they have a logic (ie. direct way and cross-relation way) is not an heresy.
This is most a data layer work to achieve to provide right methods to access the right objects in the right way :)

NHibernate one-to-many mapping does not populate the bag

I have a simple object model of a header with multiple detail lines. I have mapped a bag property on the header object to the line class and also put a header property on the line class to set the two way relationship.
When I run my tests, using NHibernate Profiler I can see that the query is being performed, and the header and lines are fetched from the database, but the collection is always empty.
When I query for a collection of line objects directly I can get them and see the header object is correctly populated, so I know the mapping is probably OK.
The slightly non-standard aspect to this is that the line object has a compound key. (This is a legacy DB and I cannot change this), so I'm wondering if that is the problem.
Here are my classes and mappings (simplified)
<class name="Header" table="HEADER">
<id name="ID" column="HEAD_ID">
<generator class="assigned" />
</id>
<bag name="Lines" table="BODY" order-by="BODY_LINE">
<key column="BODY_HEADER_ID"/>
<one-to-many class="Line"/>
</bag>
</class>
<class name="Line" table="BODY">
<composite-id>
<key-property name="ID" column="BODY_HEADER_ID"/>
<key-property name="Line" column="SBODY_LINE"/>
</composite-id>
<many-to-one class="Header" name="Head" column="BODY_HEADER_ID" />
</class>
public class Header {
public virtual string ID { get; set; }
public virtual IList<Line> Lines { get; set; }
}
public class Line {
public virtual string ID { get; set; }
public virtual int Line { get; set; }
public virtual Header Head { get; set; }
public override bool Equals(object obj) {
var other = obj as Line;
return ID == other.ID && Line == other.Line;
}
public override int GetHashCode() {
return (ID + "|" + Line.ToString()).GetHashCode();
}
}
I can get around this by querying for the Line objects separately, but I'd like to know what I'm doing wrong.
EDIT: Ok, when I simplified things, I didn't do it very consistently. Sorry for the confusion. I have changed the mapping and class definitions to reflect things more accurately.
You are mapping the same column twice (BODY_HEADER_ID), and using different three different class names for the HEADER table.
This is the correct Line mapping:
<class name="Line" table="BODY">
<composite-id>
<key-many-to-one class="Header" name="Header" column="BODY_HEADER_ID"/>
<key-property name="Line" column="SBODY_LINE"/>
</composite-id>
</class>
And of course, Line should have only that single reference to Header.