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

删除子项(Employee)时,为什么删除了父项(Store)

我用常规Cascade.All配置。

用户输入序列非常简单:

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

这可能是一个基本的映射错误,因为这是我对NHibernate的第一次采访。 我希望Store成为聚合根 ,并认为通过不在 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 { 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 { 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 { 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 Products { get; set; } public IList Staff { get; set; } public Store() { Products = new List(); Staff = new List(); } // 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”:

程序启动

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

 NHibernate:选择this_.Id为Id3_0_,this_.Name为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); 选择SCOPE_IDENTITY(); @ p0 = NULL [类型:字符串(4000)]

加载:stores = StoreRepository.GetAll()

 NHibernate:选择this_.Id为Id3_0_,this_.Name为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 [类型: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 [类型: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 [类型: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 [类型: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:选择this_.Id为Id3_0_,this_.Name为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 [类型: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 [类型:Int32(0)]

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

 NHibernate:SELECT employee0_.Id为Id0_1_,employee0_.FirstName为FirstName0_1_,employee0_.LastName为LastName0_1_,employee0_.Store_id为Store4_0_1_,store1_.Id为Id3_0_,store1_.Name为Name3_0_ FROM [Employee] employee0_ left outer join [Store] store1_ on employee0_.Store_id = store1_.Id WHERE employee0_.Id=@p0; @ p0 = 35 [类型: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 [类型: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 [类型: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:选择this_.Id为Id3_0_,this_.Name为Name3_0_ FROM [Store] this_

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


EDIT1:

SQL没有反向

程序启动

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

 NHibernate:选择this_.Id为Id3_0_,this_.Name为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); 选择SCOPE_IDENTITY(); @ p0 = NULL [类型:字符串(4000)]

加载:stores = StoreRepository.GetAll()

 NHibernate:选择this_.Id为Id3_0_,this_.Name为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 [类型: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 [类型: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 [类型: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 [类型: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:选择this_.Id为Id3_0_,this_.Name为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 [类型: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 [类型:Int32(0)]

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

 NHibernate:SELECT employee0_.Id为Id0_1_,employee0_.FirstName为FirstName0_1_,employee0_.LastName为LastName0_1_,employee0_.Store_id为Store4_0_1_,store1_.Id为Id3_0_,store1_.Name为Name3_0_ FROM [Employee] employee0_ left outer join [Store] store1_ on employee0_.Store_id = store1_.Id WHERE employee0_.Id=@p0; @ p0 = 1 [类型: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 [类型: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 [类型: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:选择this_.Id为Id3_0_,this_.Name为Name3_0_ FROM [Store] this_

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

程序窗口

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

在此处输入图像描述


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() .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)。 如果没有级联约定,我会对所有替代方案都有例外。 我是否被迫手动删除引用 ? 我认为不应该要求它。

 //对于所有替代方案,配置不指定级联约定。

 // HasMany(x => x.Staff);  // 1.添加商店,保存,加载,添加员工, 
                              // save:TransientObjectException; 雇员
     HasMany(x => x.Staff).Inverse();  // 2.作为1
 // HasMany(x => x.Staff).Cascade.All();  // 3.添加商店,保存,加载,添加员工,保存,加载, 
                                            //删除Employee:ObjectDeletedException
 // HasMany(x => x.Staff).Inverse()。Cascade.All();  // 4.作为3
 // HasMany(x => x.Staff).Inverse()。Cascade.AllDeleteOrphan();  // 5.作为3/4
 // HasMany(x => x.Staff).Cascade.None();  // 6.为1/2

 //例外1) 
 //在StoreRepositorySaveOrUpdate(商店)上:TransientObjectException: 
 //对象引用未保存的瞬态实例 - 在刷新之前保存瞬态实例。 
 //类型:FNHib_Test.Entities.Employee,实体:FNHib_Test.Entities.Employee

 //例外3) 
 //在EmployeeRepository.Delete(员工)上; 器transaction.commit()
 // ObjectDeletedException未处理: 
 //已删除的对象将通过级联重新保存 
 //(从关联中删除已删除的对象)[FNHib_Test.Entities.Employee#1]

EDIT5:

以上exception的结果:

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

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

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

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

5)与3相同的原因。

6)与1相同。

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

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


EDIT6:

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

这是映射; 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.Id); if (emp != null) { emp.Store = null; session.Delete(emp); transaction.Commit(); } } } } } 

我想知道在保存瞬态实例时是否存在与实体-Id未被设置相关的问题。 这就是我在每次保存后加载的原因。 但我不知道为什么他们没有定。 正如我在这里描述的那样: NHibernate:在保存瞬态实例时如何更新身份ID?

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

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

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

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

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

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

emp.Store.Staff.Remove(EMP);

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

顺便说一下,这是映射: 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.Id); if (emp != null) { emp.Store.Staff.Remove(emp); session.Delete(emp); transaction.Commit(); } } } } }