NHibernate的;删除子项删除父项?

时间:2011-02-03 18:26:39

标签: c# .net database nhibernate fluent-nhibernate

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

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

用户输入序列非常简单:

  • 从空数据库开始
  • 添加父级
  • 保存,加载(加载=重新加载完整对象图)
  • 添加孩子
  • 保存,加载
  • 删除儿童
  • 结果:清空数据库。 (家长删除)

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

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

员工删除方法:

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”:

程序启动

加载:存储stores = StoreRepository.GetAll()

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

添加父级:将商店添加到空集合商店

保存:StoreRepository.SaveOrUpdate(存储)

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

加载: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)]

为所选商店添加子项:空子项集合

保存:StoreRepository.SaveOrUpdate(存储)

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

加载: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)]

删除子项:(删除所选商店的员工)EmployeeRepository.Delete(员工)

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

加载:stores = StoreRepository.GetAll()

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

(没有结果,数据库为空)


EDIT1:

没有反向的SQL

程序启动

加载:存储stores = StoreRepository.GetAll()

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

添加父级:将商店添加到空集合商店

保存:StoreRepository.SaveOrUpdate(存储)

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

加载: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)]

为所选商店添加子项:空子项集合

保存:StoreRepository.SaveOrUpdate(存储)

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

加载: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)]

删除子项:(删除所选商店的员工)EmployeeRepository.Delete(员工)

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

加载:stores = StoreRepository.GetAll()

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

(仍然;没有结果,数据库是空的)

计划窗口

所选商店的商店收藏和子集合被绑定到BindingSource / DataGridView / BindingNavigator,如下所示:

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

我现在尝试了以下所有不同的映射(1-6)。如果没有级联约定,我会在所有替代方案上获得例外。我被迫手动删除参考?我认为不应该这样做。

// 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:

以上例外的结果:

1)存储是聚合根(无反向集)。由于没有级联:我需要在保存聚合时手动处理添加的子项。 (好的)

2)Employee是聚合根(反向集)。仍然,因为没有级联:我需要手动处理添加的Employee,因为stores集合包含持久性和瞬态实体。所以1和2的线索就是cascade = none。逆是无关紧要的。 (OK)

3)存储是聚合根(无反向集)。 Cascade = all,它在两个方向上都有效,不仅来自聚合根?因此,如果没有先删除它对父级的引用,我们就无法删除它。 (也许OK)。

4)同样的原因3.反向对级联没有影响。 (也许还可以)

5)与3相同的原因。

6)与1.相同。

如果这是结论。然后它意味着我们被迫在删除子项之前删除双向实体之间的引用。无论Inverse的设置如何。

所以:我看不出Inverse对双向关系有任何影响。


EDIT6:

(呼吸......)甚至设置 emp.Store = null; 它仍然提供 ObjectDeletedException : 已删除的对象将通过级联重新保存(从关联中删除已删除的对象)[FNHib_Test.Entities.Employee#1]

这是映射; HasMany(x =&gt; 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();
                  } 
                }
            }
        }
    } 

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

3 个答案:

答案 0 :(得分:2)

不要在映射中使用逆映射。 没有逆它应该没问题。

答案 1 :(得分:1)

“我用常规Cascade.All配置”是什么意思?您使用的是Fluent NHibernate惯例吗?我在您的映射中看不到任何会导致Employee删除级联到附加商店的内容。我也没有看到为什么加载Employee会触发加载Store及其Staff and Products集合的任何原因。

对Delete方法的这种更改可能会解决问题,但它没有解决根本原因:

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

答案 2 :(得分:0)

好的..最后..我发现至少有一个可行的解决方案。不确定这是否应该如何解决:

所需要的是从父列表中删除实例。孩子仍然可以参考父母,但不能以其他方式参考。所以,如下图所示:

<强> emp.Store.Staff.Remove(EMP);

看起来有点尴尬。这只是一个正常的父子关系,我们试图删除孩子。但也许其他人可以用适当的知识来阐述......

顺便说一下,这是映射: HasMany(x =&gt; 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();
                  } 
                }
            }
        }
    }