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.
Related
I am trying to map a relaitively simple data model with NHibernate for use with breeze.js. The data model consists of four entities and looks like this:
The problem seems to be with the relationship between the CourseDate and the CourseDateStudent entity. When calling the Metdata method of my BreezeController I get the following error:
System.ArgumentException: "Could not find matching fk for property HR.CourseManager.Web.Data.Entities.CourseDateStudent.CourseDate"
I do not know what exactly causes this behavior as I think I have implemented my model as recommended by the breeze.js documentation.
There is another question convering a somewhat similar topic, but this question does not address having composite keys.
(Remark: Adding surrogate keys like numeric ids is not an option here. As you might have guessed this is not the real data model but one that simply has had its entities renamed. The underlying database is used by a whole bunch of applications and cannot be changed.)
Enough of writing about my problem. Let's see some code! So there is the CourseDate entity which has a composite key consisting of the properties CourseCode and Date:
CourseDate.cs:
public class CourseDate {
public CourseDate() {
this.Students = new List<CourseDateStudent>();
}
public virtual Course Course { get; set; }
public virtual DateTime Date { get; set; }
public virtual string CourseCode { get; set; }
public virtual ICollection<CourseDateStudent> Students { get; set; }
// Skipped implementation of Equals and GetHashCode for readability
}
CourseDate.hbm.xml:
<hibernate-mapping assembly="CourseManager.Web"
namespace="CourseManager.Web.Data.Entities"
xmlns="urn:nhibernate-mapping-2.2">
<class name="CourseDate" table="COURSE_DT" lazy="true" >
<composite-id>
<key-many-to-one name="Course" column="COURSE_CODE" />
<key-property name="Date" column="DT" />
</composite-id>
<property name="CourseCode" insert="false" update="false">
<column name="COURSE_CODE" sql-type="VARCHAR2" not-null="true" />
</property>
<bag name="Students" inverse="true">
<key>
<column name="COURSE_CODE" />
<column name="COURSE_DT" />
</key>
<one-to-many class="CourseDateStudent" />
</bag>
</class>
</hibernate-mapping>
And then there is the CourseDateStudent entity that has a primary key consisting of the CourseCode and the CourseDate property (thus defining the foreign key relationship to the CourseDate entity) as well as the StudentId property:
CourseDateStudent.cs
public class CourseDateStudent {
public virtual CourseDate CourseDate { get; set; }
public virtual Student Student { get; set; }
public virtual long StudentId { get; set; }
public virtual string CourseCode { get; set; }
public virtual DateTime Date { get; set; }
// Skipped implementation of Equals and GetHashCode for readability
}
CourseDateStudent.hbm.xml
<hibernate-mapping assembly="CourseManager.Web"
namespace="CourseManager.Web.Data.Entities"
xmlns="urn:nhibernate-mapping-2.2">
<class name="CourseDateStudent" table="COURSE_DT_STUD_LNK" lazy="true" >
<composite-id>
<key-many-to-one name="CourseDate">
<column name="COURSE_CODE" />
<column name="COURSE_DT" />
</key-many-to-one>
<key-many-to-one name="Student" column="STUD_ID" />
</composite-id>
<property name="StudentId" insert="false" update="false">
<column name="STUD_ID" sql-type="NUMBER" />
</property>
<property name="CourseCode" insert="false" update="false">
<column name="COURSE_CODE" sql-type="VARCHAR2" not-null="true" />
</property>
<property name="Date" insert="false" update="false">
<column name="COURSE_DT" sql-type="DATETIME" not-null="true" />
</property>
</class>
</hibernate-mapping>
There was a bug in the Breeze code that builds metadata from NHibernate models. As you discovered, it wasn't finding the foreign keys correctly when composite keys were used.
The fix for the issue is now on github and will be in the next release. Sorry it took so long!
I am trying to get this solved but can't so far. all kind of errors.
These are my db tables
Person (personID, name, age)
Role (roleID, roleName)
PersonRoles(personRolesID, personID, roleID)
this is my domain class
public Person
{
public virtual Roles RolesForThisPerson {get;set;}
public virtual string Name {get;set;}
public virtual int Age {get;set;}
}
public Roles
{
public virtual IList<string> RoleList {get;set;}
}
I am totally lost on how to approach this. I am so confused about sets, bags, lists... i don't even know where to start.
Anybody can give me a little push here?
thanks
Let's keep the DB schema as it is and adjust the C# domain classes first:
public class Person
{
public virtual string Name {get;set;}
public virtual int Age {get;set;}
public virtual IList<Role> RolesForThisPerson {get;set;}
}
public class Role
{
public virtual string RoleName { get; set; }
}
Now basic mapping for these two entities into defined tables:
<class name="Person" table="Person" lazy="true">
<id name="ID" column="personID">
<generator class="native" />
</id>
<property name="Name" not-null="true" />
<property name="Age" not-null="true" />
<!-- placeholder for roles -->
</class>
<class name="Role" table="Role" lazy="true">
<id name="ID" column="roleID">
<generator class="native" />
</id>
<property name="RoleName" not-null="true" />
</class>
And now we can use the <idbag> mapping and extend the Person class mapping this way:
<idbag name="RolesForThisPerson" batch-size="25" table="PersonRoles"
inverse="true" lazy="true" cascade="none" >
<collection-id column="personRolesID" type="System.Int32" >
<generator class="native" />
</collection-id>
<key column="personID" />
<many-to-many class="Role" column="roleID" />
</idbag>
The <idbag> can profit from the fact, that even the pair table has its own identifier. Cascade is set to none, expecting that roles are in the system, and users are only assigned to them (removed from). Attribute batch-size will effect how many SELECT statements will be executed when fetching the lazy roles collection.
I'm using <dynamic-component> elements to provide end-users the capacity to extend the functionality of our product by adding fields to the database.
A simplified version of our mapping is:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Caselle.Am0.DTO" assembly="Caselle.Am0.DTO" schema="dbo">
<class name="Asset" table="[tblAsset]" lazy="true">
<id name="ID" column="ID" type="Int32">
<generator class="native" />
</id>
<property name="AssetNumber" column="[AssetNumber]" type="long" not-null="true" />
<dynamic-component insert="true" update="true" name="UserDefinedFields" />
<many-to-one name="Classification" column="tblClassificationID" class="Lib.DTO.Classification, Lib.DTO" cascade="none" />
</class>
<class name="Classification" table="[tblClassification]" lazy="true">
<id name="ID" column="ID" type="Int32">
<generator class="native" />
</id>
<property name="Name" column="[Classification]" type="String" not-null="true" length="20" />
<dynamic-component insert="true" update="true" name="UserDefinedFields" />
<set name="Assets" table="tblAsset" lazy="true" cascade="all-delete-orphan" inverse="true">
<key column="tblClassificationID"/>
<one-to-many class="Lib.DTO.Asset, Lib.DTO"/>
</hibernate-mapping>
The classes look something like this:
public class Asset
{
public virtual long AssetNumber{get; set;}
public Classification Classification {get; set;}
public virtual IDictionary UserDefinedFields {get; set;}
}
public class Classification
{
public virtual string Name {get; set;}
public virtual ICollection<Asset> Assets {get; private set;}
public virtual IDictionary UserDefinedFields {get; set;}
}
The problem I'm running into is that now users want to use our filtering tools on their custom fields, and I get a QueryException(Could not resolve property 'X') when I run the following query:
var query = session.CreateCriteria<Asset>()
.Create Alias("c", "Classification")
.Add(Restrictions.Eq(Projections.Property("c.X"), "value")
.ToList<Asset>();
Is it possible to do this kind of a projection? How else might I write this query (I really like the Criteria API, since I'm generating this query on the fly, but if I have to work some other way...)?
I don't think you want to use the projection there. Try swapping out
.Add(Restrictions.Eq(Projections.Property("c.X"), "value")
for
.Add(Restrictions.Eq("c.UserDefinedFields.X", "value")
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.
I'm having one self-referencing class. A child has a reference to its parent and a parent has a list of children. Since the list of children is ordered, I'm trying to map the relation using NHibernate's .
This is my mapping:
<class name="MyClass">
<id name="Id">
<generator class="native"/>
</id>
<list name="Children" cascade="delete" inverse="true">
<key column="ParentId"/>
<index column="ListOrder"/>
<one-to-many class="MyClass"/>
</list>
<many-to-one name="Parent" class="MyClass" column="ParentId"/>
</class>
The problem I'm having is when having a bi-directional mapping child<->parent, the list index (ListOrder) isn't updated in the database when I do my CRUD dance. This means that when I e.g. remove a child, I get holes in the children list after saving to the database and fetching the parent again. If I remove the bidirectionality, by not having a many-to-one from the children to the parent (and no inverse=true), the ListOrder is updated correctly.
Have any of you seen this before? Is there any simple solution?
Yes, it's because of inverse=true, an alternate solution would be to use a set or bag instead of list with order="ListOrder", add the ListOrder column as a property to the MyClass class with an empty setter and a getter that always returns it's index from it's parent's child collection. Like this:
<class name="MyClass">
<id name="Id">
<generator class="native"/>
</id>
<bag name="Children" cascade="delete" inverse="true" order-by="ListOrder">
<key column="ParentId"/>
<one-to-many class="MyClass"/>
</bag>
<property name="ListOrder" column="ListOrder"/>
<many-to-one name="Parent" class="MyClass" column="ParentId"/>
</class>
and the class
public class MyClass
{
public virtual int ID { get; set; }
public virtual IList<MyClass> Children { get; set; }
public virtual MyClass Parent { get; set; }
public virtual int ListOrder
{
get
{
if (Parent == null || !Parent.Children.Contains(this)) return -1;
return Parent.Children.IndexOf(this);
}
set { }
}
}