Entity Framework 核心 - owned child 和另一个 chained owned child 之间的关系

Entity Framework Core - Relationship between owned child and another chained owned child

我遇到了问题,我正在尝试以正确的方式解决。

我有多个 one-to-many 'owning' 关系。但是我在两个 children Story 之间有一个重复的关系应该有一个 Variables 的列表并且链接 child Change应该指向此列表中的一个 Variable。我无法将一个 class 设置为由 2 个实体拥有,并且只有 'having' 关系我无法立即保存整个结构。我的代码结构的示例。

编辑:另一个问题来自 TransitionOption,它有 Step 它所属的 Step[=44] =] 它指向的地方。

public class Story //Main structure example class - A before Edit
{
   [Key]
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public int Id { get; set; }

   ...name, creator...
   
   [ForeignKey("FK_Story_ID")]
   public List<Step> Steps { get; set; } //Set as OwnsMany

   [ForeignKey("FK_Story_ID")]
   public List<Variable> Variables { get; set; } //Set as OwnsMany , Full list of Variables, that will be shown, which can but does not need to be changed during Step transitions
}

//example class with some main text about changes like part of the story
public class Step 
{
   [Key]
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public int Id { get; set; }

   ...some text...

   [ForeignKey("FK_Step_ID")]
   public List<TransitionOption> TransitionOptions { get; set; } //Set as OwnsMany

   public int FK_Story_ID { get; set; }
}

//example class for some option pointing to another Step
public class TransitionOption
{
   [Key]
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public int Id { get; set; }       

   [ForeignKey("FK_TransitionOption_ID")]
   public List<Change> Changes { get; set; } //Set as OwnsMany

   public int FK_Step_ID { get; set; }

   public int FK_NextStep_ID { get; set; } //this will also cause problem and I forgot to mention it before Edit
}

//referencing to one Variable from List Variables in Story
public class Change
{
   [Key]
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public int Id { get; set; }

   [ForeignKey("FK_Change_ID")]
   public Variable ChangingVariable { get; set; } //Set as OwnsOne/HasOne - or another way around with List of Changes in Variable. But this is more suitable for my case

public int FK_TransitionOption_ID { get; set; }
}

//Child that I need to have in both one Story (Story OwnsMany Variables) and multiple Changes (Change has/owns one Variable respectively Variable has/owns multiple Changes)
public class Variable 
{
   [Key]
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public int Id { get; set; }

   ...Starting value, conditions, type (text/number)...

   public int FK_Change_ID { get; set; }
}

状态将在最后的过程中保留,所以我不需要在数据库中跟踪它们。这更像是所有转换和变量的模板和容器。这个例子展示了一个类似互动故事的东西。有点符合我的人际关系。

问题 是,我不知道如何最好地为 EF Core 正确指定此关系。 编辑:另一个 ProblemTransitionOption 一起出现,它有 Step 所属的 Step 和下一个 Step 它指向的。我在编辑更易读的示例格式时发现了这个问题。

我正在尝试拥有关系 Story-Variable 和拥有关系 Change-Variable 或 Variable-Change,这会在迁移时引发错误。与关系Story-Variable,保存抛出外键违规,因为FK是0,因为故事还没有保存,没有ID,此时0不知何故被当作有效身份证件。 我想将它们全部保存为一个(如果可能的话)并将它们全部作为一个加载,因为我有 Angular 前端期待它并将其发回编辑。

如果有遗漏或不易理解的地方,我会尽力更好地回答和编辑。感谢所有评论。

我希望有人已经解决了这种情况,但我在文档中找不到这种情况的任何内容。请帮忙。

编辑:我已将结构更改为更像是我提供给@grek40 的示例(谢谢)。我无法发布真正的 classes,但这个例子应该几乎 1:1 到我遇到的问题。

基本上,OwnsMany 是一种特殊的关系,一旦您拥有多个具有指向同一目标的导航属性的实体,这种关系就不适用了。在这种情况下,您应该使用 HasMany 关系。

当您对同一实体有多个引用时(例如 TransitionOptions 属于某些 CurrentStep 并指向某些 NextStep),请使用 FluentApi 或使用 [=18= 注释], 而不是 ForeignKeyAnnotation.

一般来说,我建议您使用更流畅的 api 配置和更少的基于注释的配置 - 这样更容易维护。

根据您的问题,这是一些示例代码。

型号类

public class Story
{
    public int Id { get; set; }

    public List<Step>? Steps { get; set; }

    public List<Variable>? Variables { get; set; }
}

public class Variable
{
    public int Id { get; set; }

    public int StoryId { get; set; }

    public string? Value { get; set; }

    public Story? Story { get; set; }

    public List<Change>? Changes { get; set; }
}

public class Step
{
    public int Id { get; set; }

    public int StoryId { get; set; }

    public Story? Story { get; set; }

    public List<TransitionOption>? TransitionOptions { get; set; }
}

public class TransitionOption
{
    public int Id { get; set; }

    public int StepId { get; set; }

    public int? NextStepId { get; set; }

    public Step? CurrentStep { get; set; }

    public Step? NextStep { get; set; }
    
    public List<Change>? Changes { get; set; }
}

public class Change
{
    // If combination of TransitionOptionId and VariableId is unique,
    // you can use them as composite key instead
    public int Id { get; set; }

    public int TransitionOptionId { get; set; }

    public int VariableId { get; set; }

    public Variable? Variable { get; set; }

    public TransitionOption? TransitionOption { get; set; }
}

实体配置类

internal class StoryConfiguration : IEntityTypeConfiguration<Story>
{
    public void Configure(EntityTypeBuilder<Story> builder)
    {
        builder.HasMany(x => x.Variables)
            .WithOne(x => x.Story)
            .HasForeignKey(x => x.StoryId)
            .OnDelete(DeleteBehavior.Cascade);

        builder.HasMany(x => x.Steps)
            .WithOne(x => x.Story)
            .HasForeignKey(x => x.StoryId)
            .OnDelete(DeleteBehavior.Cascade);
    }
}
internal class VariableConfiguration : IEntityTypeConfiguration<Variable>
{
    public void Configure(EntityTypeBuilder<Variable> builder)
    {
        builder.HasMany(x => x.Changes)
            .WithOne(x => x.Variable)
            .HasForeignKey(x => x.VariableId)
            .OnDelete(DeleteBehavior.Cascade);
    }
}
internal class StepConfiguration : IEntityTypeConfiguration<Step>
{
    public void Configure(EntityTypeBuilder<Step> builder)
    {
        builder.HasMany(x => x.TransitionOptions)
            .WithOne(x => x.CurrentStep)
            .HasForeignKey(x => x.StepId)
            .OnDelete(DeleteBehavior.Cascade);

        // No navigation property in Step for TransitionOptions pointing
        // to this as NextStep.
        // It would also be possible to define such a property and mention it
        // in HasMany
        builder.HasMany<TransitionOption>()
            .WithOne(x => x.NextStep)
            .HasForeignKey(x => x.NextStepId)
            // If next step is deleted, only disconnect referencing TransitionOptions
            .OnDelete(DeleteBehavior.SetNull);
    }
}
internal class TransitionOptionConfiguration : IEntityTypeConfiguration<TransitionOption>
{
    public void Configure(EntityTypeBuilder<TransitionOption> builder)
    {
        builder.HasMany(x => x.Changes)
            .WithOne(x => x.TransitionOption)
            .HasForeignKey(x => x.TransitionOptionId)
            .OnDelete(DeleteBehavior.Cascade);
    }
}

应用 OnModelCreating 方法中的配置。

用 1 创建整个故事结构的示例方法 SaveChanges

public async Task InitDb(MyDatabase db)
{
    var variable1 = new Variable
    {
        Value = "1st Value"
    };
    db.Stories?.Add(new Story
    {
        Variables = new List<Variable>
        {
            variable1
        },
        Steps = new List<Step>
        {
            new Step
            {
                TransitionOptions = new List<TransitionOption>
                {
                    new TransitionOption
                    {
                        Changes = new List<Change>
                        {
                            new Change
                            {
                                Variable=variable1
                            }
                        }
                    }
                }
            }
        }
    });
    await db.SaveChangesAsync();
}

希望这个例子展示了您描述的结构所需的一切。