Почему родитель (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, как это :
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?
hmm. Может быть, я не знаю шагов, необходимых после того, как я изменил сопоставления. Я делаю SchemaExport.Drop & .Create. Это вообще необходимо, и нужно ли мне что-то сделать, чтобы сделать новое отображение эффективным? ..Для SQL был WITH Inverse. Поэтому, если нужно, я отправлю сообщение без него. – bretddog
Экспортировать схему должно быть достаточно, чтобы изменения отражались там. –
@bretddog: Да, пожалуйста, напишите sql 'without' inverse –