我正在尝试使用 Entity Framework Core 为产品变体数据库设计建模
设计面临的问题/障碍:
- 我在运行
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.
数据库设计:
注意:此设计是基于此链接建模的: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 和网络上尝试了这些答案,但没有成功:
- Configuring Many to Many in Entity Framework Core
- Docs: Entity Framework Core Relationships
- 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/