2011-02-03 4 views
4

Почему родитель (Store) удален при удалении ребенка (работника)?NHibernate; Удаление дочернего объекта исключает родителя?

Я настраиваю с конвенция Cascade.All.

Последовательность входа пользователя довольно прост:

  • Начните с пустой базой данных
  • Добавить родитель
  • Save, Load (Загрузить = перезагружать весь граф объектов)
  • Добавить ребенок
  • Сохранить, Загрузить
  • Удалить Ребенок
  • Результат: Пустая база данных. (Родитель удален)

Это может быть основная ошибка отображения, так как это мой первый прием на NHibernate. Я хочу, чтобы Store являлся агрегирующим корнем, и считал, что по не установка Inverse на свойство Store.Staff, а затем таблица 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(); 
         } 
        } 
       } 
      } 
     } 

Mappings

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_ 

Добавить родитель: Добавить магазин в пустые магазины коллекции

Сохранить: StoreRepository.SaveOrUpdate (магазины)

 
NHibernate: SELECT store0_.Id as Id3_0_, store0_.Name as Name3_0_ FROM [Store] store0_ WHERE [email protected];@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 [email protected];@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 [email protected];@p0 = 16 [Type: Int32 (0)] 

Добавить ребенка: в пустую коллекцию детской, для выбранного магазина

Сохранить: StoreRepository.SaveOrUpdate (магазины)

 
NHibernate: SELECT store0_.Id as Id3_0_, store0_.Name as Name3_0_ FROM [Store] store0_ WHERE [email protected];@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 [email protected];@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 [email protected];@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)] 

нагрузки: магазины = 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 [email protected];@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 [email protected];@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 [email protected];@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 [email protected];@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 [email protected];@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)] 

нагрузки: магазины = StoreRepository.GetAll()

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

(нет результата, база данных не пуста)


EDIT1:

SQL БЕЗ Inverse

запуск программы

нагрузки: магазины магазины = 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 [email protected];@p0 = 0 [Type: Int32 (0)] 
NHibernate: INSERT INTO [Store] (Name) VALUES (@p0); select SCOPE_IDENTITY();@p0 = NULL [Type: String (4000)] 

Нагрузка: магазины = 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 [email protected];@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 [email protected];@p0 = 1 [Type: Int32 (0)] 

Добавить ребенка: в пустую коллекцию детской, для выбранного магазина

Сохранить: StoreRepository.SaveOrUpdate (магазины)

 
NHibernate: SELECT store0_.Id as Id3_0_, store0_.Name as Name3_0_ FROM [Store] store0_ WHERE [email protected];@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 [email protected];@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 [email protected];@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)] 

нагрузки: магазины = 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 [email protected];@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 [email protected];@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 [email protected];@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 [email protected];@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 [email protected];@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). Без каскадного соглашения я получаю исключение по всем альтернативам. Am I принудительно удалять ссылки вручную? Я думал, что это не обязательно.

 
// 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) Магазин агрегатный корень (Нет Inverse набор). Поскольку никакой каскад: мне нужно обрабатывать добавленные дети вручную при сохранении агрегата. (ОК)

2) Сотрудник представляет собой совокупный корень (обратный набор). Тем не менее, поскольку никакой каскад: мне нужно обрабатывать добавленный Employee вручную, просто потому, что коллекция магазинов содержит как постоянные, так и переходные объекты. Таким образом, ключ 1 и 2 просто таков, что каскад = нет. Обратное не имеет значения. (OK)

3) Магазин представляет собой совокупный корень (без инверсного набора). Cascade = all, и он работает в обоих направлениях не только из совокупного корня? Таким образом, мы не можем удалить дочерний элемент без предварительного удаления ссылки на родителя. (Возможно, ОК).

4) По той же причине, что и 3. Обратный не имеет никакого значения для каскада. (Может быть, OK)

5) По той же причине, как 3.

6) То же, что 1.

Если это заключение. Тогда это означает, что мы вынуждены удалить ссылку между двунаправленными объектами перед удалением дочернего элемента. Независимо от настройки Inverse.

So: Я не вижу, что инверсия имеет ЛЮБОЙ эффект на двунаправленную связь.?


EDIT6:

(дышать ..) Даже установка эми.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>(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?

ответ

0

Ok .. Наконец .. я нашел по крайней мере, рабочий раствор. Не уверен, что так оно и должно быть решено:

Требуется удалить экземпляр из родительского списка. Ребенок все еще может иметь ссылку на родителя, но не наоборот. Таким образом, как показано ниже:

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>(employee.Id); 

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

Не используйте инверсию для отображения в вашем случае. Без инверсии все должно быть хорошо.

+0

hmm. Может быть, я не знаю шагов, необходимых после того, как я изменил сопоставления. Я делаю SchemaExport.Drop & .Create. Это вообще необходимо, и нужно ли мне что-то сделать, чтобы сделать новое отображение эффективным? ..Для SQL был WITH Inverse. Поэтому, если нужно, я отправлю сообщение без него. – bretddog

+0

Экспортировать схему должно быть достаточно, чтобы изменения отражались там. –

+0

@bretddog: Да, пожалуйста, напишите sql 'without' inverse –

1

Что вы подразумеваете под "I configure with convention Cascade.All"? Используете ли вы соглашение Fluent NHibernate? В ваших сопоставлениях ничего не видно, что приведет к тому, что Employee delete будет каскадирован в прикрепленный магазин. Я также не вижу причин, по которым загрузка триггеров Employee загружает коллекцию Store и ее сотрудников и продуктов.

Это изменение в метод удаления, вероятно, исправить эту проблему, но она не решает основную причину:

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

Да, я поместил конфигурацию в EDIT2. Я думаю, это немного избыточно, так как я также указываю каскад на отображениях, но это не должно повредить, я бы подумал ..? .. Я загружаю сотрудника? Я думал, что просто загружаю полный агрегат из корня (Store)? Я просто использую StoreRepository.GetAll() – bretddog

+0

Я не знаком с этим соглашением, но мое предположение заключается в том, что он добавляет каскадирование для каждого объекта, что объясняет поведение, которое вы видите. Удалите его и посмотрите. –

+0

«объяснил бы»: Вы имеете в виду, потому что это двунаправленные сущности с cascade.all, тогда удаление будет каскадом в обоих направлениях отношений? Я пробовал теперь без этого соглашения (см. EDIT3). Исключение составляют все альтернативы. Я думал, что с этим можно справиться, не удаляя ссылки вручную. (?) – bretddog

Смежные вопросы