how to delete fk children in nhibernate - nhibernate

I would like to delete the ICollection PriceBreaks from Product.
I'm using the following method. However they dont seem to delete. What am i missing.
When i step thru. i notice that "product.PriceBreaks.Clear();" doesn't actually clear the items. Do i need to flush or something?
public void RemovePriceBreak(int productId)
{
using (ISession session = EStore.Domain.Helpers.NHibernateHelper.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
var product = session.Get<Product>(productId);
// i can see 20 PriceBreak records, tho nh prof saids PriceBreak table hasn't been hit
product.PriceBreaks.Clear();
session.SaveOrUpdate(product);
transaction.Commit(); // PriceBreak table is hit here with select statement, no delete?
}
}
Here are my hbm files
<class name="Product" table="Products">
<id name="Id" type="Int32" column="Id" unsaved-value="0">
<generator class="identity"/>
</id>
<property name="CompanyId" column="CompanyId" type="Int32" not-null="true" />
<property name="Name" column="Name"/>
<set name="PriceBreaks" table="PriceBreaks" generic="true" cascade="all-delete-orphan" inverse="true" >
<key column="ProductId" />
<one-to-many class="EStore.Domain.Model.PriceBreak, EStore.Domain" />
</set>
</class>
<class name="PriceBreak" table="PriceBreaks">
<id name="Id" type="Int32" column="Id" unsaved-value="0">
<generator class="identity"/>
</id>
<many-to-one name="Product" column="ProductId" not-null="true" class="EStore.Domain.Model.Product, EStore.Domain" />
</class>
My Entities
public class Product
{
public virtual int Id { get; set; }
public virtual ICollection<PriceBreak> PriceBreaks { get; set; }
public virtual void AddPriceBreak(PriceBreak priceBreak)
{
priceBreak.Product = this;
PriceBreaks.Add(priceBreak);
}
}
public class PriceBreak
{
public virtual int Id { get; set; }
public virtual Product Product { get; set; }
}
Here is the sql from nhprof
//
SELECT product0_.Id as Id0_0_
FROM Products product0_
WHERE product0_.Id = 23 /* #p0 */
and then
SELECT pricebreak0_.ProductId as ProductId1_,
pricebreak0_.Id as Id1_,
pricebreak0_.Id as Id1_0_,
pricebreak0_.ProductId as ProductId1_0_,
FROM PriceBreaks pricebreak0_
WHERE pricebreak0_.ProductId = 23 /* #p0 */
there is no update or select

Product probably needs to be associated with that session. It looks like you are trying to save a detached object. Which means there is no session to track that call to clear the PriceBreaks. Change it to this...
public void RemovePriceBreak(int productId)
{
using (ISession session = EStore.Domain.Helpers.NHibernateHelper.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
var product = session.Get<Product>(productId);
product.PriceBreaks.Clear();
session.SaveOrUpdate(product);
transaction.Commit();
}
}
or move the session and transaction management further up. Which would basically eliminate the need for this method.

Have you tried deleting the cascade="all" on the PriceBreak side ?
Have you tried using session.Save() instead of SaveOrUpdate() ?

Related

Cascade Save - StaleObjectStateException: Row was updated or deleted by another transaction

Having an issue with updating the NHibernate version. Current version is 3.3.1.4000 and trying to update to 4.
After updating the unit test which does save with cascade fails with:
NHibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [NHibernateTests.TestMappings.ProductLine#cdcaf08d-4831-4882-84b8-14de91581d2e]
The mappings:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateTests" namespace="NHibernateTests.TestMappings">
<class name="Product" lazy="false" table="UserTest">
<id name="Id">
<generator class="guid"></generator>
</id>
<version name="Version" column="Version" unsaved-value="0"/>
<property name="Name" not-null="false"></property>
<property name="IsDeleted"></property>
<bag name="ProductLines" table="ProductLine" inverse="true" cascade="all" lazy="true" where="IsDeleted=0" >
<cache usage="nonstrict-read-write" />
<key column="UserId" />
<one-to-many class="ProductLine" />
</bag>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateTests" namespace="NHibernateTests.TestMappings">
<class name="ProductLine" where="IsDeleted=0" lazy="false">
<cache usage="nonstrict-read-write" />
<id name="Id">
<generator class="guid"></generator>
</id>
<version name="Version" column="Version" unsaved-value="0"/>
<property name="IsDeleted"></property>
<many-to-one name="Product" class="Product" column="UserId" not-null="true" lazy="proxy"></many-to-one>
</class>
</hibernate-mapping>
Classes:
public class Product
{
public Guid Id { get; set; }
public int Version { get; set; }
public bool IsDeleted { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public IList<ProductLine> ProductLines { get; private set; }
public Product()
{
ProductLines = new List<ProductLine>();
}
}
public class ProductLine
{
public Guid Id { get; set; }
public int Version { get; set; }
public bool IsDeleted { get; set; }
public Product Product { get; set; }
}
The test:
[TestMethod]
public void CascadeSaveTest()
{
var product = new Product
{
Id = Guid.NewGuid(),
Name = "aaa",
IsActive = true
};
var productLine = new ProductLine
{
Id = Guid.NewGuid(),
Product = product,
};
product.ProductLines.Add(productLine);
using (var connection = new RepositoryConnection())
{
using (var repositories = new Repository<Product>(connection))
{
repositories.Create(product);
//the below just calls the Session.Transaction.Commit();
connection.Commit(); //NH3.3.1.400 passes, NH4 fails
}
}
}
Thanks for you ideas in advance.
Well, I guess I have now understood what causes the error with NH 4. And If I am right, that is a bit contrived case causing this behavior to be hard to qualify as a bug.
In your example, products lines are mapped through a bag. A bag can contains duplicates, which requires an intermediate table between Product and ProductLine. (Something like a ProductProductLine table with an UserId (ProductId) column, and a ProductLineId column.)
You have set this intermediate table as being the ProductLine table. I suspect the commit to db causes NHibernate to insert the product, then the product line, then to try to insert the relationship by inserting again in ProductLine table. (You may check that by profiling SQL queries on your db.)
Things are a bit muddy, since doc states (emphasis is mine):
table (optional - defaults to property name) the name of the
collection table (not used for one-to-many associations)
But then, how to honor the bag semantics allowing duplicates in the collection? From the same doc:
A bag is an unordered, unindexed collection which may contain the same
element multiple times.
Anyway, within your example, you really should map your one-to-many with a set, as shown in doc.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateTests" namespace="NHibernateTests.TestMappings">
<class name="Product" lazy="false" table="UserTest">
<id name="Id">
<generator class="guid"></generator>
</id>
<version name="Version" column="Version" unsaved-value="0"/>
<property name="Name" not-null="false"></property>
<property name="IsDeleted"></property>
<set name="ProductLines" inverse="true" cascade="all" lazy="true" where="IsDeleted=0" >
<cache usage="nonstrict-read-write" />
<key column="UserId" />
<one-to-many class="ProductLine" />
</set>
</class>
</hibernate-mapping>
And change your collection type for using .Net fx 4 System.Collections.Generic.ISet<T>.
public ISet<ProductLine> ProductLines { get; private set; }
public Product()
{
ProductLines = new HashSet<ProductLine>();
}
If this causes your trouble to disappear, it would mean something has changed in bag handling in NH 4. But should we consider this change as being a bug? Not sure, since using a bag in this case does not look right for me.
Drilling it further down turned out that NHibernate4 has problems identifying whether it is a new entity or an already existent when concerned with Cascade.
With the scenario in question it was calling a SQL Update for the ProductLine, rather than Create.
It works fine with the below changes, however I'm quite puzzled with such a changes in between NHibernate versions.
Change to ProductLine Mapping
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateTests" namespace="NHibernateTests.TestMappings">
<class name="ProductLine" where="IsDeleted=0" lazy="false">
<cache usage="nonstrict-read-write" />
<!-- here comes the updated line -->
<id name="Id" type="guid" unsaved-value="00000000-0000-0000-0000-000000000000">
<generator class="guid"></generator>
</id>
<version name="Version" column="Version" unsaved-value="0"/>
<property name="IsDeleted"></property>
<many-to-one name="Product" class="Product" column="UserId" not-null="true" lazy="proxy"></many-to-one>
</class>
</hibernate-mapping>
Change to test method
[TestMethod]
public void CascadeSaveTest()
{
var product = new Product
{
Id = Guid.NewGuid(),
Name = "aaa",
IsActive = true
};
var productLine = new ProductLine
{
Id = Guid.Empty, //The updated line
Product = product,
};
product.ProductLines.Add(productLine);
using (var connection = new RepositoryConnection())
{
using (var repositories = new Repository<Product>(connection))
{
repositories.Create(product);
connection.Commit();
}
}
}

nhibernate saving a dictionary in a ternary relation

Class:
class Track
{
...
public virtual IDictionary<People, Role> PeopleRoles { get; set; }
}
Mapping:
<class name="Track" ...>
...
<map name="PeopleRoles" table="track_people_role">
<key column="track_id"/>
<map-key-many-to-many column="people_id" class="People"/>
<many-to-many column="role_id" class="Role"/>
</map>
</class>
When I execute the code below I can see the row inserted in track_people_role table as 47637,10,1
Person p = PersonManager.GetById(10);
Role pr = RoleManager.GetById(1);
Track t = TrackManager.GetById(47637);
t.PeopleRoles.Add(p, pr);
TrackManager.Save(t);
after when I execute the previous with a different role I get "An item with the same key has already been added" error
Person p = PersonManager.GetById(10);
Role pr = RoleManager.GetById(2);
Track t = TrackManager.GetById(47637);
t.PeopleRoles.Add(p, pr);
TrackManager.Save(t);
Any ideas how i can manage to insert the second row.
EDIT:
Mapping:
<class name="TrackPersonRole, App.Data" table="trackpeoplerole" lazy="true">
<composite-id>
<key-many-to-one name="Person" class="Person" column="people_id"/>
<key-many-to-one name="Role" class="Role" column="role_id"/>
</composite-id>
</class>
<class name="Track" ...>
...
<bag name="TrackPeopleRoles">
<key column="track_id"/>
<one-to-many class="TrackPersonRole"/>
</bag>
</class>
TrackPersonRole tpr = new TrackPersonRole();
tpr.Person = PersonManager.GetById(10);
tpr.Role = RoleManager.GetById(2);
entity.PeopleRoles.Add(tpr);
TrackManager.SaveOrUpdate(entity);
generates the below update statement, instead of an insert.
UPDATE trackpeoplerole SET track_id = 47637 /* ?p0 */
WHERE people_id = 10 /* ?p1 */ AND role_id = 2 /* ?p2 */
The error message is correct: you are adding a duplicate key to a dictionary.
It does not even come from NHibernate - it's generated by the Dictionary class.
If a Track can have the same Person in more than one Role, you'll have to map the track_person_role table as an entity:
class Track
{
...
public virtual ICollection<TrackPersonRole> PeopleRoles { get; set; }
}
class TrackPeopleRole
{
public virtual Person Person { get; set; }
public virtual Role Role { get; set; }
}
Mapping:
<class name="Track" ...>
...
<bag name="TrackPeopleRoles">
<key column="track_id"/>
<one-to-many class="TrackPersonRole"/>
</map>
</class>
There are more details regarding inverse and cascade, but start with this.

Fluent NHibernate - joined subclass ForeignKey Name

I am looking at moving to Fluent NHibernate - the only issue I have encounter so far is that you can not specify a foreign key name on a joined sub class mapping.
Has anyone got a solution for this, or a workaround?
I found this post but the suggestion clearly was not added to the code.
I would like to avoid customising the code myself if possible.
Any help would be great...
Example:
public class Product
{
public string Name { get; set; }
}
public class Hammer : Product
{
public string Description { get; set; }
}
public class ProductMap : ClassMap<Product, long>
{
public ProductMap()
{
Polymorphism.Implicit();
Map(x => x.Name);
}
}
public class HammerMap : SubclassMap<Hammer>
{
public HammerMap()
{
Extends<Product>();
}
}
This generates something like:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="field.camelcase-underscore" auto-import="false" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" dynamic-insert="true" dynamic-update="true" mutable="true" polymorphism="implicit" optimistic-lock="version" name="Domain.Product, Domain" table="Product">
<id name="Id" type="System.Int64">
<column name="Id" />
<generator class="native">
<param name="sequence">ProductId</param>
</generator>
</id>
<property name="Name" type="System.String">
<column name="Name" />
</property>
<joined-subclass name="Domain.Hammer, Domain" table="Hammer">
<key>
<column name="Product_Id" />
</key>
<property name="Description" type="System.String">
<column name="Description" />
</property>
</joined-subclass>
</class>
</hibernate-mapping>
Note that there is no foreign key name specified in the mapping hbm file - as in:
<joined-subclass name="Domain.Hammer, Domain" table="Hammer">
<key column="Product_Id" foreign-key="FK_Hammer_Product"/>
</joined-subclass>
Try something like this
public class JoinedSubclassForeignKeyConvention : IJoinedSubclassConvention
{
public void Apply(IJoinedSubclassInstance instance)
{
instance.Key.ForeignKey(string.Format("FK_{0}_{1}",
instance.EntityType.Name, instance.Type.Name));
}
}

Nhibernate get collection by ICriteria

colleagues. I've got a problem at getting my entity. Mapping:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Clients.Core"
namespace="Clients.Core.Domains">
<class name="Sales, Clients.Core" table='sales'>
<id name="Id" unsaved-value="0">
<column name="id" not-null="true"/>
<generator class="native"/>
</id>
<property name="Guid">
<column name="guid"/>
</property>
<set name="Accounts" table="sales_users" lazy="false">
<key column="sales_id" />
<element column="user_id" type="Int32" />
</set>
</class>
Domain:
public class Sales : BaseDomain
{
ICollection<int> accounts = new List<int>();
public virtual ICollection<int> Accounts
{
get { return accounts; }
set { accounts = value; }
}
public Sales() { }
}
I want get query such as
SELECT *
FROM sales s
INNER JOIN sales_users su on su.sales_id=s.id
WHERE su.user_id=:N
How can i do this through ICriterion object?
Thanks a lot.
This is what I think the answer should be:
public IEnumerable<Sales> GetSalesForUser(int userId)
{
return session.CreateCriteria<Sales>()
.CreateAlias("Accounts", "accounts")
.Add(Restrictions.Eq("accounts.UserId", "userId"))
.List<Sales>();
}
But I'm confused by your model. It appears that Accounts has a many-to-many relationship with Sales, but you haven't mapped it that way. I'm not sure how to filter an int collection (HashSet in this case). You could try:
public IEnumerable<Sales> GetSalesForUser(int userId)
{
return session.CreateCriteria<Sales>()
.Add(Restrictions.Eq("Accounts", userId))
.List<Sales>();
}
var sales = session.CreateCriteria(typeof(Sales))
.SetFetchMode("Accounts", FetchMode.Join)
.SetResultTransformer(Transformers.DistinctRootEntity)
.List<Sales>();

Doubly connected ordered tree mapping using NHibernate

We need to map simple class using NHibernate:
public class CatalogItem
{
private IList<CatalogItem> children = new List<CatalogItem>();
public Guid Id { get; set; }
public string Name { get; set; }
public CatalogItem Parent { get; set; }
public IList<CatalogItem> Children
{
get { return children; }
}
public bool IsRoot { get { return Parent == null; } }
public bool IsLeaf { get { return Children.Count == 0; } }
}
There are a batch of tutorials in the internet on this subject, but none of them cover little nasty detail: we need order to be preserved in Children collection. We've tried following mapping, but it led to strange exeptions thrown by NHibernate ("Non-static method requires a target.").
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Domain.Model" assembly="Domain">
<class name="CatalogItem" lazy="false">
<id name="Id" type="guid">
<generator class="guid" />
</id>
<property name="Name" />
<many-to-one name="Parent" class="CatalogItem" lazy="false" />
<list name="Children" cascade="all">
<key property-ref="Parent"/>
<index column="weight" type="Int32" />
<one-to-many not-found="exception" class="CatalogItem"/>
</list>
</class>
</hibernate-mapping>
Does anyone have any thoughts?
I'm no expert, but <key property-ref=...> looks strange to me in this usage. You should be able to do <key column="ParentID"/>, and NHibernate will automatically use the primary key of the associated class -- itself, in this case.
You may also need to set the list to inverse="true", since the relationship is bidirectional. [See section 6.8 in the docs.]