为什么删除子(员工) 时删除了 父(商店)?

我使用 约定 Cascade.All 进行配置。

用户输入序列非常简单:

  • 从空数据库开始
  • 添加父项
  • 保存、加载(加载 = 重新加载完整的对象图)
  • 添加一个 child
  • 保存、加载
  • 删除子项
  • 结果:空数据库。 (父删除)

  • 这可能是一个基本的映射错误,因为这是我第一次使用 NHibernate。我希望 Store 成为聚合根 ,并认为通过 而不是 在 Store.Staff 属性上设置 Inverse,那么 Store 表将负责保存,因此是聚合根。这是一种误解吗?实际上,无论我是否使用 Inverse,我仍然会得到相同的结果。所以也许
    这不是问题,但我也想了解这一点。

    并且故意不使用更广泛的 session 范围,因为我想学习如何使用分离的和 transient 的实体。

    员工删除方法:
    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();
                          }
                        }
                    }
                }
            }
    

    映射
    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");
         }
    
    }
    

    实体:
       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; }
        }
    

    程序伪代码和结果“SQL”:

    程序启动

    加载:商店商店 = 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(stores)

    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)]
    

    Load: 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_ on products0_.Product_id=product1_.Id 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_ FROM [Employee] staff0_ WHERE staff0_.Store_id=@p0;@p0 = 16 [Type: Int32 (0)]
    

    Add child: to empty child collection, for selected store

    Save: StoreRepository.SaveOrUpdate(stores)

    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_ on products0_.Product_id=product1_.Id 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_ FROM [Employee] staff0_ WHERE staff0_.Store_id=@p0;@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)]
    

    Load: 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_ on products0_.Product_id=product1_.Id 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_ FROM [Employee] staff0_ WHERE staff0_.Store_id=@p0;@p0 = 16 [Type: Int32 (0)]
    

    Delete child: (Delete Employee for selected store) 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 Name3_0_ FROM [Employee] employee0_ left outer join [Store] store1_ 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_ on products0_.Product_id=product1_.Id 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_ FROM [Employee] staff0_ WHERE staff0_.Store_id=@p0;@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)]
    

    Load: stores = StoreRepository.GetAll()

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

    (no result, database is empty)


    EDIT1:

    SQL WITHOUT Inverse

    Program startup

    Load: Stores 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(stores)

    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)]
    

    Load: 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_ on products0_.Product_id=product1_.Id 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_ FROM [Employee] staff0_ WHERE staff0_.Store_id=@p0;@p0 = 1 [Type: Int32 (0)]
    

    Add child: to empty child collection, for selected store

    Save: StoreRepository.SaveOrUpdate(stores)

    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_ on products0_.Product_id=product1_.Id 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_ FROM [Employee] staff0_ WHERE staff0_.Store_id=@p0;@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)]
    

    Load: 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_ on products0_.Product_id=product1_.Id 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_ FROM [Employee] staff0_ WHERE staff0_.Store_id=@p0;@p0 = 1 [Type: Int32 (0)]
    

    Delete child: (Delete Employee for selected store) 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 Name3_0_ FROM [Employee] employee0_ left outer join [Store] store1_ 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_ on products0_.Product_id=product1_.Id 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_ FROM [Employee] staff0_ WHERE staff0_.Store_id=@p0;@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)]
    

    Load: stores = StoreRepository.GetAll()

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

    (Still; no result, database is empty)

    Program window

    Store collection and child collection of selected store is bound to BindingSource/DataGridView/BindingNavigator like this:

    c# - NHibernate;删除子项会删除父项?-LMLPHP


    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

    我现在已经尝试了以下所有不同的映射(1-6)。如果没有级联约定,我对所有替代方案都有异常(exception)。我 是否被迫手动删除引用 ?我认为它不应该是必需的。
    // 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:

    Findings to above exceptions:

    1) Store is aggregate root ( No Inverse set). Since no cascade: I need to handle added children manually on saving aggregate. (OK)

    2) Employee is aggregate root (Inverse set). Still, since no cascade: I need to handle added Employee manually, simply because the stores collection contain both persistent and transient entities. So the clue of 1 and 2 is simply that cascade = none. Inverse is irrelevant. (OK)

    3) Store is aggregate root (No inverse set). Cascade=all, and it works in both directions, not only from the aggregate root? So we can not delete the child without first removing it's reference to the parent. (Maybe OK).

    4) Same reason as 3. Inverse makes no difference on the cascade. (Maybe OK)

    5) Same reason as 3.

    6) Same as 1.

    If this is the conclusion. Then it means we are forced to remove the reference between bidirectional entities before delete of child. No matter the setting of Inverse.

    So: I can't see that Inverse has ANY effect on a bidirectional relationship. ?


    EDIT6:

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

    This was with mapping; 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();
                      }
                    }
                }
            }
        }
    

    我想知道是否会出现与保存 transient 实例时未设置实体 ID 相关的问题。这就是我在每次保存后加载的原因。但我不知道为什么没有设置它们。正如我在这里描述的:NHibernate: How is identity Id updated when saving a transient instance?

    最佳答案

    不要在映射中使用 inverse 为您的情况。没有逆应该没问题。

    关于c# - NHibernate;删除子项会删除父项?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/4890123/

    10-13 03:29