Entity Framework: как удалить связанную сущность при обновлении или удалении "универсального" родителя
У меня есть класс Product
, который содержит свойство Picture
. Другие классы также могут иметь изображение, например A Customer
может иметь Picture
.
Продукт.cs:
public class Product
{
public Picture Picture { get; set; }
}
Клиент.cs:
public class Customer
{
public Picture Picture { get; set; }
}
Картинка.cs:
public class Picture
{
public string Type { get; set; }
public byte[] Content { get; et; }
}
Изображениевсегда является необязательным , то есть продукты и клиенты могут иметь или не иметь изображение. Тем не менее, изображение однозначно связано именно с одной "родительской" сущностью (продуктом или клиентом). Без родителя нет никакой пользы для ребенка. Картина, чтобы существовать. Обратите внимание, что нет никакой связи между картинкой и родительским классом, потому что этот родитель может быть нескольких типов (продукт или клиент).
Другое требование состоит в том, что я хочу иметь полный контроль над тем, загружается ли изображение или нет, через нетерпеливую или ленивую загрузку. Например, при получении списка продуктов я не хочу, чтобы картинка была извлечена, но если запрашивается один продукт, картинка должна быть включена.Мой вопрос : как можно Я настраиваю Entity Framework так, чтобы:
- запись изображения удаляется при удалении родительской записи (продукта или клиента)
- запись изображения удаляется всякий раз, когда родительская запись обновляется и новая версия не содержит изображения
- запись изображения заменяется (delete + create of a record) всякий раз, когда родитель обновляется новой картинкой
- картинка необязательна
- всегда контролируйте, загружено ли изображение. или нет
Я использую fluent API для определения связи. В настоящее время нет никакого каскадного удаления вообще.
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap()
{
ToTable("PRODUCTS");
HasKey(x => x.Id);
HasOptional(x => x.Picture).WithOptionalDependent().Map(m => m.MapKey("PICTUREID"));
}
}
Я пробовал использовать WithRequired, но это приводит к ошибкам, потому что нет ссылки или внешнего ключа от Picture
до Product/Customer
.
4 ответа:
Вы можете сделать следующее:
public abstract class Picture { public int Id {get;set;} public string Type { get; set; } public byte[] Content { get; set; } } public class ProductImage:Picture { public int ProductId {get;set;} public virtual Product Product {get;set;} } public class CustomerImage:Picture { public int CustomerId {get;set;} public virtual Customer Customer{get;set;} }
Тогда вы можете настроить вот так: экс. для продукта:
HasOptional(x=>x.ProductImage) .withRequired(x=>x.Product) .HasForeignKey(x=>x.ProductId); //cascade on delete is default true
Таким образом, вы можете загрузить изображение, когда вам это нужно, и если вы удалите товар, изображение также будет удалено. Изображение является необязательным и может быть заменено. При обновлении необходимо указать новый образ или удалить старый образ.
Надеюсь, что эта альтернатива поможет вам выбрать именно то, что вы хотите.
Я думаю, что вы можете настроить
Picture
КакComplexType
, что сделает свойстваPicture
столбцов класса в родительской таблице.Это означает:
- всякий раз, когда родитель удаляется, соответствующий
Picture
удаляется.- всякий раз, когда родитель обновляется новыми данными
Picture
(обновленные данные изображения или нет данных), соответствующие столбцы в родительской таблице также обновляются.Здесь описано, как можно сконфигурировать сущность
Picture
КакComplexType
: http://weblogs.asp.net/manavi/entity-association-mapping-with-code-first-part-1-one-to-one-associations.Как минимум, это то, что вам нужно сделать:
public class MyDbContext:DbContext { public DbSet<Customer> Customers { get; set; } public DbSet<Product> Products { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.ComplexType<Picture>(); } }
Если вы запустите
migration
, то, возможно, он будет генерировать миграции, подобные следующим:public override void Up() { CreateTable( "dbo.Customers", c => new { Id = c.Int(nullable: false, identity: true), Picture_Type = c.String(), Picture_Content = c.Binary(), }) .PrimaryKey(t => t.Id); CreateTable( "dbo.Products", c => new { Id = c.Int(nullable: false, identity: true), Picture_Type = c.String(), Picture_Content = c.Binary(), }) .PrimaryKey(t => t.Id); }
Кроме того, вы также можете написать конфигурацию fluent для
ComplexType
следующим образом:public class PictureConfig : ComplexTypeConfiguration<Picture> { public PictureConfig() { Property(t => t.Type).HasColumnName("PictureType"); Property(t => t.Content).HasColumnName("PictureContent"); } }
И добавьте эту конфигурацию в
DbContext
:public class MyDbContext:DbContext { public DbSet<Customer> Customers { get; set; } public DbSet<Product> Products { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { //modelBuilder.ComplexType<Picture>(); modelBuilder.Configurations.Add(new PictureConfig()); } }
Если вы сейчас
add-migration
, это может выглядеть как следующее:public override void Up() { CreateTable( "dbo.Customers", c => new { Id = c.Int(nullable: false, identity: true), PictureType = c.String(), PictureContent = c.Binary(), }) .PrimaryKey(t => t.Id); CreateTable( "dbo.Products", c => new { Id = c.Int(nullable: false, identity: true), PictureType = c.String(), PictureContent = c.Binary(), }) .PrimaryKey(t => t.Id); }
Надеюсь, это поможет.
Обновление на основе комментария: Эта часть обновления на самом деле не дает вам именно то, что вы хотите, а скорее делает простое предложение.
Я думаю, что вы ищете отношение, подобное следующему:
Product---1-----1---Picture ---1------1---Customer |- Id |- Id |- Id |- PictureId? |- PictureId?
То есть вы можете сохранить
nullable
PictureId
на родительских классах, и изменить этоPictureId
, всякий раз, когда картина меняется.Недостаток:вам нужно управлять деятельностью по манипулированию данными (CRUD) сам по себе.
Преимущество: таким образом, Вы имеете полный контроль над тем, когда загружать изображение, так как загрузка родителя не загружает
Picture
автоматически. Кроме того, это позволяет вам отделитьPicture
от вашей базы данных и использовать какое-то хранилище blob-объектов (может быть в облаке или около того).
Я нашел это здесь, на StackOverflow, и, возможно, это поможет вам:
Я думаю, что вам нужно немного изменить свой код, но он должен работать...modelBuilder.Entity<Product>() .HasKey(c => c.Id) .HasRequired(c => c.User) .WithRequiredDependent(c => c.UserProfile) .WillCascadeOnDelete(true);
Оригинальная запись: EF Code First Fluent API-Cascade Delete
Возможно, это также поможет вам: каскадное удаление с помощью Fluent API
Добавьте вызов для удаления изображения, на котором вы удаляете продукт. Если вы делаете это в нескольких местах, пересчитайте, чтобы инкапсулировать эту операцию. Я нахожу конфигурации EF менее прозрачными, но простой вызов delete в методе DeleteProduct или методе DeleteCustomer легко читается и понимается.