NHibernate; Removing child excludes parent?

Why is the parent (Store) removed when the child (Employee) is deleted ?

I customize using the Cascade.All convention .

The user input sequence is pretty simple:

  • Start with an empty database
  • Add parent
  • Save, load (Download = reload the full graph of objects)
  • Add baby
  • Save, load
  • Remove baby
  • Result: an empty database. (Parent removed)

This may be a major display error, since this is the first time I am using NHibernate. I want the Store to be an aggregated root , and believed that if the Inverse parameter is not set in the Store.Staff property, the Store table will be responsible for saving and, therefore, the aggregate root. It's a delusion? In fact, if I use Inverse or not, I still get the same result. So maybe this is not a problem, but I would like to understand it too.

And he intends not to use a wider scope of the session, since I want to learn how to work with separate and temporary objects.

Employee Removal Method:

class EmployeeRepository public static void Delete(Employee employee) { using (ISession session = FNH_Manager.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { if (employee.Id != 0) { var emp = session.Get(typeof(Employee), employee.Id); if (emp != null) { session.Delete(emp); transaction.Commit(); } } } } } 

<strong> mapping

 public class StoreMap : ClassMap<Store> { public StoreMap() { Id(x => x.Id); Map(x => x.Name); HasMany(x => x.Staff) // 1:m .Inverse() // tried both with and without, what is correct? .Cascade.All(); HasManyToMany(x => x.Products) // m:m .Cascade.All() .Table("StoreProduct"); } } public class EmployeeMap : ClassMap<Employee> { public EmployeeMap() { Id(x => x.Id); // By default an int Id is generated as identity Map(x => x.FirstName); Map(x => x.LastName); References(x => x.Store); // m:1 } } public class ProductMap : ClassMap<Product> { public ProductMap() { Id(x => x.Id).GeneratedBy.Identity(); Map(x => x.Name).Length(20); Map(x => x.Price).CustomSqlType("decimal").Precision(9).Scale(2); HasManyToMany(x => x.StoresStockedIn) .Cascade.All() .Inverse() .Table("StoreProduct"); } } 

Objects:

  public class Store { public int Id { get; private set; } public string Name { get; set; } public IList<Product> Products { get; set; } public IList<Employee> Staff { get; set; } public Store() { Products = new List<Product>(); Staff = new List<Employee>(); } // AddProduct & AddEmployee is required. "NH needs you to set both sides before // it will save correctly" ?? public void AddProduct(Product product) { product.StoresStockedIn.Add(this); Products.Add(product); } public void AddEmployee(Employee employee) { employee.Store = this; Staff.Add(employee); } } public class Employee { public int Id { get; private set; } public string FirstName { get; set; } public string LastName { get; set; } public Store Store { get; set; } } 

Software pseudo-code and the resulting "SQL":

Program launch

Download: Store Stores = StoreRepository.GetAll ()

  NHibernate: SELECT this_.Id as Id3_0_, this_.Name as Name3_0_ FROM [Store] this_

Add parent: add store to empty collection stores

Save: StoreRepository.SaveOrUpdate (saves)

  NHibernate: SELECT store0_.Id as Id3_0_, store0_.Name as Name3_0_ FROM [Store] store0_ WHERE store0_.Id=@p0 ; @ p0 = 0 [Type: Int32 (0)]
 NHibernate: INSERT INTO [Store] (Name) VALUES (@ p0);  select SCOPE_IDENTITY (); @ p0 = NULL [Type: String (4000)]

Download: stores = StoreRepository.GetAll ()

  NHibernate: SELECT this_.Id as Id3_0_, this_.Name as Name3_0_ FROM [Store] this_
 NHibernate: SELECT products0_.Store_id as Store2_1_, products0_.Product_id as Product1_1_, product1_.Id as Id1_0_, product1_.Name as Name1_0_, product1_.Price as Price1_0_ FROM StoreProduct products0_ left outer join [Product] product1__ products__ left outer join WHERE products0_.Store_id=@p0 ; @ p0 = 16 [Type: Int32 (0)]
 NHibernate: SELECT staff0_.Store_id as Store4_1_, staff0_.Id as Id1_, staff0_.Id as Id0_0_, staff0_.FirstName as FirstName0_0_, staff0_.LastName as LastName0_0_, staff0_.Store_id as Store4_0_0_0_0_0_0_0_0_0_0_0_er_firm p0 = 16 [Type: Int32 (0)]

Add child: to remove the child collection for the selected store

Save: StoreRepository.SaveOrUpdate (saves)

  NHibernate: SELECT store0_.Id as Id3_0_, store0_.Name as Name3_0_ FROM [Store] store0_ WHERE store0_.Id=@p0 ; @ p0 = 16 [Type: Int32 (0)]
 NHibernate: SELECT products0_.Store_id as Store2_1_, products0_.Product_id as Product1_1_, product1_.Id as Id1_0_, product1_.Name as Name1_0_, product1_.Price as Price1_0_ FROM StoreProduct products0_ left outer join [Product] product1__ products__ left outer join WHERE products0_.Store_id=@p0 ; @ p0 = 16 [Type: Int32 (0)]
 NHibernate: SELECT staff0_.Store_id as Store4_1_, staff0_.Id as Id1_, staff0_.Id as Id0_0_, staff0_.FirstName as FirstName0_0_, staff0_.LastName as LastName0_0_, staff0_.Store_id as Store4_0_0_0_0_0_0_0_0_0_0_0_er_firm p0 = 16 [Type: Int32 (0)]
 NHibernate: INSERT INTO [Employee] (FirstName, LastName, Store_id) VALUES (@ p0, @ p1, @ p2);  select SCOPE_IDENTITY (); @ p0 = NULL [Type: String (4000)], @ p1 = NULL [Type: String (4000)], @ p2 = 16 [Type: Int32 (0)]

Download: stores = StoreRepository.GetAll ()

  NHibernate: SELECT this_.Id as Id3_0_, this_.Name as Name3_0_ FROM [Store] this_
 NHibernate: SELECT products0_.Store_id as Store2_1_, products0_.Product_id as Product1_1_, product1_.Id as Id1_0_, product1_.Name as Name1_0_, product1_.Price as Price1_0_ FROM StoreProduct products0_ left outer join [Product] product1__ products__ left outer join WHERE products0_.Store_id=@p0 ; @ p0 = 16 [Type: Int32 (0)]
 NHibernate: SELECT staff0_.Store_id as Store4_1_, staff0_.Id as Id1_, staff0_.Id as Id0_0_, staff0_.FirstName as FirstName0_0_, staff0_.LastName as LastName0_0_, staff0_.Store_id as Store4_0_0_0_0_0_0_0_0_0_0_0_0_0_er_firm p0 = 16 [Type: Int32 (0)]

Delete child: (Delete employee for selected storage) EmployeeRepository.Delete (employee)

  NHibernate: SELECT employee0_.Id as Id0_1_, employee0_.FirstName as FirstName0_1_, employee0_.LastName as LastName0_1_, employee0_.Store_id as Store4_0_1_, store1_.Id as Id3_0_, store1_.Name as Store_1_Obload_name_name_name_name_name on employee0_.Store_id = store1_.Id WHERE employee0_.Id=@p0 ; @ p0 = 35 [Type: Int32 (0)]
 NHibernate: SELECT products0_.Store_id as Store2_1_, products0_.Product_id as Product1_1_, product1_.Id as Id1_0_, product1_.Name as Name1_0_, product1_.Price as Price1_0_ FROM StoreProduct products0_ left outer join [Product] product1__ products__ left outer join WHERE products0_.Store_id=@p0 ; @ p0 = 16 [Type: Int32 (0)]
 NHibernate: SELECT staff0_.Store_id as Store4_1_, staff0_.Id as Id1_, staff0_.Id as Id0_0_, staff0_.FirstName as FirstName0_0_, staff0_.LastName as LastName0_0_, staff0_.Store_id as Store4_0_0_0_0_0_0_0_0_0_0_0_0_0_er_firm p0 = 16 [Type: Int32 (0)]
 NHibernate: DELETE FROM [Employee] WHERE Id = @ p0; @ p0 = 35 [Type: Int32 (0)]
 NHibernate: DELETE FROM [Store] WHERE Id = @ p0; @ p0 = 16 [Type: Int32 (0)]

Download: stores = StoreRepository.GetAll ()

  NHibernate: SELECT this_.Id as Id3_0_, this_.Name as Name3_0_ FROM [Store] this_

(no result, the database is empty)


EDIT1:

SQL WITHOUT reverse

Program launch

Download: Store Stores = StoreRepository.GetAll ()

  NHibernate: SELECT this_.Id as Id3_0_, this_.Name as Name3_0_ FROM [Store] this_

Add parent: add store to empty collection stores

Save: StoreRepository.SaveOrUpdate (saves)

  NHibernate: SELECT store0_.Id as Id3_0_, store0_.Name as Name3_0_ FROM [Store] store0_ WHERE store0_.Id=@p0 ; @ p0 = 0 [Type: Int32 (0)]
 NHibernate: INSERT INTO [Store] (Name) VALUES (@ p0);  select SCOPE_IDENTITY (); @ p0 = NULL [Type: String (4000)]

Download: stores = StoreRepository.GetAll ()

  NHibernate: SELECT this_.Id as Id3_0_, this_.Name as Name3_0_ FROM [Store] this_
 NHibernate: SELECT products0_.Store_id as Store2_1_, products0_.Product_id as Product1_1_, product1_.Id as Id1_0_, product1_.Name as Name1_0_, product1_.Price as Price1_0_ FROM StoreProduct products0_ left outer join [Product] product1__ products__ left outer join WHERE products0_.Store_id=@p0 ; @ p0 = 1 [Type: Int32 (0)]
 NHibernate: SELECT staff0_.Store_id as Store4_1_, staff0_.Id as Id1_, staff0_.Id as Id0_0_, staff0_.FirstName as FirstName0_0_, staff0_.LastName as LastName0_0_, staff0_.Store_id as Store4_0_0_0_0_0_0_0_0_0_0_0_0_0_er_firm p0 = 1 [Type: Int32 (0)]

Add child: to remove the child collection for the selected store

Save: StoreRepository.SaveOrUpdate (saves)

  NHibernate: SELECT store0_.Id as Id3_0_, store0_.Name as Name3_0_ FROM [Store] store0_ WHERE store0_.Id=@p0 ; @ p0 = 1 [Type: Int32 (0)]
 NHibernate: SELECT products0_.Store_id as Store2_1_, products0_.Product_id as Product1_1_, product1_.Id as Id1_0_, product1_.Name as Name1_0_, product1_.Price as Price1_0_ FROM StoreProduct products0_ left outer join [Product] product1__ products__ left outer join WHERE products0_.Store_id=@p0 ; @ p0 = 1 [Type: Int32 (0)]
 NHibernate: SELECT staff0_.Store_id as Store4_1_, staff0_.Id as Id1_, staff0_.Id as Id0_0_, staff0_.FirstName as FirstName0_0_, staff0_.LastName as LastName0_0_, staff0_.Store_id as Store4_0_0_0_0_0_0_0_0_0_0_0_0_0_er_firm p0 = 1 [Type: Int32 (0)]
 NHibernate: INSERT INTO [Employee] (FirstName, LastName, Store_id) VALUES (@ p0, @ p1, @ p2);  select SCOPE_IDENTITY (); @ p0 = NULL [Type: String (4000)], @ p1 = NULL [Type: String (4000)], @ p2 = 1 [Type: Int32 (0)]
 NHibernate: UPDATE [Employee] SET Store_id = @ p0 WHERE Id = @ p1; @ p0 = 1 [Type: Int32 (0)], @ p1 = 1 [Type: Int32 (0)]

Download: stores = StoreRepository.GetAll ()

  NHibernate: SELECT this_.Id as Id3_0_, this_.Name as Name3_0_ FROM [Store] this_
 NHibernate: SELECT products0_.Store_id as Store2_1_, products0_.Product_id as Product1_1_, product1_.Id as Id1_0_, product1_.Name as Name1_0_, product1_.Price as Price1_0_ FROM StoreProduct products0_ left outer join [Product] product1__ products__ left outer join WHERE products0_.Store_id=@p0 ; @ p0 = 1 [Type: Int32 (0)]
 NHibernate: SELECT staff0_.Store_id as Store4_1_, staff0_.Id as Id1_, staff0_.Id as Id0_0_, staff0_.FirstName as FirstName0_0_, staff0_.LastName as LastName0_0_, staff0_.Store_id as Store4_0_0_0_0_0_0_0_0_0_0_0_0_0_er_firm p0 = 1 [Type: Int32 (0)]

Delete child: (Delete employee for selected storage) EmployeeRepository.Delete (employee)

  NHibernate: SELECT employee0_.Id as Id0_1_, employee0_.FirstName as FirstName0_1_, employee0_.LastName as LastName0_1_, employee0_.Store_id as Store4_0_1_, store1_.Id as Id3_0_, store1_.Name as Store_1_Obload_name_name_name_name_name on employee0_.Store_id = store1_.Id WHERE employee0_.Id=@p0 ; @ p0 = 1 [Type: Int32 (0)]
 NHibernate: SELECT products0_.Store_id as Store2_1_, products0_.Product_id as Product1_1_, product1_.Id as Id1_0_, product1_.Name as Name1_0_, product1_.Price as Price1_0_ FROM StoreProduct products0_ left outer join [Product] product1__ products__ left outer join WHERE products0_.Store_id=@p0 ; @ p0 = 1 [Type: Int32 (0)]
 NHibernate: SELECT staff0_.Store_id as Store4_1_, staff0_.Id as Id1_, staff0_.Id as Id0_0_, staff0_.FirstName as FirstName0_0_, staff0_.LastName as LastName0_0_, staff0_.Store_id as Store4_0_0_0_0_0_0_0_0_0_0_0_0_0_er_firm p0 = 1 [Type: Int32 (0)]
 NHibernate: UPDATE [Employee] SET Store_id = null WHERE Store_id = @ p0; @ p0 = 1 [Type: Int32 (0)]
 NHibernate: DELETE FROM [Employee] WHERE Id = @ p0; @ p0 = 1 [Type: Int32 (0)]
 NHibernate: DELETE FROM [Store] WHERE Id = @ p0; @ p0 = 1 [Type: Int32 (0)]

Download: stores = StoreRepository.GetAll ()

  NHibernate: SELECT this_.Id as Id3_0_, this_.Name as Name3_0_ FROM [Store] this_

(However, there is no result, the database is empty)

Program window

The collection persistence and child collection of the selected store are bound to the BindingSource / DataGridView / BindingNavigator as follows:

enter image description here


EDIT2

  private static ISessionFactory CreateSessionFactory() { if (sessionFactory == null) { return Fluently.Configure() .Database(MsSqlConfiguration.MsSql2008 .ConnectionString(Properties.Settings.Default.FnhDbString) .Cache(c => c .UseQueryCache()).ShowSql()) .Mappings(m => m.FluentMappings.AddFromAssemblyOf<EmployeeMap>() .Conventions.Add(FluentNHibernate.Conventions.Helpers.DefaultLazy.Never()) .Conventions.Add(FluentNHibernate.Conventions.Helpers.DefaultCascade.All()) .ExportTo("D:/VB/")) .ExposeConfiguration(c => cfg = c) .BuildSessionFactory(); } return sessionFactory; } 

EDIT3

Now I have tried all the different mappings below (1-6). Without a cascading agreement, I get an exception for all alternatives. Am I forced to manually delete links ? I thought it was not necessary.

  // For all alternatives, configuration does not specify cascade-convention.

 // HasMany (x => x.Staff);  // 1. add store, save, load, add employee, 
                              // save: TransientObjectException;  Employee
     HasMany (x => x.Staff) .Inverse ();  // 2. As 1
 // HasMany (x => x.Staff) .Cascade.All ();  // 3. Add store, Save, Load, Add Employee, Save, Load, 
                                            // Delete Employee: ObjectDeletedException
 // HasMany (x => x.Staff) .Inverse (). Cascade.All ();  // 4. As 3
 // HasMany (x => x.Staff) .Inverse (). Cascade.AllDeleteOrphan ();  // 5. As 3/4
 // HasMany (x => x.Staff) .Cascade.None ();  // 6. As 1/2

 // Exception of 1) 
 // On StoreRepositorySaveOrUpdate (stores): TransientObjectException: 
 // object references an unsaved transient instance - save the transient instance before flushing. 
 // Type: FNHib_Test.Entities.Employee, Entity: FNHib_Test.Entities.Employee

 // Exception of 3) 
 // On EmployeeRepository.Delete (employee);  transaction.Commit ()
 // ObjectDeletedException was unhandled: 
 // deleted object would be re-saved by cascade 
 // (remove deleted object from associations) [FNHib_Test.Entities.Employee # 1]

EDIT5:

The findings of the above exceptions:

1) The store is the cumulative root (without inverse set). Since there is no cascade: I need to process the added children manually while saving the unit. (OK)

2) An employee is an aggregate root (reverse set). However, since there is no cascade: I need to process the added Employee manually, simply because the store collection contains both permanent and transitional objects. Thus, key 1 and 2 are simply such that cascade = no. The converse does not matter. (OK)

3) The store is the cumulative root (without inverse set). Cascade = all, and does it work in both directions not only from the aggregate root? Therefore, we cannot delete the child without first deleting its reference to the parent. (Maybe OK).

4) For the same reason as 3. The reverse does not affect the cascade. (Maybe OK)

5) For the same reason as 3.

6) Same as 1.

If this is a conclusion. Then this means that we are forced to remove the link between bidirectional objects before deleting the child. Regardless of the Inverse setting.

So: I do not see that Inverse has ANY effect for a bi-directional relationship. ?


EDIT6:

(breathe ..) Even setting emp.Store = null; . It still gives an ObjectDeletedException : the deleted object will be re-saved by cascade (remove the deleted object from the associations) [FNHib_Test.Entities.Employee # 1]

It was with a display; HasMany (x => x.Staff) .Cascade.All ();

  public static void Delete(Employee employee) { using (ISession session = FNH_Manager.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { employee.Store = null; if (employee.Id != 0) { // var emp = session.Get(typeof(Employee), employee.Id); Employee emp = session.Get<Employee>( employee.Id); if (emp != null) { emp.Store = null; session.Delete(emp); transaction.Commit(); } } } } } 

I wonder if there could be a problem with the entity identifier, which is not set when I save temporary instances. This is why I load after every save. But I do not know why they are not installed. As I described here: NHibernate: How is the identifier updated when a temporary instance is saved?

+4
source share
3 answers

Good .. Finally .. I found at least a working solution. Iโ€™m not sure that this is how it should be solved:

It is required to remove an instance from the parent list. A child can still have a link to a parent, but not vice versa. So, as shown below:

emp.Store.Staff.Remove (EMP);

Seems a bit uncomfortable. This is just a normal parent-child relationship in which we are trying to remove a child. But maybe someone else can work out with the proper knowledge ...

Btw, this is with a mapping: HasMany (x => x.Staff) .Cascade.All ();

  public static void Delete(Employee employee) { using (ISession session = FNH_Manager.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { if (employee.Id != 0) { Employee emp = session.Get<Employee>(employee.Id); if (emp != null) { emp.Store.Staff.Remove(emp); session.Delete(emp); transaction.Commit(); } } } } } 
0
source

Do not use inverse to display in your case. Without inversion, this should be good.

+2
source

What do you mean by โ€œI customize using the Cascade.All conventionโ€? Do you use the Fluent NHibernate Agreement? I see nothing in your mappings that would cause Employee delete to be cascaded in the connected Storage. I also see no reason why loading trigger Employee loads the collection of the Store and its employees and products.

This change to the Delete method is likely to fix the problem, but does not fix the root cause:

  if (emp != null) { emp.Store = null; session.Delete(emp); transaction.Commit(); } 
+1
source

Source: https://habr.com/ru/post/1338300/


All Articles