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.
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 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've been trying to get this working for a while now with no luck. I want to enable the 2nd level cache to prevent fetching from some lookup tables.
I set up my configuration in code
cfg = new Configuration();
cfg.Properties[NHibernate.Cfg.Environment.ConnectionProvider] = "NHibernate.Connection.DriverConnectionProvider";
string connectionString = connection.ConnectionString;
cfg.Properties[NHibernate.Cfg.Environment.ConnectionString] = connectionString;
cfg.Properties[NHibernate.Cfg.Environment.ConnectionDriver] = "NHibernate.Driver.SqlClientDriver";
cfg.Properties[NHibernate.Cfg.Environment.Dialect] = "NHibernate.Dialect.MsSql2005Dialect";
cfg.Properties[NHibernate.Cfg.Environment.CommandTimeout] = "720";
cfg.Properties[NHibernate.Cfg.Environment.CacheProvider] = "NHibernate.Cache.HashtableCacheProvider";
cfg.Properties[NHibernate.Cfg.Environment.UseSecondLevelCache] = "true";
cfg.Properties[NHibernate.Cfg.Environment.UseQueryCache] = "true";
cfg.AddAssembly("APPName.PersistentEntities");
factory = cfg.BuildSessionFactory();
Then in my xml configuration I added the cache attribute:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="APPName.PersistentEntities.LockStatus, APPName.PersistentEntities" table="LockStatus" lazy="false">
<meta attribute="class-description">lockstatus</meta>
<cache usage="read-only" />
<id name="Id" column="Id" type="Int32" unsaved-value="0">
<generator class="native"></generator>
</id>
<property name="Name" column="Name" type="String" length="100" not-null="true"/>
</class>
</hibernate-mapping>
This entity is then referenced from other uncached entities
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="APPName.PersistentEntities.Entity, APPName.PersistentEntities" table="Entity" lazy="false">
<meta attribute="class-description">entity</meta>
<id name="Id" column="Id" type="Int32" unsaved-value="0">
<generator class="native">
</generator>
</id>
<property name="Name" column="Name" type="String" length="500" not-null="true">
<meta attribute="field-description">name</meta>
</property>
<many-to-one name="LockStatus" column="LockStatusId" class="APPName.PersistentEntities.LockStatus, APPName.PersistentEntities" not-null="false" lazy="false">
</many-to-one>
</class>
</hibernate-mapping>
>
I query this other entity with something like:
session = factory.OpenSession();
IList<T> s = session.CreateSQLQuery(query).AddEntity(typeof(T)).SetCacheable(true).List<T>();
session.Clear();
session.Close();
The query and mappings run fine so to make sure its using the cache I try updating the names in the database. When I click again in the application I see the updated names so I assume it is not using the cache re-querying.
you need to add a cache declaration on the relation as well (LockStatus).
also-
you can use nHibernate's logging to see exactly the sql sent on every call.
I don't see why you'd want to use an SQLQuery in your case; you can simply use Query or QueryOver.
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 have a one-to-one relationship between a Company class and a CompanySettings class. When I create a new Company object, (a CompanySettings object is created in Company's constructor for its Settings property), and then
SaveOrUpdate(session, companyObject)
I expect the INSERT to cascade from the Company to the CompanySettings. However, this does not happen unless I explicitly call SaveOrUpdate on the CompanySettings object as well.
Mapping files shown below:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"`>
<class name="AST.Domain.Company, AST.Domain" table="Companies">
<id name="EntityID" column="CompanyId">
<generator class="guid.comb" />
</id>
<property name="CompanyName" />
. . .
<one-to-one name="Settings" class="AST.Domain.CompanySettings, AST.Domain"
constrained="true" lazy="false" />
</class>
</hibernate-mapping>
My mapping file for the Company Settings class:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="AST.Domain.CompanySettings, AST.Domain" table="CompanySettings">
<id name="EntityID" column="CompanyId">
<generator class="foreign">
<param name="property">Company</param>
</generator>
</id>
<property name="MaxUsers" />
<one-to-one name="Company" class="AST.Domain.Company, AST.Domain" />
</class>
</hibernate-mapping>
Have you tried specifying cascade="all" on your one-to-one mapping?
Set the cascade attribute of the one-to-one in the Company mapping.
But, on another note:
Have you thought of mapping the CompanySettings as a 'component' of Company instead of a separate entity ?
Isn't it so that 'CompanySettings' is a 'value object', and should be mapped better as a component ?
By doing this, you can put the CompanySettings values in the same table as 'Company', but it will be treated as a separate class.
Since this is a one-to-one mapping, I think it is a better option for your data model as well.
Then, your mapping would look something like this:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"`>
<class name="AST.Domain.Company, AST.Domain" table="Companies">
<id name="EntityID" column="CompanyId">
<generator class="guid.comb" />
</id>
<property name="CompanyName" />
. . .
<component name="Settings" class="AST.Domain.CompanySettings, AST.Domain">
<property name="MaxUsers" />
</component>
</class>
</hibernate-mapping>
You will have indeed 2 separate objects (Company & CompanySettings, and Company will have a 'Companysettings' object, but the settings will be saved in the Company table).
Or, is there any special reason on why you've put the CompanySettings in a separate table ? I mean, it is a one-to-one relation so this is completely not necessary (and imho even a bad practice :) ).