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)
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>(); }
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:

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) {
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?