【问题标题】:Adding another foreign key to a table setup in ef core在 ef core 中的表设置中添加另一个外键
【发布时间】:2020-10-20 09:30:30
【问题描述】:

我正在与 ef 一起从事 .net 核心项目。 我有两张桌子:

    public class Asset
    {
        [Key]
        public Guid Id { get; set; }
        
        public string Name { get; set; }
        public string Description { get; set; }
        
        // Relationships
        public ICollection<AssetMixRecord> AssetMixRecords { get; set; }
    }

    public class AssetMixRecord
    {        
        public decimal Percentage { get; set; }        
        public Guid AssetId { get; set; }

        // Relationships
        public Guid ParentAssetId { get; set; }
    }

上下文如下所示:

            modelBuilder.Entity<Asset>()
                .HasMany(a => a.AssetMixRecords)
                .WithOne()
                .OnDelete(DeleteBehavior.Cascade);

            modelBuilder.Entity<AssetMixRecord>()
                .HasKey(c => new { c.ParentAssetId, c.AssetId })
                .IsClustered();

此迁移代码如下所示:

            migrationBuilder.CreateTable(
                name: "AssetMixRecords",
                columns: table => new
                {
                    AssetId = table.Column<Guid>(nullable: false),
                    ParentAssetId = table.Column<Guid>(nullable: false),
                    Percentage = table.Column<decimal>(type: "decimal(8,7)", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AssetMixRecords", x => new { x.ParentAssetId, x.AssetId })
                        .Annotation("SqlServer:Clustered", true);
                    table.ForeignKey(
                        name: "FK_AssetMixRecords_Assets_AssetId",
                        column: x => x.AssetId,
                        principalTable: "Assets",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

这一切都很好。但是“ParentAssetId”也是“Assets.Id”的外键。

我们的用例如下所示:

var asset1 = new Asset();
            var asset2 = new Asset();

            dbContext.Add(asset1);
            dbContext.Add(asset2);
            dbContext.SaveChanges();

            var asset3 = new Asset();

            asset3.AssetMixRecords.Add(new AssetMixRecord()
            {
                AssetId = asset1.Id,
                ParentAssetId = asset3.Id
            });

            asset3.AssetMixRecords.Add(new AssetMixRecord()
            {
                AssetId = asset2.Id,
                ParentAssetId = asset3.Id
            });

            dbContext.Add(asset3);
            dbContext.SaveChanges();

我无法将第二个外键放入迁移代码中。我应该手动添加吗?

感谢和问候

S.

【问题讨论】:

    标签: .net-core entity-framework-core entity-framework-core-migrations


    【解决方案1】:

    请查看我的完整演示解决方案,它将生成以下迁移。我不确定DeleteBehavior.Cascade 是否可以使用这种架构,EF 迁移工具抱怨循环依赖。

    migrationBuilder.DropForeignKey(
        name: "FK_AssetMixRecords_Assets_AssetId",
        table: "AssetMixRecords");
    
    migrationBuilder.AddForeignKey(
        name: "FK_AssetMixRecords_Assets_AssetId",
        table: "AssetMixRecords",
        column: "AssetId",
        principalTable: "Assets",
        principalColumn: "Id");
    
    migrationBuilder.AddForeignKey(
        name: "FK_AssetMixRecords_Assets_ParentAssetId",
        table: "AssetMixRecords",
        column: "ParentAssetId",
        principalTable: "Assets",
        principalColumn: "Id");
    

    演示本身如下。请注意Asset 内部有 2 个导航属性。 EF 将根据子实体的位置(AssetId 或 ParentAssetId)加载那里的子实体

    using Microsoft.EntityFrameworkCore;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    
    namespace ConsoleApp9
    {
        public class Asset
        {
            [Key]
            public Guid Id { get; set; }
    
            public string Name { get; set; }
            public string Description { get; set; }
    
            // items where AssetMixRecord.AssetId == Id
            public ICollection<AssetMixRecord> AssetMixRecords { get; set; }
    
            // items where AssetMixRecord.ParentAssetId == Id
            public ICollection<AssetMixRecord> ParentAssetMixRecords { get; set; }
        }
    
        public class AssetMixRecord
        {
            public decimal Percentage { get; set; }
            public Guid AssetId { get; set; }
            public virtual Asset Asset { get; set; }
            public Guid ParentAssetId { get; set; }
            public virtual Asset ParentAsset { get; set; }
        }
    
        public class ApplicationDbContext : DbContext
        {
            public DbSet<Asset> Assets { get; set; }
            public DbSet<AssetMixRecord> AssetMixRecords { get; set; }
    
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSqlServer("Server=DESKTOP-1111111;Database=testef11db;Integrated Security=true;");
    
                base.OnConfiguring(optionsBuilder);
            }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Asset>()
                    .HasMany(a => a.AssetMixRecords)
                    .WithOne(x => x.Asset)
                    .HasForeignKey(x => x.AssetId)
                    .OnDelete(DeleteBehavior.NoAction);
    
                modelBuilder.Entity<Asset>()
                    .HasMany(x => x.ParentAssetMixRecords)
                    .WithOne(x => x.ParentAsset)
                    .HasForeignKey(x => x.ParentAssetId)
                    .OnDelete(DeleteBehavior.NoAction);
    
                modelBuilder.Entity<AssetMixRecord>()
                    .HasKey(c => new { c.ParentAssetId, c.AssetId })
                    .IsClustered();
    
                base.OnModelCreating(modelBuilder);
            }
        }
        class Program
        {
            static void Main(string[] args)
            {
                var dbContext = new ApplicationDbContext();
    
                var asset1 = new Asset();
                var asset2 = new Asset();
    
                dbContext.Add(asset1);
                dbContext.Add(asset2);
                dbContext.SaveChanges();
    
                var assetDataMixRecord = new AssetMixRecord()
                {
                    AssetId = asset1.Id,
                    ParentAssetId = asset2.Id
                };
    
                dbContext.Add(assetDataMixRecord);
                dbContext.SaveChanges();
    
                var assets = dbContext.Assets
                    .Include(x => x.AssetMixRecords)
                    .Include(x => x.ParentAssetMixRecords)
                    .ToList();
            }
        }
    }
    

    如果您不想 ParentAssetId 等于 ChildAssetId,只需将下面的代码添加到上面的解决方案中

    modelBuilder.Entity<AssetMixRecord>()
        .HasCheckConstraint("PK_Check_ChildAssetId_And_ParentAssetId", "ParentAssetId != ChildAssetId");
    

    如果您将AssetMixRecords 更改为ParentAssetMixRecords for asset3,您的用例将正常工作

    var dbContext = new ApplicationDbContext();
    var asset1 = new Asset();
    var asset2 = new Asset();
    
    dbContext.Add(asset1);
    dbContext.Add(asset2);
    dbContext.SaveChanges();
    
    var asset3 = new Asset();
    
    asset3.ParentAssetMixRecords.Add(new AssetMixRecord()
    {
        AssetId = asset1.Id,
        ParentAssetId = asset3.Id
    });
    
    asset3.ParentAssetMixRecords.Add(new AssetMixRecord()
    {
        AssetId = asset2.Id,
        ParentAssetId = asset3.Id
    });
    
    dbContext.Add(asset3);
    dbContext.SaveChanges();
    

    【讨论】:

    • 您好叶戈尔,非常感谢您的努力。我从中得到了一些灵​​感,尽管它不是我想要的。例如: // AssetMixRecord.AssetId == Id 的项目将完全破坏我们的用例。这绝对不能发生,我们需要避免它。
    • 好的,不要忘记发布您自己的解决方案作为答案
    • :-) 目前,解决方案似乎更遥远:github.com/dotnet/efcore/issues/23071
    • 公平地说,如果OnModelCreating中没有提到,我不知道为什么你认为父资产id应该是迁移中的外键
    • 我不确定我是否理解您的评论。这是问题的一部分。如何将两个外键放入上下文定义中。
    猜你喜欢
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 2023-03-07
    • 2019-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多