EF Core:在级联循环和具有简单名称的冲突 属性 之间游刃有余

EF Core: Juggling between cascading cycles and conflicting property with simple name

我试图通过使用链接 table Booking 创建多对多关系,这让我在两个错误之间徘徊,我很困惑。

Introducing FOREIGN KEY constraint 'FK_Bookings_WorkProfiles_WorkProfileId' on table 'Bookings' 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. See previous errors.

我试图解决进入 Context class modelBuilder.

partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Booking>()
                .HasOne(e => e.UserProfile)
                .WithMany()
                .OnDelete(DeleteBehavior.Restrict);
}

但这会产生以下错误:

The foreign key property 'Booking.UserProfileId1' was created in shadow state because a conflicting property with the simple name 'UserProfileId' exists in the entity type, but is either not mapped, is already used for another relationship, or is incompatible with the associated primary key type. See https://aka.ms/efcore-relationships for information on mapping relationships in EF Core.

我很困惑。我尝试通过删除外键等来修复此错误,但我又回到了第一个错误。

我想要什么:删除 UserProfileWorkProfile 以不级联删除预订,这样我就不会收到第一个错误。

public class Booking : BaseDateClass
{
    public int BookingId { get; set; }
    [ForeignKey("AdvertTreatmentId")]
    public AdvertTreatment AdvertTreatment { get; set; }
    [ForeignKey("UserProfileId")]
    public UserProfile UserProfile { get; set; }
    [ForeignKey("WorkProfileId")]
    public WorkProfile WorkProfile { get; set; }
}

public class UserProfile : BaseDateClass
{
    public int UserProfileId { get; set; }
    public List<Booking> Bookings { get; set; }
}

public class WorkProfile : BaseDateClass
{
    public int WorkProfileId { get; set; }
    [ForeignKey("WorkAccountId")]
    public WorkAccount WorkAccount { get; set; }
    public List<Advert>? Adverts { get; set; }
    public List<WorkProfileLanguage> WorkProfileLanguages { get; set; }
    public List<Booking> Bookings { get; set; }
}

编辑:这是一个Entity Framework Core 6.0.5 项目

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.5" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.5" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite" Version="6.0.5" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
  </ItemGroup>

我认为你的问题是你在属性中定义 FK,然后 FluentApi 试图添加另一个。使用 by-convention 值,该值将与通过属性指定的项目相同。

尝试删除:

[ForeignKey("UserProfileId")]

从您的 class 并将您的 Fluent 配置更新为:

    modelBuilder.Entity<Booking>()
    .HasOne(e => e.UserProfile)
    .WithMany()
    .HasForiegnKey("UserProfileId")
     .OnDelete(DeleteBehavior.Restrict);

更新

我猜你的其他关系导致了一些问题。

在没有任何 Fluent Config 的情况下使用以下模型:

public class Booking
{
    public int BookingId { get; set; }
    [ForeignKey("UserProfileId")]
    public UserProfile UserProfile { get; set; }
    [ForeignKey("WorkProfileId")]
    public WorkProfile WorkProfile { get; set; }
}

public class UserProfile
{
    public int Id { get; set; }
    public List<Booking> Bookings { get; set; }
}

public class WorkProfile
{
    public int WorkProfileId { get; set; }
    public List<Booking> Bookings { get; set; }
}

我创建的迁移没有问题,并且能够更新到 Sql 服务器数据库。

创建的迁移是:

public partial class initial : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "UserProfile",
            columns: table => new
            {
                Id = table.Column<int>(type: "int", nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1")
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_UserProfile", x => x.Id);
            });

        migrationBuilder.CreateTable(
            name: "WorkProfile",
            columns: table => new
            {
                WorkProfileId = table.Column<int>(type: "int", nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1")
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_WorkProfile", x => x.WorkProfileId);
            });

        migrationBuilder.CreateTable(
            name: "Bookings",
            columns: table => new
            {
                BookingId = table.Column<int>(type: "int", nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                UserProfileId = table.Column<int>(type: "int", nullable: false),
                WorkProfileId = table.Column<int>(type: "int", nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Bookings", x => x.BookingId);
                table.ForeignKey(
                    name: "FK_Bookings_UserProfile_UserProfileId",
                    column: x => x.UserProfileId,
                    principalTable: "UserProfile",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
                table.ForeignKey(
                    name: "FK_Bookings_WorkProfile_WorkProfileId",
                    column: x => x.WorkProfileId,
                    principalTable: "WorkProfile",
                    principalColumn: "WorkProfileId",
                    onDelete: ReferentialAction.Cascade);
            });

        migrationBuilder.CreateIndex(
            name: "IX_Bookings_UserProfileId",
            table: "Bookings",
            column: "UserProfileId");

        migrationBuilder.CreateIndex(
            name: "IX_Bookings_WorkProfileId",
            table: "Bookings",
            column: "WorkProfileId");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(
            name: "Bookings");

        migrationBuilder.DropTable(
            name: "UserProfile");

        migrationBuilder.DropTable(
            name: "WorkProfile");
    }
}

备选

public class Booking
{
    public int BookingId { get; set; }
    //[ForeignKey("UserProfileId")]  // REMOVED
    public UserProfile UserProfile { get; set; }
    [ForeignKey("WorkProfileId")]
    public WorkProfile WorkProfile { get; set; }
}

public class UserProfile
{
    public int Id { get; set; }
    public List<Booking> Bookings { get; set; }
}

public class WorkProfile
{
    public int WorkProfileId { get; set; }
    public List<Booking> Bookings { get; set; }
}

public class Context : DbContext
{
    public Context(DbContextOptions<Context> options)
    : base(options) { }

    public DbSet<Booking> Bookings { get; set; }

    public DbSet<UserProfile> UserProfiles { get; set; }    

    public DbSet<WorkProfile> WorkProfiles { get; set; }

    protected override void OnModelCreating(ModelBuilder mb)
    {
        mb.Entity<Booking>()
            .HasOne(b => b.UserProfile)
            .WithMany()
            .HasForeignKey("UserProfileId")
            .OnDelete(DeleteBehavior.Restrict);
    }
}

also works fine.

刚注意到你OnModelCreating的签名!!

partial void OnModelCreatingPartial(ModelBuilder modelBuilder)

更改它以匹配我最新 post 中的签名。

第二个问题是fluent关系配置的典型错误造成的

关系配置需要先Has{One|Many}后跟[=​​13=],但是做的时候很多人没有意识到不把导航属性传给这些fluent calls 并不意味着“使用常规默认值”,而是意味着该关系在相应端具有 no 导航 属性。

但是,当您 有导航 属性 时,这会使它未映射,这反过来导致 EF 将不同的单独关系与传统的 FK 名称相关联。这反过来会导致出现问题的错误,并且通常不是您想要的。

因此,您应该始终将正确的参数传递给 Has / With 方法。

在您的情况下,问题的原因是 HasMany() 调用此处

    modelBuilder.Entity<Booking>()
                .HasOne(e => e.UserProfile)
                .WithMany() // <--
                .OnDelete(DeleteBehavior.Restrict);

这使得 UserProfile.Bookings 集合导航 属性 未映射。

所以改成

.WithMany(e => e.Bookings)

问题就迎刃而解了