c# - Entity Framework 核心 : Challenge Modeling Product Variants Database Design with Many to Many

标签 c# entity-framework asp.net-core asp.net-core-mvc entity-framework-core

我正在尝试使用 Entity Framework Core产品变体数据库设计建模

设计面临的问题/障碍:

  1. 我在运行 dotnet ef migrations add InitialCreate 命令时收到以下错误:

Introducing FOREIGN KEY constraint 'FK_ProductSKUValues_ProductSKUs_ProductId_SkuId' on table 'ProductSKUValues' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not create constraint or index.

数据库设计:


enter image description here

注意:此设计是基于此链接建模的:Modeling Product Variants

具有 Fluent API 的 ApplicationDbContext.cs(注意 ProductSKU 和 ProductSKUValue 关系):

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

Product.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; }

    }


}

ProductSKU.cs

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

            }
     }

ProductSKUValue.cs

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

    }
}

ProductOption.cs

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

    }
}

ProductOptionValue.cs

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

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

  1. Configuring Many to Many in Entity Framework Core
  2. Docs: Entity Framework Core Relationships
  3. EF One-To-Many - may cause cycles or multiple cascade paths Ef 4 Solution with nullable primary key (which I have tried)

我正在使用

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

如有任何帮助,我们将不胜感激。我已经在网上搜索了 2 天的解决方案

最佳答案

除了下面的流利配置,其他都ok

modelBuilder
    .Entity<ProductSKUValue>()
    .HasOne<ProductSKU>()
    .WithMany(p => p.ProductSKUValues)
    .IsRequired(false)
    .OnDelete(DeleteBehavior.Restrict);

这导致了几个问题。

首先,无参.HasOne<ProductSKU>()离开 ProductSKU ProductSKUValue 的导航属性类未映射,因此按照惯例 EF 会尝试创建另一个一对多关系。

第二,.IsRequired(false)不允许使用现有的 {ProductId, SkuId}字段作为外键,因为它们是必需的(不允许 null 值),因此 EF 为此创建了另外两个可为 null 的字段。

这是上述配置的结果表:

migrationBuilder.CreateTable(
    name: "ProductSKUValues",
    columns: table => new
    {
        ProductId = table.Column<int>(nullable: false),
        SkuId = table.Column<int>(nullable: false),
        OptionId = table.Column<int>(nullable: false),
        ProductSKUProductId = table.Column<int>(nullable: true),
        ProductSKUProductId1 = table.Column<int>(nullable: true),
        ProductSKUSkuId = table.Column<int>(nullable: true),
        ProductSKUSkuId1 = table.Column<int>(nullable: true),
        ValueId = table.Column<int>(nullable: false)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_ProductSKUValues", x => new { x.ProductId, x.SkuId, x.OptionId });
        table.ForeignKey(
            name: "FK_ProductSKUValues_ProductOptions_ProductId_OptionId",
            columns: x => new { x.ProductId, x.OptionId },
            principalTable: "ProductOptions",
            principalColumns: new[] { "ProductId", "OptionId" },
            onDelete: ReferentialAction.Restrict);
        table.ForeignKey(
            name: "FK_ProductSKUValues_ProductSKUs_ProductSKUProductId_ProductSKUSkuId",
            columns: x => new { x.ProductSKUProductId, x.ProductSKUSkuId },
            principalTable: "ProductSKUs",
            principalColumns: new[] { "ProductId", "SkuId" },
            onDelete: ReferentialAction.Restrict);
        table.ForeignKey(
            name: "FK_ProductSKUValues_ProductSKUs_ProductSKUProductId1_ProductSKUSkuId1",
            columns: x => new { x.ProductSKUProductId1, x.ProductSKUSkuId1 },
            principalTable: "ProductSKUs",
            principalColumns: new[] { "ProductId", "SkuId" },
            onDelete: ReferentialAction.Restrict);
        table.ForeignKey(
            name: "FK_ProductSKUValues_ProductOptionValues_ProductId_OptionId_ValueId",
            columns: x => new { x.ProductId, x.OptionId, x.ValueId },
            principalTable: "ProductOptionValues",
            principalColumns: new[] { "ProductId", "OptionId", "ValueId" },
            onDelete: ReferentialAction.Restrict);
    });

请注意 ProductSKUs 的附加列和两个 FK 约束.

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

modelBuilder
    .Entity<ProductSKUValue>()
    .HasOne(p => p.ProductSKU)
    .WithMany(p => p.ProductSKUValues)
    .HasForeignKey(x => new { x.ProductId, x.SkuId })
    .OnDelete(DeleteBehavior.Restrict);

关于c# - Entity Framework 核心 : Challenge Modeling Product Variants Database Design with Many to Many,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45627859/

相关文章:

c# - 如何在附加到 DBContext 之前单独通过属性检索 EF 4 代码优先实体的 [Key]?

c# - 如何处理修改后的实体记录

c# - 英孚核心 2.1。将自定义列添加到 MigrationHistory 表

docker - .NET Core Docker镜像似乎未安装node.js

Azure Cosmos DB : HTTP 400 in Application Insights

c# - 无法为投影仪、asp.net projet 显示器等不同设备修复屏幕分辨率

c# - 用 C# (WP7) 创建音频文件

c# - Azure 不从 WebJob 读取连接字符串

c# - 在右边<TD>的右边显示左边的<TD>

entity-framework - CRUD(插入和更新)函数应该返回什么?