首页 > 解决方案 > EF Core - 与相同依赖实体类型的附加导航属性的一对多关系

问题描述

我在 EF Core 中配置我的关系时遇到问题。我受到以下例外的欢迎 -

指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束

我已经为这篇文章删减了实体,但是这两个实体都有自己的表格。

public class ApplicationSetupTest
{
    public Guid Id { get; set; }
    public Guid SchemeId { get; set; }
    public string Description { get; set; }

    public Guid LatestVersionId { get; set; }
    public ApplicationSetupVersionTest LatestVersion { get; set; }

    public ICollection<ApplicationSetupVersionTest> VersionHistory { get; set; }
}

public class ApplicationSetupVersionTest
{
    public Guid Id { get; set; }
    public Guid SetupId { get; set; }
    public string Data { get; set; }
    public string AuditComment { get; set; }
    public Guid PreviousVersionId { get; set; }
}

该类有效地使用作为导航属性键的ApplicationSetupTest静态数据定义。LatestVersionIdLatestVersion

该类ApplicationSetupVersionTest是版本化/审计的数据。这些中的每一个都有一个SetupId将其链接回ApplicationSetupTest所指的对象。

VersionHistory纯粹为这篇文章添加了该属性,以证明ApplicationSetupVersionTest每个ApplicationSetupTest. 我没有添加一个ApplicationSetupTestApplicationSetupVersionTest因为这不是我期望需要的。

我的配置ApplicationSetupTest如下:

public class ApplicationSetupEntityConfiguration : IEntityTypeConfiguration<ApplicationSetupTest>
{
    public void Configure(EntityTypeBuilder<ApplicationSetupTest> builder)
    {
        builder.Property(t => t.SchemeId).IsRequired();
        builder.Property(t => t.Description).IsRequired();
        builder.Property(t => t.LatestVersionId).IsRequired();

        builder.HasMany(t => t.VersionHistory)
            .WithOne()
            .HasForeignKey(t => t.SetupId)
            .IsRequired();

        builder.HasOne(t => t.LatestVersion)
            .WithOne()
            .HasForeignKey<ApplicationSetupTest>(t => t.LatestVersionId)
            .OnDelete(DeleteBehavior.NoAction)
            .IsRequired();

        builder.HasOne<Scheme>()
            .WithMany()
            .HasForeignKey(t => t.SchemeId)
            .IsRequired();
    }
}

- HasMany> WithOneonVersionHistory用于定义当我删除设置时,我应该删除所有版本实体。

因此,我认为第二种配置是需要更改的区域。是在 Google 搜索之后添加的OnDelete(NoAction),我还尝试删除IsRequired()以及使null 可以为LatestVersionId空。

我正在寻找配置第二个关系,以便LatestVersion可以在查询中包含该属性。

关于如何配置这种关系的任何想法?还是我在做一些你不推荐的事情?

标签: entity-framework-coreentity-relationship

解决方案


(为了简单起见,我将模型称为SetupVersion)。

使用您的一对多配置 -

builder.HasMany(t => t.VersionHistory)
    .WithOne()
    .HasForeignKey(t => t.SetupId)
    .IsRequired();

您已声明Setup为主端和Version从属端,这是正确的。

但是你有一个LatestVersionId外键Setup,引用Version和配置一对一关系 -

builder.HasOne(t => t.LatestVersion)
    .WithOne()
    .HasForeignKey<ApplicationSetupTest>(t => t.LatestVersionId)
    .OnDelete(DeleteBehavior.NoAction)
    .IsRequired();

试图配置Setup为从属端和Version主端。我相信你可以看到矛盾。

使用以下简化模型 -

public class Setup
{
    public Guid Id { get; set; }
    public string Description { get; set; }

    public Version LatestVersion { get; set; }
    public ICollection<Version> VersionHistory { get; set; }
}

public class Version
{
    public Guid Id { get; set; }        
    public string Data { get; set; }

    // not nullable - every Version must belong to a Setup
    public Guid SetupIdHistory { get; set; }

    // nullable - not every Version is a latest version
    public Guid? SetupIdLatest { get; set; }
}

您可以正确配置它们以将您的关系表示为 -

public void Configure(EntityTypeBuilder<Setup> builder)
{
    builder.HasMany(p => p.VersionHistory)
        .WithOne()
        .HasForeignKey(p => p.SetupIdHistory)
        .OnDelete(DeleteBehavior.Cascade)  // not required, cascading is default
        .IsRequired();

    builder.HasOne(p => p.LatestVersion)
        .WithOne()
        .HasForeignKey<Version>(p => p.SetupIdLatest)
        .OnDelete(DeleteBehavior.NoAction)
        .IsRequired(false);
}

如果您选择不为一对多关系使用外键,EF 将为您创建一个可为空的外键,并使用影子属性在模型级别管理该关系。但是对于一对一的关系,你必须定义一个外键。

public class Version
{
    public Guid Id { get; set; }        
    public string Data { get; set; }

    // nullable - not every Version is a latest version
    public Guid? SetupId { get; set; }
}
public void Configure(EntityTypeBuilder<Setup> builder)
{
    builder.HasMany(p => p.VersionHistory)
        .WithOne()
        .OnDelete(DeleteBehavior.Cascade)
        .IsRequired();    // this will have no effect, the FK will be nullable

    builder.HasOne(p => p.LatestVersion)
        .WithOne()
        .HasForeignKey<Model.Version>(p => p.SetupId)
        .OnDelete(DeleteBehavior.NoAction)
        .IsRequired(false);
}

推荐阅读