Код сначала TPT и каскад при удалении

Я использую EF4.1 с первым кодом и наследование TPT (Table per Type). У меня есть такая структура

public class Customer 
{
    public virtual ICollection<Product> Products {get; set;}
}

public class Product
{
   [Required]
   public int Id { get; set; }

   [Required]
   public virtual Customer {get; set;}

   public decimal Price { get; set; }
}

public class SpecializedProduct : Product
{
   public string SpecialAttribute { get; set; }
}

когда я удаляю клиента, я хочу, чтобы все продукты, связанные с этим клиентом, были удалены. Я могу указать WillCascadeOnDelete (true) между Клиентом и Продуктом:

modelBuilder.Entity<Customer>().HasMany(e => e.Products).WithRequired(p => p.Customer).WillCascadeOnDelete(true);

но поскольку существует ключевое отношение foreighn между SpecializedProduct и Product i, я получаю исключение при попытке удалить клиента:

Операция DELETE противоречила ограничению REFERENCE "SpecializedProduct _TypeConstraint_From_Product_To_SpecializedProduct". Конфликт произошел в базе данных "Тест", таблице "dbo.SpecializedProduct", в столбце "Id". Заявление завершено.

Если я вручную установил каскад on delete в свойстве SpecializedProduct _TypeConstraint_From_Product_To_SpecializedProduct, он работает, но я хотел бы указать это с помощью модификатора модели или каким-либо другим способом в коде. Возможно ли это?

Спасибо заранее!

С наилучшими пожеланиями

Саймон

Ответ 1

Когда дело доходит до базы данных, Наследование TPT реализуется с помощью Ассоциации общих первичных ключей между базовый класс (например, Product) и все производные классы (например, SpecializedProduct). Теперь, когда вы удаляете объект Customer без получения свойства Products, EF не знает, что у этого Клиента есть куча продуктов, которые также необходимо удалить согласно вашему требованию. Если вы включите каскадные удаления путем маркировки вашей ассоциации клиент-продукт по мере необходимости, тогда база данных позаботится об удалении дочерней записи из таблицы продуктов, но если эта дочерняя запись является специализированным продуктом, то соответствующая строка в SpecializedProduct не будет удалить и, следовательно, исключение, которое вы получаете. Поэтому в основном следующий код не будет работать:

// This works only if customer products are not SpecializedProduct
Customer customer = context.Customers.Single(c => c.CustomerId == 1);
context.Customers.Remove(customer);
context.SaveChanges();    

Этот код заставит EF отправить в базу данных следующий SQL:

exec sp_executesql N'delete [dbo].[Customer] where ([CustomerId] = @0)',N'@0 int',@0=1


Тем не менее, нет возможности включить каскадное удаление между таблицами Product и SpecializedProduct, а именно, как EF Code First реализует наследование TPT, и вы не можете его переопределить.

Итак, какое решение?

Один из способов - это то, что вы уже выяснили, вручную переключая каскады между таблицами Product и SpecializedProduct, чтобы избежать исключения при удалении клиента с помощью SpecializedProducts.

Второй способ - позволить EF заботиться о клиенте SpecializedProducts при удалении клиента. Как я уже говорил, это происходит из-за того, что объект Customer не был правильно выбран, и EF не знает о специализации SpecializedProducts клиента, что означает, что, правильно загружая объект клиента, Ef начнет отслеживать ассоциации клиентов и будет предоставлять необходимые SQL-инструкции, чтобы убедиться что каждая связанная запись удаляется перед удалением клиента:

Customer customer = context.Customers
                           .Include(c => c.Products)
                           .Single(c => c.CustomerId == 1);

context.Customers.Remove(customer);
context.SaveChanges();    

В результате EF отправит в базу данных следующие операторы SQL, которые отлично удаляют все по порядку:

exec sp_executesql N'delete [dbo].[SpecializedProduct] where ([Id] = @0)',N'@0 int',@0=1

exec sp_executesql N'delete [dbo].[Product] where (([Id] = @0) and ([Customer_CustomerId] = @1))',N'@0 int,@1 int',@0=1,@1=1

exec sp_executesql N'delete [dbo].[Customer] where ([CustomerId] = @0)',N'@0 int',@0=1