I have the following Nhibernate XML file, edited to the core bits:
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping assembly="TTP.DAO" namespace="TTP.DAO" xmlns="urn:nhibernate-mapping-2.2">
<class name="InitialProcessSetup" table="InitialProcessSetup" lazy="true" >
<id name="SetupID" column="setupID" unsaved-value="0" type="System.Int32">
<generator class="identity"/>
</id>
<property name="DateCreated" column="DateCreated" />
<many-to-one class="StoredUser" name="CreatedBy" column="CreatedBy" cascade="all-delete-orphan" not-null="true"/>
<property name="OriginalDeadline" column="Deadline" />
<property name="Detail" column="Detail" />
<bag name="Subjects" table="InitialProcessSetupRatingSubject" inverse="true" cascade="all-delete-orphan">
<key column="SetupId" not-null="true" />
<many-to-many column="SubjectStoredUserId" class="StoredUser" />
</bag>
<bag name="Reviewers" table="InitialProcessSetupReviewer" cascade="all-delete-orphan">
<key column="SetupId" not-null="true" />
<many-to-many column="ReviewerStoredUserId" class="StoredUser" />
</bag>
<bag name="Invitations" cascade="all-delete-orphan">
<key column="Setup" not-null="true"/>
<one-to-many class="ProcessInvitation" />
</bag>
</class>
I populate a new instance of the class indicated in the above mapping and save via GenericDAO().SaveOrUpdate(setup); and it saves the main object details but does not store the bit.
On the class Subjects is defined as a virtual property that returns IList-StoredUser. The class constructor sets this property to a new List-StoredUser-().
The table indicated by the Subjects bag element is a pure mapping table that is it has a SetupID and StoredUserID column and that's it.
I add using a StoredUser to my derived StoredUserDAO() to the Subjects lists before saving. After the call to SaveOrUpdate() I also call Flush() as well.
In my database (SQL Server 2008R2) The main setup data is saved and the stored user I added to the Subjects list property is also saved it's just the mapping table does not get the setupID/StoredUserID added.
I'm a bit of a noob with Nhibernate and this is an existing project.
Any help ideas on what is wrong would be most appreciated.
You have set inverse="true" for the Subjects collection. This implies that for NHibernate to create a record in the relation table, it must be the StoredUser that references the InitialProcessSetup.
Related
I have a mapped view, which I would like to reference in another mapping.
Mapped View:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Model.TagBE, Model" table="vw_CurrentTag" lazy="true" schema="dbo">
<id name="Id" column="TAG_HISTORY_ID" type="Guid"
unsaved-value="00000000-0000-0000-0000-000000000000">
</id>
<property name="Tag" column="TAG"/>
<property name="TagStatus" column="TAG_STATUS"/>
<property name="Created" column="CREATE_DATE"/>
<many-to-one name="Defect" class="Model.DefectBE, Model" fetch="join"
not-found="ignore" cascade="none" column="DEFECT_ID" />
<many-to-one name="CreatedBy" class="Model.UserBE, Model" lazy="false"
fetch="join" cascade="none" column="CREATE_USER_ID" />
</class>
</hibernate-mapping>
I would like to use it in this mapping, to get a single TagBE:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="DefectBE, Model" lazy="true" table="DEFECT" schema="dbo">
<id name="Id" column="DEFECT_ID" type="Guid">
<generator class="assigned" />
</id>
<property name="Sn" column="SERIAL_NUMBER" />
<property name="Description" column="PART_DESCRIPTION" />
<many-to-one name="System" class="Model.SystemBE, Model" fetch="join"
cascade="none" column="SYSTEM_ID" not-found="ignore"/
</class>
</hibernate-mapping>
The above mappings have been trimmed down for the sake of an example (removed a bunch of properties and and man-to-one relationships not related to the problem at hand.
The many-to-one shown with the name "System" works as expected, and it is just another mapped table, not a view. My problem is that when I try to map the view "vw_CurrentTag", I can only access the data when I specifically use ISession and ICriteria to get specificaly something of the TagBE class.
When I try to use the following in the DefectBE mapping, it just gives gives null for the TagBE:
<many-to-one name="Tag" class="Model.TagBE, Model" fetch="join" cascade="none"
column="DEFECT_ID" not-found="ignore"/>
I have also tried using one-to-one, but i always get null when the TagBE object part of a DefectBE object.
If it's not possible I can always use a Bag with only 1 entry.
The view is just a query on a single table to return unique Tag column based on other columns.
the many-to-one "Tag" uses the column DEFECT_ID on the table DEFECT to match the primary key of the view TAG_HISTORY_ID which is never equal.
What you probably want is a one-to-one mapping on the Defect class which points to the DEFECT_ID on the view.
<one-to-one name="Tag" class="Model.TagBE, Model" fetch="join" property-ref="Defect" foreign-key="none"/>
I have some problems trying to get a bag collection of a class to be loaded through custom sql.
Here's the xml mappings I have for my class
<hibernate-mapping>
<class name="alekso.npe.model.Utente" table="VNPEZZ_UTE_MAT" lazy="false">
<id name="matricola" column="C_UTE_MAT">
</id>
<property name="nome" column="T_NOM" />
<property name="cognome" column="T_COG" />
<property name="email" column="T_EML" />
<bag name="ruoli" table="VNPEZX_UTE_APP_TRN"
inverse="false" lazy="true" fetch="select" cascade="all" >
<key column="C_UTE_MAT" />
<one-to-many class="alekso.npe.model.Ruolo" />
<loader query-ref="rolesQuery"/>
</bag>
</class>
<class name="alekso.npe.model.Ruolo" table="VNPEZH_TIP_USR"
lazy="false" where="C_APP = 'NPE'">
<id name="codice" column="C_TIP_USR">
</id>
<property name="nome" column="T_DES_TIP_USR"/>
</class>
<sql-query name="rolesQuery">
<return alias="role" class="alekso.npe.model.Ruolo"></return>
<load-collection alias="ruoli" role="alekso.npe.model.Ruolo" ></load-collection>
<![CDATA[select {ruolo.*}
from NPEA.vnpezx_ute_app_trn permesso join NPEA.vnpezh_tip_usr ruolo
on ruolo.c_tip_usr = permesso.c_tip_usr
where permesso.c_app = 'NPE'
and permesso.c_ute_mat = :matricola ]]>
</sql-query>
</hibernate-mapping>
But when I run the application I get an error:
org.hibernate.HibernateException: Errors in named queries: rolesQuery in the buildSessionFactory phase.
Can you tell me what's wrong with this mapping?
I tried both with and without the <return> tag inside the <sql-query> but it still don't work
I found out the error... the line <load-collection alias="ruoli" role="alekso.npe.model.Ruolo" ></load-collection> inside the custom query definition gives the error.
Without that line, everything works fine (almost... the collection of roles is lazy loaded even if I set lazy="false" everywhere).
But that line I removed is on the documentation, so I'd like to know why it's wrong.
I'll leave the question open for other to give some hints on this.
I am using Nhibernate for my ORM.
I have a class "Control" that has a one to many relationship with ControlDetail (ie. A control has many controlDetails).
In the control xml config it has the following
<bag name="ControlDetails" lazy="true" access="property" order-by="SortOrder asc" cascade="all-delete-orphan"
table="ControlDetail">
<key column="ControlID"/>
<one-to-many class="ControlDetail"/>
</bag>
such that I believe unless otherwise told it would lazy load the controldetails of a control.
I am running NHProf to try and fix some performance issues we are having and it has identfied a Select N + 1 issue around these classes.
We are using a repository DA layer and I have tried to see if I can add in a way to eagerly fetch the data when required and came up with this.
public T GetById<T>(Int32 id, List<string> fetch) where T : BaseObject
{
T retObj = null;
ISession session = EnsureCurrentSession();
{
ICriteria criteria = session.CreateCriteria(typeof (T));
criteria.SetCacheable(true);
criteria.Add(Expression.Eq("Id", id));
foreach(var toFetch in fetch)
{
criteria.SetFetchMode(toFetch, FetchMode.Eager);
}
retObj = criteria.List<T>().FirstOrDefault();
}
return retObj;
}
*Note: I'm not a fan of how the repository is setup but it was done before I came to the project so we have to stick with this pattern for now.
I call this method like so
public Control GetByIDWithDetail(int controlID)
{
return DataRepository.Instance.GetById<Control>(controlID, new List<string>() {"ControlDetail"});
}
When I debug the GetByID method and look at the retObj I can see that the ControlDetails list has been populated (although strangely enough I also noticed without the setfetchmode set the list was being populated)
Even with this fix NHProf identifies a Select N+1 issue with the following line
List<ControlDetail> details = control.ControlDetails.ToList();
What exactly am I missing and how can I stop this N+1 but still iterate over the controlDetails list
EDIT: the xml configs look like so (slightly edited to make smaller)
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="DomainObjects" assembly="DomainObjects">
<class name="Control" lazy="false" table="Control" optimistic-lock="version" select-before-update="true" >
<id name="Id" type="int" column="ControlID" access="property">
<generator class="native" />
</id>
<version name="Version" column="Version" />
<property name="AdministrativeControl" column="AdministrativeControl" access="property" not-null="true" />
<property name="Description" column="ControlDescription" access="property" />
<property name="Title" column="Title" access="property" />
<property name="CountOfChildControls" access="property" formula="(select count(*) from Control where Control.ParentControlID = ControlID)" />
<bag name="ControlDetails" lazy="true" access="property" order-by="SortOrder asc" cascade="all-delete-orphan"
table="ControlDetail">
<key column="ControlID" />
<one-to-many class="ControlDetail" />
</bag>
</class>
</hibernate-mapping>
and this
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="DomainObjects" assembly="DomainObjects">
<class name="ControlDetail" lazy="false" table="ControlDetail" select-before-update="true" optimistic-lock="version">
<id name="Id" type="int" column="ControlDetailID" access="property">
<generator class="native" />
</id>
<version name="Version" column="Version" />
<property name="Description" column="Description" access="property" not-null="true" />
<property name="Title" column="Title" access="property" />
<many-to-one name="Control" lazy="false" class="Control" column="ControlID" access="property"/>
</class>
</hibernate-mapping>
There is a substantial difference between eager fetching and eager loading. Fetching means: putting into the same query. It has several side effects and may break it. Eager loading means forcing NH to load it immediately, not waiting until it is accessed the first time. It is still loaded using additional queries, which leads to the N+1 problem.
NH probably does not eagerly fetch because the property is called ControlDetails, but you pass ControlDetail as argument.
On the other side, this isn't a good way to avoid the N+1 problem. Use batch-size instead. It is fully transparent to the application and reduces the amount of queries by the given factor (values from 5 to 50 make sense, use 10 if you don't know what to use).
One option, you can reduce the select n+1 problem, keep lazy loading and forget about eager loading....
All you need to do is to add one simple attribute batch-size to your XML
<bag name="ControlDetails" batch-size="25" ..>
I also noticed that you have lazy="true" in your mapping, did you try changing to false?
I am having this problem..
using (var transaction = Session.BeginTransaction())
{
var t = new Ticket();
t.Title = "TestTicket";
var ticketId = (Guid)Session.Save(t);
var pe = new ProcessExec();
pe.Ticket = t;
Session.Save(pe);
var ticket = Session.Get<Ticket>(ticketId);
transaction.Commit();
Assert.NotNull(ticket);
Assert.True(ticket.ProcessExecCollection.Count > 0);
}
Now the problem is this that the assert fails on
Assert.True(ticket.ProcessExec.Count>0).
But If I do
Session.Refresh(ticket);
just after the transaction.Commit(), everithing works fine.
How to tell NHibernate that when I create new ProcessExec and set its ticket, to automatically update the ticket?
I need this because I do lots of stuff creating and selecting in a transaction.
Please help.
<class name="Domain.Model.Sdwwf.ProcessExec" table="[PROCESS_EXEC]" schema="[SDWWF]" dynamic-update="true">
<id name="Id" column="ID" type="Guid">
<generator class="guid" />
</id>
<version name="Version" column="VERSION" />
<many-to-one name="Ticket" column="[TICKET_ID]" cascade="save-update, persist" />
<class name="Domain.Model.Sdwwf.Ticket" table="[TICKET]" schema="[SDWWF]" dynamic-update="true">
<id name="Id" column="ID" type="Guid">
<generator class="guid" />
</id>
<version name="Version" column="VERSION" />
<bag name="ProcessExec" inverse="true" cascade="all">
<key column="[TICKET_ID]" />
<one-to-many class="Softworks.SDW.Domain.Model.Sdwwf.ProcessExec" />
</bag>
<property name="Title" column="[TITLE]" />
</class>
What's happening is that when you do the ISession.Get call before you commit the transaction, the session has not been flushed yet, so it gets the cached version of the entity. NHibernate is not like using a database connection as it is very lazy most of the time. It will only go to the database when it absolutely has to.
You can either manually refresh the entity, just like you said or you can manually do
ticket.ProcessExecCollection.Add(new ProcessExec());
Then when you save the ticket, NHibernate will cascade the newly created ProcessExec. Thereby saving you a second call to NHibernate.
I am having an issue with saving entities with one-to-one relationships. I just want to save the parent entity and have the child be save aswell but I am having to save both separately.
This is an example of what I am having to do, otherwise the child is not saved.
var session = SessionProvider.OpenSession.Session;
using (var tx = session.BeginTransaction())
{
try
{
session.SaveOrUpdate(parent);
if (parent.Child.IsPersisted)
{
session.Update(parent.Child);
}
else
{
session.Save(parent.Child);
}
}
}
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="false" assembly="xxx">
<class name="Parent" polymorphism="explicit" table="Parent">
<id name="Id" column="JointID" type="int">
<generator class="native" />
</id>
<one-to-one name="Child" class="Child" />
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="false"
assembly="xxx">
<class name="Child" xmlns="urn:nhibernate-mapping-2.2" polymorphism="explicit" table="Child">
<id name="Id" column="JointID" type="int" unsaved-value="0">
<generator class="native" />
</id>
<many-to-one name="Parent" column="JointID" insert="false" update="false" />
</class>
</hibernate-mapping>
Any ideas on how I can just make it save without having to do two save calls?
When I set the relationship to cascade as suggested below I get foreign key constraint errors. If I analyse the queries with NHProf, its trying to use the temporary id (-1) as the JointId in the insert statement rather than the newly created parent id. The JointId in the Parent table is an Identity key, perhaps that is an issue?
You'll need to enable cascading on your <one-to-one> mapping to have this work properly.
Something like:
<one-to-one name="Child" class="Child" cascade="save-update" />
You can read up on the various cascade settings here.