Код первого TPT и каскад при удалении - PullRequest
10 голосов
/ 19 июля 2011

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

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);

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

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

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

Заранее спасибо!

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

Simon

1 Ответ

10 голосов
/ 13 августа 2012

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

// This works only if customer's 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 позаботиться о специализированных продуктах клиента при его удалении. Как я уже говорил ранее, это происходит из-за того, что объект Customer не был правильно извлечен, а EF не знает о специализированных клиентских продуктах, что означает, что при правильном извлечении объекта Customer 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
...