I am trying to get NHibernate to save complete object graph in a one-to-one mapping scenario. I have following classes
public class Employee : EntityBase
{
public virtual string EmployeeNumber { get; set; }
public virtual Address ResidentialAddress {get; set; }
}
public class Address : EntityBase
{
public virtual string AddressLine1 { get; set; }
public virtual string AddressLine2 { get; set; }
public virtual string Postcode { get; set; }
public virtual string City { get; set; }
public virtual string Country { get; set; }
public virtual Employee Employee { get; set; }
}
I am trying to use one-to-one mapping here so that one employee has only one residential address. My mappings are below
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Domain" namespace="Domain">
<class name="Employee">
<id name="Id" generator="hilo" />
<property name="EmployeeNumber" length="10" />
<one-to-one name="ResidentialAddress" class="Address" property-ref="Employee" cascade="all" />
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Domain" namespace="Domain">
<class name="Address">
<id name="Id" generator="hilo" />
<property name="AddressLine1"/>
<property name="AddressLine2" />
<property name="Postcode" />
<property name="City" />
<property name="Country" />
<many-to-one name="Employee" class="Employee" unique="true" />
</class>
</hibernate-mapping>
I am using following code to save an instance of employee object
using (var transaction = Session.BeginTransaction())
{
id = Session.Save(new Employee
{
EmployeeNumber = "123456789",
ResidentialAddress = new Address
{
AddressLine1 = "Address line 1",
AddressLine2 = "Address line 2",
Postcode = "postcode",
City = "city",
Country = "country"
}
});
transaction.Commit();
}
In the above situation, the foreign key on Address back to Employee is always null. But if I change RResidentialAddress property on Employee class so that Employee property is always populated correctly as below
private Address address;
public virtual Address ResidentialAddress
{
get { return address; }
set
{
address = value;
if (value != null) value.Employee = this;
}
}
This makes it work perfectly. Why do I have to set ResidentialAddress.Employee? Am I missing something in the mappings? Should NHibernate not automatically save the complete object graph (and hence determine proper foreign key values).
The above working code concerns me as it may create a problem when called from NHiberante during loading of entity from database.
Your are not missing anything.
We (developers) should always care about setting both ends of any bidirectional relation.
When we use one-to-many (without inverse="true") and without setting the many-to-one, we always end up with redundant and unefficient sequence of SQL statements:
INSERT child record, place NULL into Parent_ID
UPDATE child record, set the Parent_ID with Parent.ID
This is not suggested: 6.8. Bidirectional Associations:
... The non-inverse side is used to save the in-memory representation to the database. We would get an unneccessary INSERT/UPDATE and probably even a foreign key violation if both would trigger changes! The same is of course also true for bidirectional one-to-many associations...
So, that is in one-to-many, many-to-one.
In case of one-to-one
There is no collection persister in place as discussed above. No man in the middle, which would take care about the other end, and issue "unefficient" INSERT and later UPDATE. NHibernate will use standard entity persisters for Employee and Address.
Each association end of the relation (Employee-Address) has its own persister. These could be triggered in a cascade (usually good idea to have <one-to-one ... cascade="all" />)
But each persister needs enough information to create proper INSERT or UPDATE statement. I.e. even C# Address must know about its Employee. The Address persister, will handle INSERT/UPDATE alone, with access to Address instance only.
SUMMARY: We have to set both ends in code.... And that is good practice even if we are not forced (non inverse child mapping with nullable parent column).
BTW: once loaded from DB, we expect that NHibernate will set both ends... why should not we do the same?
Related
I have the below two classes:
public class Project
{
public virtual int ProjectId { get; set; }
public virtual string ProjectName { get; set; }
public virtual LegalEntity LegalEntity { get; set; }
}
and
public class LegalEntity
{
public virtual int LegalEntId { get; set; }
public virtual string Name { get; set; }
}
with mappings as:
<class name="Project" table="Project" dynamic-update="true">
<id name="ProjectId">
<generator class="native"/>
</id>
<property name="ProjectName" />
<many-to-one name="LegalEntity" column="LegalEntId" fetch="join" cascade="all-delete-orphan" />
</class>
and
<class name="LegalEntity" table="LegalEnt" dynamic-update="true">
<id name="LegalEntId">
<generator class="native"/>
</id>
<property name="Name" />
</class>
In database, Project table has a FK to LegalEntity's PK column. One Project will have only one legal entity. Different projects can have same legal entity. So thats the reason I have gone for many-to-one. Not sure if this is correct though.
Insert and update is working fine. But if I update a legal entity id in a project and that legal entity becomes orphan, I want it to be deleted. But its not happening. Am I wrong in understanding delete-all-orphan? If yes, how can I achieve this behaviour?
The many-to-one cascade does not support all-delete-orphan, see:
5.1.10. many-to-one
<many-to-one
...
cascade="all|none|save-update|delete" (4)
...
Also, it would be almost impossible to handle this feature by NHibernate's session. Because it does not have to be clear, that the referenced many-to-one is really orphan. There should be some farther checks in DB... there could be other places referencing this table row...
Suggestion: do it in your code as a part of the DAO or Business Facade implementation. Check if there are really no dependencies, and then issue explicit Delete()
EXTEND: Here is a QueryOver syntax to get a list of all "orphan" LegalEntity
// subquery
var subquery = QueryOver.Of<Project>()
.Select(x => x.LegalEntity.LegalEntId);
// just these legal entities, which are NOT used
var query = session.QueryOver<LegalEntity>()
.WithSubquery
.WhereProperty(y => y.LegalEntId)
.NotIn(subquery)
;
// orphans
var list = query
.List<LegalEntity>();
Now all-delete-orphan and delete-orphan have been implemented for many-to-one as you can see in this commit from Nov 19, 2014.
Those were not supported when the OP asked the questions or when Radim Köhler wrote his answer, but I think future visitors will appretiate the update.
The documentation is also updated and now says:
cascade="all|none|save-update|delete|delete-orphan|all-delete-orphan"
But the documentation is confusing now, because it still has the following note:
The cascade attribute permits the following values: all, save-update, delete, none.
So I've created a defect to fix that last part of the documentation.
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!
I hope anyone can help. I have to develop up against this third party database and I am kind of stuck with their crappy design. Still, I want to use NHibernate so I will have to jump through hoops.
Simplified, there is this "Event" table that has a relation a "Transportation" table. The transportation table has a composite primary key composed of the field "ID" and "FK_EventID", the latter of course referring back to the Event-record. Each event points to one distinct record in the transportation table, so it is a one-to-one relation really. Both fields are Guids BTW.
Attempting to map this out, this is how I created the classes (leaving out other data fields for simplicity's sake):
public class FcoEvent : IFcoObject
{
public virtual Guid ID { get; set; }
//public virtual Guid FK_TransportationID { get; set; } //ignore
public virtual FcoTransportation Transportation { get; set; }
And:
[Serializable]
public class FcoTransportation : IFcoObject
{
#region Members
public virtual Guid ID { get; set; }
public virtual Guid FK_EventID { get; set; }
In the mapping files I am attempting this (note that I am using many-to-one):
<class name="FcoLib.FcoEvent, FcoLib" table="FCO_Event">
<id name="ID" column="ID">
<generator class="guid" />
</id>
<many-to-one name="Transportation" not-found="ignore" cascade="save-update"
class="FcoLib.FcoTransportation, FcoLib">
<column name="FK_TransportationID" />
<column name="ID" />
</many-to-one>
And:
<class name="FcoLib.FcoTransportation, FcoLib" table="FCO_Transportation">
<composite-id>
<key-property name="ID" />
<key-property name="FK_EventID" />
</composite-id>
When I try to run this, I get the following exception message:
NHibernate.QueryException: could not resolve property: FK_TransportationID of: FcoLib.FcoEvent
My first hunch was that there may be a spelling error in the field name, but that didn't hold. So now I am completely puzzled and don't know how to proceed. Any help is greatly appreciated. Thnx.
Update
I think I found the source of the error. I had not looked there yet, because I assumed it was a mapping error, but apparently it is a querying error. It happens where I do the query:
fcoEvents = session.CreateCriteria(typeof(FcoEvent))
.Add(Restrictions.Eq("ID", eventId))
.Add(Restrictions.Eq("FK_TransportationID", transportId))
.List<FcoEvent>();
I will look further into this, but obviously I need to query this in a different way...
Silly me. I have been distracted by some faulty, outdated code. The point was to be able to retrieve the event including the related transport child using the primary key of the event and that simply works. Also it should be possible to retrieve the transport issue with the composite primary key and that can be accomplished with the below code.
public FcoTransportation GetTransportation(Guid transportId, Guid eventId)
{
FcoTransportation transport;
ISession session = Factory.OpenSession();
ITransaction tx = session.BeginTransaction();
try
{
transport = session.Get<FcoTransportation>(new FcoTransportation()
{
ID = transportId,
FK_EventID = eventId
});
So this has been a non-issue really. I have just been confused by the whole composite foreign key stuff. I hope I did not waste people's time.
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 :)
My database structure looks something like this:
Person
Id
Name
FieldA
FieldB
Phone
Id
Number
PersonPhone
PhoneId
PersonId
IsDefault
My NHibernate mappings for the Person and Phone objects are straight forward, its the PersonPhone I'm having difficult with. I want to have a collection of PersonPhone objects as a property of Person which will allow me to have the Phone number of a person and be able to tell which is the "default" or primary phone number for a person.
ideally Id like my PersonPhone object to look like this:
public class PersonPhone
{
public virtual Person Person { get; set; }
public virtual Phone Phone { get; set; }
public virtual bool IsDefault { get; set; }
}
so far my NHibernate mapping for this table looks like the following:
<class name="PersonPhone" table="PersonPhone">
<composite-id>
<key-property name="Person" column="PersonId" />
<key-property name="Phone" column="PhoneId" />
</composite-id>
<property name="IsDefault" column="IsDefault"/>
</class>
but when NHibernate compiles my mappings I get an error saying:
Could not compile the mapping document: MyApp.Entities.PersonPhone.hbm.xml. NHibernate.MappingException : Could not determine type for: MyApp.Entities.Person, MyApp.Entities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null, for columns: NHibernate.Mapping.Column(PersonId)
Any ideas on how this should be mapped?
The answer is to use the element in your composite key rather than the key-property
<class name="PersonPhone" table="PersonPhone">
<composite-id>
<key-many-to-one name="Person" column="PersonId"></key-many-to-one>
<key-many-to-one name="Phone" column="PhoneId"></key-many-to-one>
</composite-id>
<property name="IsDefault" column="IsDefault"/>
</class>
I think It is more proper to consider Many-to-Many relationship between Phone and Peron entities and get rid of PersonPhone entity.
To set-up the same mapping with Fluent NHibernate, do this:
public class PersonPhoneMap : ClassMap<PersonPhone>
{
public PersonPhoneMap()
{
CompositeId()
.KeyReference(p => m.Person)
.KeyReference(p => m.Phone);
References(p => p.Person)
.Column("PersonID");
References(m => m.Phone)
.Column("PhoneID");
Map(p => p.IsDefault)
.Column("IsDefault");
}
}