Entity Framework 核心与级联删除共享 table

Entity Framework Core shared table with cascade delete

我尝试使用 EF Core(代码优先)创建以下数据库设计

  1. 实体 "Recipe" 可以有一个类型为 "Resource"
  2. 的列表
  3. 实体 "Shop" 可以有一个 "Resource"
  4. 实体 "InstructionStep" 可以有一个类型为 "Resource"
  5. 的列表

如果我从"Recipe"、"InstructionStep"(集合)或"Shop"(单个-属性)中删除一个资源,那么相应的"Resource" 实体也应删除。 (级联删除)

我已经尝试了几种使用和不使用映射表的方法,但是 none 我的方法是成功的。 另一个想法是在 "Resource" 实体中有一个 属性 "ItemRefId" 来保存 "RecipeId/ShopId/InstructionStepId" 但我没有让它工作...

示例类:

    public class Recipe
    {
        public int RecipeId { get; set; }

        public string Title { get; set; }

        public ICollection<RecipeResource> Resources { get; set; } = new List<RecipeResource>();
    }

    public class Shop
    {
        public int ShopId { get; set; }
        public string Title { get; set; }
        public Resource Logo { get; set; }
    }

    public class Resource
    {
        public int ResourceId { get; set; }
        public string Path { get; set; }
        public int ItemRefId { get; set; }
    }

    public class InstructionStep
    {
        public string InstructionStepId { get; set; }
        public string Title { get; set; }
        public ICollection<RecipeResource> Resources { get; set; } = new List<RecipeResource>();
    }

有什么建议吗?非常感谢。

那不是级联删除。级联删除是指当一个 Recipe 被删除时,所有相关的 Resources 也被删除。

在 EF Core 3 中,您可以为此使用 Owned Entity Types。生成的关系模型与您提议的不同,因为 Recipe_Resource 和 InstructionStep_Resource 将分开 tables,而 Shop.Logo 将存储在商店 table。但这是正确的关系模型。拥有一个 Resource table,其中一些行引用 Recipe 而一些行引用 InstructionStep 是一个坏主意。

这种情况有时称为 "Strong Relationship",其中相关实体的标识依赖于主要实体,并且应该通过将外键列作为主键列在关系模型中实现依赖实体。这样就无法删除 Recipe_Resource 而不删除它。

例如

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;

namespace EfCore3Test
{

    public class Recipe
    {
        public int RecipeId { get; set; }

        public string Title { get; set; }

        public ICollection<Resource> Resources { get; } = new List<Resource>();
    }

    public class Shop
    {
        public int ShopId { get; set; }
        public string Title { get; set; }
        public Resource Logo { get; set; }
    }

    public class Resource
    {
        public int ResourceId { get; set; }
        public string Path { get; set; }
        public int ItemRefId { get; set; }
    }

    public class InstructionStep
    {
        public string InstructionStepId { get; set; }
        public string Title { get; set; }
        public ICollection<Resource> Resources { get; } = new List<Resource>();
    }

    public class Db : DbContext
    {
        public DbSet<Recipe> Recipes { get; set; }
        public virtual DbSet<Shop> Shops { get; set; }
        public virtual DbSet<InstructionStep> InstructionSteps { get; set; }

        private static readonly ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
            {
                builder.AddFilter((category, level) =>
                   category == DbLoggerCategory.Database.Command.Name
                   && level == LogLevel.Information).AddConsole();
            });
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseLoggerFactory(loggerFactory)
                          .UseSqlServer("Server=.;database=EfCore3Test;Integrated Security=true", 
                                        o => o.UseRelationalNulls());

            base.OnConfiguring(optionsBuilder);
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<Shop>().OwnsOne(p => p.Logo);
            modelBuilder.Entity<InstructionStep>().OwnsMany(p => p.Resources);
            modelBuilder.Entity<Recipe>().OwnsMany(p => p.Resources);
        }
    }

    class Program
    {

        static void Main(string[] args)
        {
            using var db = new Db();

            db.Database.EnsureDeleted();
            db.Database.EnsureCreated();

            var r = new Recipe();
            r.Resources.Add(new Resource() { ItemRefId = 2, Path = "/" });

            db.Recipes.Add(r);
            db.SaveChanges();

            r.Resources.Remove(r.Resources.First());

            db.SaveChanges();

            var s = new Shop();
            s.Logo = new Resource { ItemRefId = 2, Path = "/" };
            db.Shops.Add(s);
            db.SaveChanges();

            s.Logo = null;
            db.SaveChanges();
        }
    }
}