C# 实体框架核心:挑战建模产品变体数据库设计的多对多

C# 实体框架核心:挑战建模产品变体数据库设计的多对多,c#,entity-framework,asp.net-core,asp.net-core-mvc,entity-framework-core,C#,Entity Framework,Asp.net Core,Asp.net Core Mvc,Entity Framework Core,我正在尝试用实体框架核心对产品变体数据库设计进行建模 设计面临的问题/障碍: 运行dotnet ef migrations add InitialCreate命令时,我遇到以下错误: 在表“ProductSKUValues”上引入外键约束“FK\U ProductSKUValues\U ProductSkuID\U SkuId”可能会导致循环或多个级联路径。指定“在删除时不执行操作”或“在更新时不执行操作”,或修改其他外键约束。 无法创建约束或索引 Db设计: using Microsoft.A

我正在尝试用实体框架核心对产品变体数据库设计进行建模

设计面临的问题/障碍:

  • 运行
    dotnet ef migrations add InitialCreate
    命令时,我遇到以下错误:
  • 在表“ProductSKUValues”上引入外键约束“FK\U ProductSKUValues\U ProductSkuID\U SkuId”可能会导致循环或多个级联路径。指定“在删除时不执行操作”或“在更新时不执行操作”,或修改其他外键约束。 无法创建约束或索引

    Db设计:

    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    using TikkyBoxWebAPI.Models.Account;
    using TikkyBoxWebAPI.Models;
    using TikkyBoxWebAPI.Models.Core;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata;
    using System.Linq;
    
    namespace TikkyBoxWebAPI.Data
    {
        public class TikkyBoxDbContext : DbContext
        {
    
            public TikkyBoxDbContext(DbContextOptions<TikkyBoxDbContext> options)
                : base(options)
            {
                Database.Migrate();
            }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
    
                modelBuilder
                    .Entity<ProductSKU>()
                    .HasKey(p => new { p.ProductId, p.SkuId });
    
                modelBuilder
                .Entity<ProductSKU>()
                .HasOne(p => p.Product)
                .WithMany(ps => ps.ProductSKUs)
                .HasForeignKey(x => x.ProductId);
    
                modelBuilder
                    .Entity<ProductSKU>()
                    .HasIndex(p => p.Sku);
    
                modelBuilder
                    .Entity<ProductSKU>()
                    .Property(p => p.SkuId).ValueGeneratedOnAdd();
    
                modelBuilder
                .Entity<ProductSKUValue>()
                .HasOne<ProductSKU>()
                .WithMany( p => p.ProductSKUValues)
                .IsRequired(false)
                .OnDelete(DeleteBehavior.Restrict);
    
                modelBuilder
                    .Entity<ProductSKUValue>()
                    .HasKey(p => new { p.ProductId, p.SkuId, p.OptionId});
    
                modelBuilder
                .Entity<ProductSKUValue>()
                .HasOne(p => p.ProductOptionValue)
                .WithMany(ps => ps.ProductSKUValues)
                .HasForeignKey(x => new { x.ProductId, x.OptionId, x.ValueId })
                .OnDelete(DeleteBehavior.Restrict);
    
                modelBuilder
                .Entity<ProductSKUValue>()
                .HasOne(p => p.ProductOption)
                .WithMany(ps => ps.ProductSKUValues)
                .HasForeignKey(x => new { x.ProductId, x.OptionId })
                .OnDelete(DeleteBehavior.Restrict);
    
                modelBuilder
            .Entity<ProductOptionValue>()
            .HasKey(p => new { p.ProductId, p.OptionId, p.ValueId });
    
                modelBuilder
            .Entity<ProductOptionValue>()
            .HasOne(p => p.ProductOption)
            .WithMany(ps => ps.ProductOptionValues)
            .HasForeignKey(x => new { x.ProductId, x.OptionId });
                //    .OnDelete(DeleteBehavior.Restrict);
    
                modelBuilder
                    .Entity<ProductOptionValue>()
                    .Property(p => p.ValueId).ValueGeneratedOnAdd();
    
    
                modelBuilder
            .Entity<ProductOption>()
            .HasKey(p => new { p.ProductId, p.OptionId });
    
                modelBuilder
            .Entity<ProductOption>()
            .HasOne(p => p.Product)
            .WithMany(po => po.ProductOptions)
            .HasForeignKey(x => new { x.ProductId })
            .OnDelete(DeleteBehavior.Restrict);
    
    
                modelBuilder
                .Entity<ProductOption>()
                .Property(p => p.OptionId).ValueGeneratedOnAdd();
    
                // base.OnModelCreating(modelBuilder);
    
            }
            public DbSet<Product> Products { get; set; }
            public DbSet<ProductOption> ProductOptions { get; set; }
            public DbSet<ProductOptionValue> ProductOptionValues { get; set; }
            public DbSet<ProductSKU> ProductSKUs { get; set; }
            public DbSet<ProductSKUValue> ProductSKUValues { get; set; }
        }
    }
    

    注意:此设计基于以下链接建模:

    ApplicationDbContext.cs和流畅的API(注意产品SKU和产品SKU值的关系):

    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    using TikkyBoxWebAPI.Models.Account;
    using TikkyBoxWebAPI.Models;
    using TikkyBoxWebAPI.Models.Core;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata;
    using System.Linq;
    
    namespace TikkyBoxWebAPI.Data
    {
        public class TikkyBoxDbContext : DbContext
        {
    
            public TikkyBoxDbContext(DbContextOptions<TikkyBoxDbContext> options)
                : base(options)
            {
                Database.Migrate();
            }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
    
                modelBuilder
                    .Entity<ProductSKU>()
                    .HasKey(p => new { p.ProductId, p.SkuId });
    
                modelBuilder
                .Entity<ProductSKU>()
                .HasOne(p => p.Product)
                .WithMany(ps => ps.ProductSKUs)
                .HasForeignKey(x => x.ProductId);
    
                modelBuilder
                    .Entity<ProductSKU>()
                    .HasIndex(p => p.Sku);
    
                modelBuilder
                    .Entity<ProductSKU>()
                    .Property(p => p.SkuId).ValueGeneratedOnAdd();
    
                modelBuilder
                .Entity<ProductSKUValue>()
                .HasOne<ProductSKU>()
                .WithMany( p => p.ProductSKUValues)
                .IsRequired(false)
                .OnDelete(DeleteBehavior.Restrict);
    
                modelBuilder
                    .Entity<ProductSKUValue>()
                    .HasKey(p => new { p.ProductId, p.SkuId, p.OptionId});
    
                modelBuilder
                .Entity<ProductSKUValue>()
                .HasOne(p => p.ProductOptionValue)
                .WithMany(ps => ps.ProductSKUValues)
                .HasForeignKey(x => new { x.ProductId, x.OptionId, x.ValueId })
                .OnDelete(DeleteBehavior.Restrict);
    
                modelBuilder
                .Entity<ProductSKUValue>()
                .HasOne(p => p.ProductOption)
                .WithMany(ps => ps.ProductSKUValues)
                .HasForeignKey(x => new { x.ProductId, x.OptionId })
                .OnDelete(DeleteBehavior.Restrict);
    
                modelBuilder
            .Entity<ProductOptionValue>()
            .HasKey(p => new { p.ProductId, p.OptionId, p.ValueId });
    
                modelBuilder
            .Entity<ProductOptionValue>()
            .HasOne(p => p.ProductOption)
            .WithMany(ps => ps.ProductOptionValues)
            .HasForeignKey(x => new { x.ProductId, x.OptionId });
                //    .OnDelete(DeleteBehavior.Restrict);
    
                modelBuilder
                    .Entity<ProductOptionValue>()
                    .Property(p => p.ValueId).ValueGeneratedOnAdd();
    
    
                modelBuilder
            .Entity<ProductOption>()
            .HasKey(p => new { p.ProductId, p.OptionId });
    
                modelBuilder
            .Entity<ProductOption>()
            .HasOne(p => p.Product)
            .WithMany(po => po.ProductOptions)
            .HasForeignKey(x => new { x.ProductId })
            .OnDelete(DeleteBehavior.Restrict);
    
    
                modelBuilder
                .Entity<ProductOption>()
                .Property(p => p.OptionId).ValueGeneratedOnAdd();
    
                // base.OnModelCreating(modelBuilder);
    
            }
            public DbSet<Product> Products { get; set; }
            public DbSet<ProductOption> ProductOptions { get; set; }
            public DbSet<ProductOptionValue> ProductOptionValues { get; set; }
            public DbSet<ProductSKU> ProductSKUs { get; set; }
            public DbSet<ProductSKUValue> ProductSKUValues { get; set; }
        }
    }
    
    ProductOption.cs

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace TikkyBoxWebAPI.Models.Core
    {
    
        public class Product
        {
            public int Id { get; set; }
            [Required]
            public String Name { get; set; }
            // to be used for barcode : remember look at datatype
            [MaxLength(32)]
            public String UniversalProductCode { get; set; }
            public Decimal Height { get; set; }
            public Decimal Weight { get; set; }
            public Decimal NetWeight { get; set; }
            public Decimal Depth { get; set; }
    
            [MaxLength(128)]
            public String ShortDescription { get; set; }
            [MaxLength(255)]
            public String LongDescription { get; set; }
            public DateTime CreatedOn { get; set; }
            public DateTime UpdatedOn { get; set; }
            public virtual ICollection<ProductSKU> ProductSKUs { get; set; }
            public virtual ICollection<ProductOption> ProductOptions { get; set; }
    
        }
    
    
    }
    
         using System;
         using System.Collections.Generic;
         using System.ComponentModel.DataAnnotations;
         using System.ComponentModel.DataAnnotations.Schema;
    
         namespace TikkyBoxWebAPI.Models.Core
         {
                public class ProductSKU
                {
                    public int ProductId { get; set; }
                    public int SkuId { get; set; }
    
                    [Required]
                    [MaxLength(64)]
                    public String Sku { get; set; }
    
                    public  Product Product { get; set; }
                    public List<ProductSKUValue> ProductSKUValues { get; set; }
    
                }
         }
    
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace TikkyBoxWebAPI.Models.Core
    {
        public class ProductSKUValue
        {
            public int ProductId { get; set; }
    
            public int SkuId { get; set; }
    
            public int OptionId { get; set; }
            public int ValueId { get; set; }
    
            public virtual ProductSKU ProductSKU { get; set; }
            public virtual ProductOption ProductOption { get; set; }
            public virtual ProductOptionValue ProductOptionValue { get; set; }
    
        }
    }
    
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System;
    
    namespace TikkyBoxWebAPI.Models.Core
    {
        public class ProductOption
        {
            public int ProductId { get; set; }
            public int OptionId { get; set; }
            [Required]
            [MaxLength(40)]
            public String OptionName { get; set; }
            public virtual Product Product { get; set; }
    
            public virtual ICollection<ProductSKUValue> ProductSKUValues { get; set; }
            public virtual ICollection<ProductOptionValue> ProductOptionValues { get; set; }
    
        }
    }
    
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System;
    
    namespace TikkyBoxWebAPI.Models.Core
    {
        public class ProductOptionValue
        {
            public int ProductId { get; set; }
    
            public int ValueId  { get; set; }
            public int OptionId { get; set; }
            [Required]
            [MaxLength(32)]
            public String ValueName { get; set; }
    
            public virtual  ProductOption ProductOption { get; set; }
            public virtual  ICollection<ProductSKUValue> ProductSKUValues { get; set; }
        }
    }
    
    使用System.Collections.Generic;
    使用System.ComponentModel.DataAnnotations;
    使用System.ComponentModel.DataAnnotations.Schema;
    使用制度;
    命名空间TikkyBoxWebAPI.Models.Core
    {
    公共类产品选项
    {
    public int ProductId{get;set;}
    public int OptionId{get;set;}
    [必需]
    [MaxLength(40)]
    公共字符串选项名称{get;set;}
    公共虚拟产品产品{get;set;}
    公共虚拟ICollection ProductSKUValues{get;set;}
    公共虚拟ICollection ProductOptionValues{get;set;}
    }
    }
    
    ProductOptionValue.cs

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace TikkyBoxWebAPI.Models.Core
    {
    
        public class Product
        {
            public int Id { get; set; }
            [Required]
            public String Name { get; set; }
            // to be used for barcode : remember look at datatype
            [MaxLength(32)]
            public String UniversalProductCode { get; set; }
            public Decimal Height { get; set; }
            public Decimal Weight { get; set; }
            public Decimal NetWeight { get; set; }
            public Decimal Depth { get; set; }
    
            [MaxLength(128)]
            public String ShortDescription { get; set; }
            [MaxLength(255)]
            public String LongDescription { get; set; }
            public DateTime CreatedOn { get; set; }
            public DateTime UpdatedOn { get; set; }
            public virtual ICollection<ProductSKU> ProductSKUs { get; set; }
            public virtual ICollection<ProductOption> ProductOptions { get; set; }
    
        }
    
    
    }
    
         using System;
         using System.Collections.Generic;
         using System.ComponentModel.DataAnnotations;
         using System.ComponentModel.DataAnnotations.Schema;
    
         namespace TikkyBoxWebAPI.Models.Core
         {
                public class ProductSKU
                {
                    public int ProductId { get; set; }
                    public int SkuId { get; set; }
    
                    [Required]
                    [MaxLength(64)]
                    public String Sku { get; set; }
    
                    public  Product Product { get; set; }
                    public List<ProductSKUValue> ProductSKUValues { get; set; }
    
                }
         }
    
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace TikkyBoxWebAPI.Models.Core
    {
        public class ProductSKUValue
        {
            public int ProductId { get; set; }
    
            public int SkuId { get; set; }
    
            public int OptionId { get; set; }
            public int ValueId { get; set; }
    
            public virtual ProductSKU ProductSKU { get; set; }
            public virtual ProductOption ProductOption { get; set; }
            public virtual ProductOptionValue ProductOptionValue { get; set; }
    
        }
    }
    
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System;
    
    namespace TikkyBoxWebAPI.Models.Core
    {
        public class ProductOption
        {
            public int ProductId { get; set; }
            public int OptionId { get; set; }
            [Required]
            [MaxLength(40)]
            public String OptionName { get; set; }
            public virtual Product Product { get; set; }
    
            public virtual ICollection<ProductSKUValue> ProductSKUValues { get; set; }
            public virtual ICollection<ProductOptionValue> ProductOptionValues { get; set; }
    
        }
    }
    
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System;
    
    namespace TikkyBoxWebAPI.Models.Core
    {
        public class ProductOptionValue
        {
            public int ProductId { get; set; }
    
            public int ValueId  { get; set; }
            public int OptionId { get; set; }
            [Required]
            [MaxLength(32)]
            public String ValueName { get; set; }
    
            public virtual  ProductOption ProductOption { get; set; }
            public virtual  ICollection<ProductSKUValue> ProductSKUValues { get; set; }
        }
    }
    
    使用System.Collections.Generic;
    使用System.ComponentModel.DataAnnotations;
    使用System.ComponentModel.DataAnnotations.Schema;
    使用制度;
    命名空间TikkyBoxWebAPI.Models.Core
    {
    公共类ProductOptionValue
    {
    public int ProductId{get;set;}
    public int ValueId{get;set;}
    public int OptionId{get;set;}
    [必需]
    [MaxLength(32)]
    公共字符串ValueName{get;set;}
    公共虚拟产品选项产品选项{get;set;}
    公共虚拟ICollection ProductSKUValues{get;set;}
    }
    }
    

    我已经在StackOverflow和web上尝试了这些答案,但没有成功:

    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    using TikkyBoxWebAPI.Models.Account;
    using TikkyBoxWebAPI.Models;
    using TikkyBoxWebAPI.Models.Core;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata;
    using System.Linq;
    
    namespace TikkyBoxWebAPI.Data
    {
        public class TikkyBoxDbContext : DbContext
        {
    
            public TikkyBoxDbContext(DbContextOptions<TikkyBoxDbContext> options)
                : base(options)
            {
                Database.Migrate();
            }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
    
                modelBuilder
                    .Entity<ProductSKU>()
                    .HasKey(p => new { p.ProductId, p.SkuId });
    
                modelBuilder
                .Entity<ProductSKU>()
                .HasOne(p => p.Product)
                .WithMany(ps => ps.ProductSKUs)
                .HasForeignKey(x => x.ProductId);
    
                modelBuilder
                    .Entity<ProductSKU>()
                    .HasIndex(p => p.Sku);
    
                modelBuilder
                    .Entity<ProductSKU>()
                    .Property(p => p.SkuId).ValueGeneratedOnAdd();
    
                modelBuilder
                .Entity<ProductSKUValue>()
                .HasOne<ProductSKU>()
                .WithMany( p => p.ProductSKUValues)
                .IsRequired(false)
                .OnDelete(DeleteBehavior.Restrict);
    
                modelBuilder
                    .Entity<ProductSKUValue>()
                    .HasKey(p => new { p.ProductId, p.SkuId, p.OptionId});
    
                modelBuilder
                .Entity<ProductSKUValue>()
                .HasOne(p => p.ProductOptionValue)
                .WithMany(ps => ps.ProductSKUValues)
                .HasForeignKey(x => new { x.ProductId, x.OptionId, x.ValueId })
                .OnDelete(DeleteBehavior.Restrict);
    
                modelBuilder
                .Entity<ProductSKUValue>()
                .HasOne(p => p.ProductOption)
                .WithMany(ps => ps.ProductSKUValues)
                .HasForeignKey(x => new { x.ProductId, x.OptionId })
                .OnDelete(DeleteBehavior.Restrict);
    
                modelBuilder
            .Entity<ProductOptionValue>()
            .HasKey(p => new { p.ProductId, p.OptionId, p.ValueId });
    
                modelBuilder
            .Entity<ProductOptionValue>()
            .HasOne(p => p.ProductOption)
            .WithMany(ps => ps.ProductOptionValues)
            .HasForeignKey(x => new { x.ProductId, x.OptionId });
                //    .OnDelete(DeleteBehavior.Restrict);
    
                modelBuilder
                    .Entity<ProductOptionValue>()
                    .Property(p => p.ValueId).ValueGeneratedOnAdd();
    
    
                modelBuilder
            .Entity<ProductOption>()
            .HasKey(p => new { p.ProductId, p.OptionId });
    
                modelBuilder
            .Entity<ProductOption>()
            .HasOne(p => p.Product)
            .WithMany(po => po.ProductOptions)
            .HasForeignKey(x => new { x.ProductId })
            .OnDelete(DeleteBehavior.Restrict);
    
    
                modelBuilder
                .Entity<ProductOption>()
                .Property(p => p.OptionId).ValueGeneratedOnAdd();
    
                // base.OnModelCreating(modelBuilder);
    
            }
            public DbSet<Product> Products { get; set; }
            public DbSet<ProductOption> ProductOptions { get; set; }
            public DbSet<ProductOptionValue> ProductOptionValues { get; set; }
            public DbSet<ProductSKU> ProductSKUs { get; set; }
            public DbSet<ProductSKUValue> ProductSKUValues { get; set; }
        }
    }
    
  • 我正在使用

    • Microsoft.EntityFrameworkCore.SqlServer
    • Microsoft.EntityFrameworkCore.Design
    • Microsoft.AspNetCore.Identity.EntityFrameworkCore版本1.1.2

    任何帮助都将不胜感激。我已经在网上搜索解决方案两天了

    除了以下流畅的配置之外,一切都正常

    modelBuilder
        .Entity<ProductSKUValue>()
        .HasOne<ProductSKU>()
        .WithMany(p => p.ProductSKUValues)
        .IsRequired(false)
        .OnDelete(DeleteBehavior.Restrict);
    
    请注意
    ProductSKUs
    的附加列和两个FK约束

    要解决此问题,只需使用适当的配置(类似于对其他关系所做的配置):

    modelBuilder
    .实体()
    .HasOne(p=>p.ProductSKU)
    .WithMany(p=>p.productsku值)
    .HasForeignKey(x=>new{x.ProductId,x.SkuId})
    .OnDelete(DeleteBehavior.Restrict);
    
    为了获得帮助,请删除代码截图,并为所有涉及的实体(毕竟,它们只有5个)和相关的流畅配置添加实际代码。@IvanStoev好的,不仅仅是5个实体,还有更多。但我将删除其他关系和模型,并分离出最简单、最完美的部分。显示的模型肯定包含多个级联路径,仅隔离这些实体将有助于再现问题,并确定问题是否由它们或其他原因引起。@IvanStoev,我已使用涉及的所有实体更新了Mate,请发布代码,而不是图片。它应该更容易为您,也为我们能够复制/粘贴在一个测试环境。这没有工作。仍然得到相同的错误。我只做了你建议的配置更改。那么恐怕我无能为力了。通过提供的模型、配置和上述更改,我能够生成(并成功应用)包含这5个表及其关系的迁移。Ok。我将尝试迁移。我删除了“IsRequired”,必须在现有数据库运行之前删除它。谢谢你,伙计!