we have one to many to one relationship which we are trying to implement in NHibernate. This is a rephrase of my colleague's question.
There is Block with a collection of GroupPartnerInterests every of which has a Company. Following test method passes with SetMaxResults(3) but fails with SetMaxResults(5). Exception is
NHibernate.LazyInitializationException:
Initializing[Model.EntityClasses.BaseBlock#100000121437]-failed
to lazily initialize a collection of
role:
Model.EntityClasses.BaseBlock.GroupPartnerInterests,
no session or session was closed.
Question is why does SetMaxResults's argument matter?
The test method is:
[TestMethod]
public void TestGroupPartnerInterests()
{
using ( ISession session = SessionFactory.OpenSession() )
{
IList<Block> blocks = session
.CreateCriteria( typeof( Block ) )
.SetMaxResults( 5 ).List<Block>();
foreach ( var block in blocks )
{
TestContext.WriteLine( block.BlockId + " " + block.BlockName );
if ( block.GroupPartnerInterests != null )
{
foreach ( var gpi in block.GroupPartnerInterests )
{
TestContext.WriteLine( gpi.Company.CompanyName );
}
}
}
}
}
Configuration XMLs:
<class name="Block" table="[BLOCK]">
<id name="BlockId" column="GA_ID" access="field.camelcase-underscore" >
<generator class="assigned"/>
</id>
... data properties ...
<many-to-one name="Contract" access="field.camelcase-underscore"
fetch="select" unique="true">
<column name="EPC_ID"/>
</many-to-one>
<many-to-one name="Country" access="field.camelcase-underscore"
fetch="select" cascade="none">
<column name="COUNTRY_ID"/>
</many-to-one>
<set name="GroupPartnerInterests" access="field.camelcase-underscore"
cascade="all-delete-orphan" fetch="select">
<key property-ref="GroupId">
<column name="PAR_ID"/>
</key>
<one-to-many class="GroupPartnerInterest"/>
</set>
</class>
<class name="GroupPartnerInterest" table="[GROUP_PARTNER_INTERESTS]">
<composite-id >
<key-property name="GroupId" column="PAR_ID" />
<key-property name="CompanyId" column="PU_ID" />
</composite-id>
... data properties ...
<many-to-one name="Company" access="field.camelcase-underscore"
fetch="select" cascade="none">
<column name="PU_ID"/>
</many-to-one>
</class>
<class name="Company" table="[COMPANY]">
<id name="CompanyId" column="PU_ID" access="field.camelcase-underscore" >
<generator class="assigned"/>
</id>
... data properties ...
<set name="GroupPartnerInterests" access="field.camelcase-underscore"
cascade="all-delete-orphan" inverse="true" fetch="select">
<key>
<column name="PU_ID"/>
</key>
<one-to-many class="GroupPartnerInterest"/>
</set>
</class>
I'm not certain if this is the problem in your specific case, but in general, the use of implicit transactions is discouraged when using Nhibernate as discussed here. Your data access should always follow this pattern:
using(var session = sessionFactory.OpenSession())
using(var tx = session.BeginTransaction())
{
// execute code that uses the session
tx.Commit();
}
Related
lets say i have this database schema
[User] -1----n-> [Resource] -1----n-> [ResourceVersion]
and i want to select this using Nhibernate in one database roundtrip for user by username but select resourceVersions with future doesn work. How to hydrate collection of collections in one roundtrip using Futures? I prefer QueryOver or Criteria over HQL. I am using nHibernate 4.0.
public virtual User GetUserResources(string username)
using (ISession session = GetSession())
{
Resource resAlias = null;
User userAlias = null;
var result = session.QueryOver(() => userAlias)
.JoinQueryOver(x => x.Resources, () => resAlias)
.JoinQueryOver(() => resAlias.Versions)
.Where(() => userAlias.Login == username)
.Future<User>(); //THIS DOESNT WORK
var user = session.QueryOver<User>()
.Fetch(x => x.Resources).Eager
.Where(x => x.Login == username)
.SingleOrDefault<User>();//with this i can select user and resources
return user;
}
Mappings:
USER:
<class name="User" table="[User]">
<id name="Id" type="Int32">
<generator class="identity" />
</id>
<property name="Name">
<column name="Name" sql-type="varchar(100)" />
</property>
<property name="Email">
<column name="Email" sql-type="varchar(255)" />
</property>
<property name="Login">
<column name="Login" sql-type="varchar(50)" />
</property>
<property name="PasswordHash">
<column name="PasswordHash" sql-type="varchar(100)" />
</property>
<property name="CreateDate">
<column name="CreateDate" sql-type="datetime" />
</property>
<bag name="Resources" lazy="true" fetch="subselect" cascade="all-delete-orphan">
<key column="UserResource"/>
<one-to-many class="Resource" />
</bag>
</class>
RESOURCE:
<class name="Resource" table="[Resource]" abstract="true">
<id name="Id" type="Int64">
<generator class="identity" />
</id>
<discriminator column="Type"
not-null="true"
type="String" />
<bag name="Versions" cascade="all-delete-orphan" inverse="true" lazy="true" order-by="ActiveFrom DESC">
<key column="ResourceId" not-null="true"/>
<one-to-many class="Version"/>
</bag>
<subclass name="Resource1" discriminator-value="Res1" />
<subclass name="Resource2" discriminator-value="Res2" />
</class>
VERSION:
<class name="Version" table="Version">
<id name="Id" type="long">
<!--<column name="Id" sql-type="bigint"/>-->
<generator class="identity" />
</id>
...
<many-to-one name="Resource"
class="Resource"
column="ResourceId"/>
<property name="ActiveFrom">
<column name="ActiveFrom" sql-type="datetime" />
</property>
<property name="ActiveTo">
<column name="ActiveTo" sql-type="datetime"/>
</property>
...
Only query executed according to intelli trace in visual studio is this one:
SELECT this_.Id AS Id0_1_ ,
this_.Name AS Name0_1_ ,
this_.Email AS Email0_1_ ,
this_.Login AS Login0_1_ ,
this_.PasswordHash AS Password5_0_1_ ,
this_.CreateDate AS CreateDate0_1_ ,
resource2_.UserResource AS UserResource3_ ,
resource2_.Id AS Id3_ ,
resource2_.Id AS Id4_0_ ,
resource2_.Type AS Type4_0_
FROM
[User] this_ LEFT OUTER JOIN [Resource] resource2_
ON this_.Id
=
resource2_.UserResource
WHERE this_.Login
=
#p0;
and in #p0 is username i pass to method. No sign of versions at all which i find a little odd.
you are never iterating the IEnumerable returned by the future so it never executes it. I don't have NH 4.0 here right now but the following might work
public virtual User GetUserWithResources(string username)
{
using (ISession session = GetSession())
{
Resource resAlias = null;
return session.QueryOver<User>()
.Where(user => user.Login == username)
.Left.JoinQueryOver(x => x.Resources)
.Left.JoinQueryOver(res => res.Versions)
.TransformUsing(Transformers.DistinctRootEntity)
.List<User>().SingleOrDefault();
}
}
We have a class, SpecialContainer (itself a subclass of Container), that has a list of contents of type TypeThreeContent:
class SpecialContainer {
private IList<TypeThreeContent> _contents = new List< TypeThreeContent >();
}
TypeThreeContent is just a subclass of BaseContent, both of which have some uninteresting properties I've omitted for simplicitly. Here's the mapping:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class xmlns="urn:nhibernate-mapping-2.2" discriminator-value="0" name="AbstractBaseContent" table="tblContent">
<id access="field" name="_id" type="System.Int32" unsaved-value="0">
<column name="ContentID" />
<generator class="identity" />
</id>
<discriminator type="Int32">
<column name="FormsType" />
</discriminator>
<subclass name="TypeOneContent" discriminator-value="1">
</subclass>
<subclass name="TypeTwoContent" discriminator-value="2">
</subclass>
<subclass name="TypeThreeContent" discriminator-value="3">
</subclass>
</class>
<class xmlns="urn:nhibernate-mapping-2.2" discriminator-value="-1" name="BaseContainer" table="tblContainers">
...
<subclass name="SpecialContainer" discriminator-value="1">
<join table="tblSpecialContainers">
...
<bag access="field" cascade="none" inverse="true" lazy="true" name="_contents">
<key>
<column name="ContainerId" />
</key>
<one-to-many class="TypeThreeContent" />
</bag>
</join>
</class>
</hibernate-mapping>
The problem I'm having is that rows with other discriminator values (i.e. 1 and 2) are getting picked up in NH's fetching of the list. I used NHProf to verify that the SQL query is not specifying a discriminator in its criteria:
SELECT ...
FROM dbo.tblContent content0_
WHERE content0_.SpecialContainerId = 140 /* #p0 */
What's going on here?
I am trying figure out how to set up the nhibernate mapping for the class BOM shown in the code sample below. the part i am having trouble with is how to map the property BOM.Components so that the mapping is equivalent to the SQL I have posted below.
Here is my mapping for BOM so far:
:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Inventory.Core"
namespace="Inventory.Core.Entities">
<class name="BOM" table="bom">
<id name="Id" column="bom_id">
<generator class="hilo" />
</id>
<many-to-one name="Product" class="Material" column="mtrl_id" foreign-key="fk_BOM_Material" unique="true" not-null="true" />
<map name="Components" table="bom_cmpnnts">
<key column="bom_id" foreign-key="fk_BOMComponent_BOM" not-null="true" />
</map>
</class>
</hibernate-mapping>
Update:
Here is the Mapping for BOMComponent:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Inventory.Core"
namespace="Inventory.Core.Entities">
<class name="BOMComponent" table="bom_cmpnnt">
<id name="Id" column="bom_cmpnnt_id">
<generator class="hilo" />
</id>
<version name="Version" column="bom_cmpnnt_vrsn" />
<many-to-one name="Component" class="Material" column="mtrl_id" foreign-key="fk_BOMComponent_Material" not-null="true" />
<property name="Unit" column="bom_cmpnnt_unt" not-null="true" />
<property name="Quantity" column="bom_cmpnnt_qntty" not-null="true" />
<many-to-one name="User" class="User" column="upsrt_usr_id" foreign-key="fk_BOMComponent_User" lazy="false" not-null="true" />
<property name="Timestamp" column="upsrt_dttm" generated="always" />
</class>
</hibernate-mapping>
Classes:
public class BOM
{
int Id { get; set; }
Material Product { get; set; }
IDictionary<int, BOMComponent> Components { get; set; }
// Key = BOMComponent.Material.Id
// for each BOM, materials should be unique
//other properties ...
}
public class BOMComponent
{
int Id { get; set; }
Material Material { get; set; }
//other properties ...
}
public class Material
{
int Id { get; set; }
//other properties
}
This is the what the SQL should look like:
delimiter $$
CREATE TABLE `mtrl` (
`mtrl_id` int(11) NOT NULL,
PRIMARY KEY (`mtrl_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8$$
delimiter $$
CREATE TABLE `bom` (
`bom_id` int(11) NOT NULL,
`mtrl_id` int(11) NOT NULL,
PRIMARY KEY (`bom_id`),
UNIQUE KEY `mtrl_id_UNIQUE` (`mtrl_id`),
KEY `fk_BOM_Material` (`mtrl_id`),
CONSTRAINT `fk_BOM_Material` FOREIGN KEY (`mtrl_id`) REFERENCES `mtrl` (`mtrl_id`) ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8$$
delimiter $$
CREATE TABLE `bom_cmpnnt` (
`bom_cmpnnt_id` int(11) NOT NULL,
`bom_id` int(11) NOT NULL,
`mtrl_id` int(11) NOT NULL,
PRIMARY KEY (`bom_cmpnnt_id`),
UNIQUE KEY `IX_BOMComponent_UQ` (`bom_id`,`mtrl_id`),
KEY `fk_BOMComponent_BOM` (`bom_id`),
KEY `fk_BOMComponent_Material` (`mtrl_id`),
CONSTRAINT `fk_BOMComponent_BOM` FOREIGN KEY (`bom_id`) REFERENCES `bom` (`bom_id`) ON DELETE CASCADE ON UPDATE NO ACTION,
CONSTRAINT `fk_BOMComponent_Material` FOREIGN KEY (`mtrl_id`) REFERENCES `mtrl` (`mtrl_id`) ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8$$
I hate to answer my own question but I got it to work the way I wanted by using these two mappings:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Inventory.Core"
namespace="Inventory.Core.Entities">
<class name="BOM" table="bom">
<id name="Id" column="bom_id">
<generator class="hilo" />
</id>
<version name="Version" column="bom_vrsn" />
<many-to-one name="Product" class="Material" column="mtrl_id" foreign-key="fk_BOM_Material" unique="true" not-null="true" />
<map name="Components" table="bom_cmpnnt">
<key column="bom_id" foreign-key="fk_BOMComponent_BOM" not-null="true" />
<index column="mtrl_id" type="System.Int32" />
<composite-element class="BOMComponent" />
</map>
<many-to-one name="User" class="User" column="upsrt_usr_id" foreign-key="fk_BOM_User" lazy="false" not-null="true" />
<property name="Timestamp" column="upsrt_dttm" generated="always" />
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Inventory.Core"
namespace="Inventory.Core.Entities">
<class name="BOMComponent" table="bom_cmpnnt">
<id name="Id" column="bom_cmpnnt_id">
<generator class="hilo" />
</id>
<version name="Version" column="bom_cmpnnt_vrsn" />
<many-to-one name="Component" class="Material" column="mtrl_id" foreign-key="fk_BOMComponent_Material" not-null="true" />
<property name="Unit" column="bom_cmpnnt_unt" not-null="true" />
<property name="Quantity" column="bom_cmpnnt_qntty" not-null="true" />
<many-to-one name="User" class="User" column="upsrt_usr_id" foreign-key="fk_BOMComponent_User" lazy="false" not-null="true" />
<property name="Timestamp" column="upsrt_dttm" generated="always" />
</class>
</hibernate-mapping>
Currious behavior here. If I create a new HashCode, save the HashCode, then add a Transaction to the Transactions collection, the cascade is failing on Update. The Transaction object doesn't appear in the DB, and, curiously, the HashCode object's properties aren't updated either!
I have no idea what could be causing this. Here is the relevant mapping:
<class name="MyProject.HashCode, MyProject" table="HashCodes">
<id column="Id" name="Id">
<generator class="native" />
</id>
<many-to-one name="User" column="UserId" class="MyProject.User, MyProject" />
<property name="Hash" />
<property name="PasswordHash" />
<property name="InitialValue" update="false" />
<property name="CurrentValue" update="true" />
<property name="ClaimedDate" />
<property name="ClaimId" column="RowGuid" generated="insert" />
<bag name="Transactions" table="Transactions" cascade="all" inverse="true">
<key column="HashCodeId" />
<many-to-many column="Id" class="MyProject.Transaction, MyProject" />
</bag>
</class>
<class name="MyProject.Transaction, MyProject" table="Transactions">
<id column="Id" name="Id">
<generator class="native" />
</id>
<many-to-one name="HashCode" column="HashCodeId" class="MyProject.HashCode, MyProject" />
<property name="AmountCharged" />
<property name="AmountBilled" />
<property name="PreviousBalance" />
<property name="Memo" />
<property name="TransactionDate" generated="insert" update="false" />
</class>
Here is the test case that is failing, too, in case it's relevant:
[Test]
public void TransactionsCascadeWhenUpdatingHashCodes()
{
var user = TestFactory.CreateUser();
var hashCode = TestFactory.CreateHashCode(user);
var transaction = new Transaction
{
AmountBilled = hashCode.CurrentValue,
AmountCharged = decimal.Subtract(hashCode.CurrentValue, decimal.Multiply(hashCode.CurrentValue, 0.03M)),
HashCode = hashCode,
Memo = "This is a test",
PreviousBalance = hashCode.CurrentValue,
TransactionDate = DateTime.Now
};
hashCode.Transactions.Add(transaction);
// Now try to save it.
var hashCodeRepository = new HashCodeRepository(Session);
hashCodeRepository.Update(hashCode);
// Now see if that transaction is good to go
Assert.IsTrue(hashCode.Transactions[0].Id > 0);
// See if that transaction got persisted.
var loadedTransaction = Session.Load<Transaction>(hashCode.Transactions[0].Id);
Assert.IsNotNull(loadedTransaction);
}
// The repository method that is called...
public virtual void Update(TObj obj)
{
CurrentSession.Update(obj);
}
Anyone have any suggestions or ideas?
You are not flushing the session anywhere.
Change your update method to the following:
public virtual void Update(TObj obj)
{
using (ITransaction tx = CurrentSession.BeginTransaction())
{
CurrentSession.SaveOrUpdate(obj);
tx.Commit();
}
}
I have following model:
<class name="Person" table="Person" optimistic-lock="version">
<id name="Id" type="Int32" unsaved-value="0">
<generator class="native" />
</id>
<!-- plus some properties here -->
</class>
<class name="Event" table="Event" optimistic-lock="version">
<id name="Id" type="Int32" unsaved-value="0">
<generator class="native" />
</id>
<!-- plus some properties here -->
</class>
<class name="PersonEventRegistration" table="PersonEventRegistration" optimistic-lock="version">
<id name="Id" type="Int32" unsaved-value="0">
<generator class="native" />
</id>
<property name="IsComplete" type="Boolean" not-null="true" />
<property name="RegistrationDate" type="DateTime" not-null="true" />
<many-to-one name="Person" class="Person" column="PersonId" foreign-key="FK_PersonEvent_PersonId" cascade="all-delete-orphan" />
<many-to-one name="Event" class="Event" column="EventId" foreign-key="FK_PersonEvent_EventId" cascade="all-delete-orphan" />
</class>
There are no properties pointing to PersonEventRegistration either in Person nor in Event.
When I try to delete an entry from PersonEventRegistration, I get the following error:
"deleted object would be re-saved by cascade"
The problem is, I don't store this object in any other collection - the delete code looks like this:
public bool UnregisterFromEvent(Person person, Event entry)
{
var registrationEntry = this.session
.CreateCriteria<PersonEventRegistration>()
.Add(Restrictions.Eq("Person", person))
.Add(Restrictions.Eq("Event", entry))
.Add(Restrictions.Eq("IsComplete", false))
.UniqueResult<PersonEventRegistration>();
bool result = false;
if (null != registrationEntry)
{
using (ITransaction tx = this.session.BeginTransaction())
{
this.session.Delete(registrationEntry);
tx.Commit();
result = true;
}
}
return result;
}
What am I doing wrong here?
As far as I know, cascade="all-delete-orphan" belongs on the collection mapping element, not the many-to-one. You haven't shown the other two parts of your mapping so I can't say for sure but this is possible (likely) the problem.
I think Person should look something like:
<!-- other properties -->
<set name="Events" inverse="true" cascade="all-delete-orphan">
<key column="Person_id" />
<one-to-many class="PersonEventRegistration" />
</set>
Event:
<!-- similar mapping for Event -->
PersonEventRegistration:
<!-- other properties -->
<many-to-one name="Person" class="Person" column="PersonId" foreign-key="FK_PersonEvent_PersonId" cascade="delete" <!-- or many ="all" ? --> />
Actually, the above could be conflicting cascades (which might be what you have). So really, my answer is two things:
cascade="all-delete-orphan" has no meaning on many-to-one.
Make sure you have really thought-through how you are working with your entities and how they should cascade their operations.
Try de-referencing Person and Event in the delete:
public bool UnregisterFromEvent(Person person, Event entry)
{
var registrationEntry = this.session
.CreateCriteria<PersonEventRegistration>()
.Add(Restrictions.Eq("Person", person))
.Add(Restrictions.Eq("Event", entry))
.Add(Restrictions.Eq("IsComplete", false))
.UniqueResult<PersonEventRegistration>();
bool result = false;
if (null != registrationEntry)
{
using (ITransaction tx = this.session.BeginTransaction())
{
registrationEntry.Person = null;
registrationEntry.Event = null;
this.session.Delete(registrationEntry);
tx.Commit();
result = true;
}
}
return result;
}
Also, I wasn't aware that you could add a restriction on an object, I would have written this using aliases and IDs.
.Add(Restrictions.Eq("Person", person))
.Add(Restrictions.Eq("Event", entry))