Почему родитель (Магазин) удаляется, когда я удаляю ребенка (Сотрудник) ?
Я настраиваю с конвенцией 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](https://i.stack.imgur.com/Aq6Z1.png)
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: Как обновляется идентификатор идентификации при сохранении временного экземпляра?