NHibernate; Удаление потомка удаляет родителя? - PullRequest
4 голосов
/ 03 февраля 2011

Почему родитель (Магазин) удаляется, когда я удаляю ребенка (Сотрудник) ?

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

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

  • Начать с пустой базы данных
  • Добавить родителя
  • Сохранить, загрузить (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();
                      }
                    }
                }
            }
        } 

Отображения

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

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

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

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

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

Сохранить: 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)]

Загрузить: 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 (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)]

Загрузить: 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 (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)]

Загрузить: stores = StoreRepository.GetAll ()

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

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


EDIT1:

SQL без инверсии

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

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

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

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

Сохранить: 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)]

Загрузить: 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 (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)]

Загрузить: 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 (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)]

Загрузить: 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) Сотрудник - совокупный корень (обратный набор). Тем не менее, поскольку нет каскада: мне нужно обрабатывать добавленный сотрудник вручную, просто потому, что коллекция хранилищ содержит как постоянные, так и временные объекты. Таким образом, подсказка 1 и 2 просто каскад = нет. Обратное не имеет значения. (OK)

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

4) Та же причина, что и у 3. Инверсия не влияет на каскад. (Может быть хорошо)

5) Та же причина, что и у 3.

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

Если это вывод. Тогда это означает, что мы вынуждены удалить ссылку между двунаправленными объектами перед удалением дочернего элемента. Независимо от настройки 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>( employee.Id);
                  if (emp != null)
                  {
                    emp.Store = null;
                    session.Delete(emp);
                    transaction.Commit();
                  } 
                }
            }
        }
    } 

Интересно, может ли быть проблема, связанная с тем, что идентификатор сущности не устанавливается, когда я сохраняю временные экземпляры. Вот почему я загружаю после каждого сохранения. Но я не знаю, почему они не установлены. Как я описал здесь: NHibernate: Как обновляется идентификатор идентификации при сохранении временного экземпляра?

Ответы [ 3 ]

2 голосов
/ 03 февраля 2011

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

1 голос
/ 03 февраля 2011

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

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

                  if (emp != null)
                  {
                    emp.Store = null;
                    session.Delete(emp);
                    transaction.Commit();
                  }
0 голосов
/ 04 февраля 2011

Окей .. Наконец-то .. Я нашел хотя бы рабочее решение.Не уверен, что именно так это и должно решаться:

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

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();
                  } 
                }
            }
        }
    } 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...