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 2

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 легко читается и понимается.