nHibernate - Complicated many-to-many class mapping - nhibernate

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 :)

Related

NHibernate 3.2 ignores entity-name

I'm using NHibernate 3.2 and have two tables mapped for same class, specifying the "entity-name" in mapping. The trouble is that when I use the method in ISession to indicate the entity name NHibernate insists deduct on their own behalf, ignoring my specification.
This is code from my unit tests:
public class Cliente
{
public virtual Guid UID { get; set; }
public virtual long Revisao { get; set; }
public virtual string Nome { get; set; }
public virtual DateTime DataNascimento { get; set; }
}
<class name="Cliente">
<id name="UID">
<generator class="guid"/>
</id>
<version name="Revisao" />
<property name="Nome" />
<property name="DataNascimento" />
</class>
<class name="Cliente" entity-name="ClienteAudit" schema="audit">
<composite-id>
<key-property name="UID" />
<key-property name="Revisao" />
</composite-id>
<property name="Nome" />
<property name="DataNascimento" />
</class>
var cliente = new Cliente {DataNascimento = DateTime.Parse("1988/07/09"), Nome = "Heber Senger"};
using (var ss = sf.OpenSession())
{
ss.Save("Cliente", cliente);
ss.Flush();
}
NHibernate insists in save the entity as "ClienteAudit" (I verify in listener and table), and I explicity inform entity name as "Cliente".
I just try:
- Specify entity name in Cliente mapping;
- Omit name in method save, let NHibernate free to discover the name, implying in "ClienteAudit" again;
- Now I studying internal code of SessionImpl and so on.
If anyone can help would be great. Thanks.
Two changes were needed to all work:
Default type of version property is int and NOT long;
And the most important: the name specified in save method is the full name of class when entity-name don't was explicity indicated in HBM.
By the way, thanks!

Why is my IList<T> being returned as a list of null objects?

Whilst I realise that a Set or Bag is probably the correct way to do this, I'm new to NHibernate and I'm trying to understand why the following is happening.
I have two classes:
public class Customer
{
public virtual int Id { get; protected set; }
public virtual string CustomerName { get; set; }
// Customer has many domains
public virtual IList<Domain> Domains { get; set; }
}
public class Domain
{
public virtual int Id { get; protected set; }
public virtual int CustomerID { get; set; }
public virtual string DomainName { get; set; }
}
My mapping files look like:
<!-- Domain -> tblDomains -->
<class name="Domain" table="tblDomains">
<id name="Id">
<column name="DomainID" sql-type="int" not-null="true"/>
<generator class="identity"/>
</id>
<property name="CustomerID"/>
<property name="DomainName"/>
</class>
<!-- Customer -> tblCustomer -->
<class name="Customer" table="tblCustomer">
<id name="Id">
<column name="CustomerID" sql-type="int" not-null="true"/>
<generator class="identity"/>
</id>
<property name="CustomerName" column="Customer"/>
<list name="Domains">
<key column="CustomerID"/>
<index column="DomainID"/>
<one-to-many class="Domain" />
</list>
</class>
When I retrieve a Customer object the Domains property contains a list of 665383 null Domain objects. The 665384'th item in the list contains a valid populated object.
There are only 63 Domain's that belong to this customer so I'm guessing this is some kind of cartesian product result. I've peeked at the SQL in NHProfiler but all I see is a query that looks fairly innocent when I iterate over the first item in the Domains list:
SELECT domains0_.CustomerID as CustomerID1_,
domains0_.DomainID as DomainID1_,
domains0_.DomainID as DomainID2_0_,
domains0_.CustomerID as CustomerID2_0_,
domains0_.DomainName as DomainName2_0_
FROM tblDomains domains0_
WHERE domains0_.CustomerID = 5667 /* #p0 */
If I use a <bag> this all works just fine. Can anyone explain what's going on under the bonnet?
With a list mapping, the index applies to the set of objects in the list. That is, if a Customer has a set of 63 Domains then normally the list would contain values from 0 to 62 to indicate the index of the Domain object in that Customer's set of Domains.
You have set the index to DomainId, the primary key of the table, which is wreaking havoc. I would guess that the Domain table has 665384 rows total (or fewer if DomainId doesn't start with 1 and has gaps), but I would think that 63 would be valid instead of one. Did you check them all? :-)

Automatically removing associations when deleting entities in 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.

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.

Lazy loading not working for many-to-one relationship when mapping to a non-key field using property-ref

I have a legacy database that I am mapping using NHibernate. The objects of concern are an Account and a list of Notification objects. The objects look like:
public class Notification
{
public virtual int Id { get; set; }
public virtual DateTime BatchDate { get; set; }
/* other properties */
public virtual Account Account { get; set; }
}
public class Account
{
public virtual int Id { get; set; }
public virtual string AccountNumber { get; set; }
/* other properties */
}
The mapping files look like:
<class name="Account" table="Account" dynamic-update="true">
<id name="Id" column="AccountID">
<generator class="native" />
</id>
<property name="AccountNumber" length="15" not-null="true" />
<!-- other properties -->
</class>
<class name="Notification" table="Notification">
<id name="Id" column="Id">
<generator class="native" />
</id>
<!-- other properties -->
<many-to-one name="Account" class="Account" property-ref="AccountNumber" lazy="proxy">
<column name="AcctNum" />
</many-to-one>
However, when I create a criteria such as
return session.CreateCriteria(typeof(Notification)).List<Notification>();
I am getting a Select N+1 case where each account is loaded even though the Account is never referenced. Why are all of the accounts getting loaded when the many-to-one is mapped as a lazy proxy?
The issue is caused by the property-ref attribute. Lazy loading only works when the many-to-one reference is using the other object's primary key since NHibernate assumes there's a foreign key constraint enforcing the validity of such a value. With a non-primary key (indicated by the property-ref), NHibernate does not make this assumption and thus does not assume the related object must exist. Since it does not want to create a proxy for an object that does not exist (i.e. should be null instead of a proxy), it eagerly fetches the remote object. This same issue exists when not-found="ignore" is specified since this indicates that the foreign key relationship is not enforced and may result in a null reference.
See also:
NHibernate creates proxy via session.Load(), but not via Linq or Criteria API
http://frankmao.com/2007/12/05/lazy-load-conflicts-with-property-ref-in-many-to-one-mapping/