【问题标题】:Multiple relationships to the same table in EF7(Core)EF7(核心)中同一个表的多个关系
【发布时间】:2016-11-26 00:25:52
【问题描述】:

我有这样的模型

public class Question
{
    public string Id { get; set; } = Guid.NewGuid().ToString();

    public Answer Answer { get; set; }
    public List<Variant> Variants { get; set; }

    public string CorrectVariantId { get; set; }
    public Variant CorrectVariant { get; set; }
}

public class Variant
{
    public string Id { get; set; } = Guid.NewGuid().ToString();

    public string QuestionId { get; set; }
    public Question Question { get; set; }
}

// mapping

modelBuilder.Entity<Question>()
    .HasOne(q => q.CorrectVariant)
    .WithOne(v => v.Question)
    .HasForeignKey<Question>(q => q.CorrectVariantId);

modelBuilder.Entity<Variant>()
    .HasOne(v => v.Question)
    .WithMany(a => a.Variants)
    .OnDelete(DeleteBehavior.Cascade);

在我从 EF RC1 升级到 RTM 之前效果很好。但现在它抛出: System.InvalidOperationException: Cannot create a relationship between 'Question.Variants' and 'Variant.Question', because there already is a relationship between 'Question.CorrectVariant' and 'Variant.Question'. Navigation properties can only participate in a single relationship.

在不从Question 模型中删除Variants 属性的情况下,是否有任何解决此问题的方法?

【问题讨论】:

  • RTM 你的意思是 Core 1.0.0 吗?
  • @BassamAlugili 是的

标签: c# .net entity-framework orm


【解决方案1】:

给出的两个示例已经让我了解了其中的一部分,但我想要一个集合和一个具有相同对象类型的单个项目,因此我的模型上的同一个表就像原始问题中一样。我试图在下面提供一个适用于 .NET Core 2.2 的简单示例:

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

    // Id for single instance navigation property
    public int? ChildModelId { get; set; }

    // Single instance navigation property to ChildTable, identified by ChildModelId property as foreign key
    public virtual ChildModel ChildModel { get; set; }

    // Collection navigation property to ChildTable with identified by ParentId property
    public virtual ICollection<ChildModel> ChildModels { get; set; }
}

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

    // Id for ParentModel property back to ParentTable
    public int ParentId { get; set; }

    // Single instance navigation property to ParentTable, identified by ParentId property as foreign key
    public virtual ParentModel ParentModel { get; set; }
}

public class ApplicationDbContext : IdentityDbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {   
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<ParentModel>()
            .ToTable("ParentTable");

        // Configure collection of ChildModels (ParentTable to ChildTable/one-to-many relationship)
        builder.Entity<ParentModel>()
            .HasMany(t => t.ChildModels)
            .WithOne(t => t.ParentModel)
            .HasForeignKey(t => t.ParentId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Cascade);

        builder.Entity<ChildModel>()
            .ToTable("ChildTable");

        // Configure single ChildModel navigation property on ParentModel (one-to-one relationship)
        builder.Entity<ParentModel>()
            .HasOne(t => t.ChildModel)
            .WithOne()
            .HasForeignKey(typeof(ParentModel), nameof(ParentModel.ChildModelId))
            .IsRequired(false)
            .OnDelete(DeleteBehavior.Restrict);
    }
}

避免Navigation properties can only participate in a single relationship. 错误的关键是仅将导航属性配置回父表一次。我们使用.WithOne(t =&gt; t.ParentModel) 为ParentTable 上的ChildModels 集合配置此项。然后我们就不用为后续的关系配置关系的另一端了,调用.WithOne()为空,因为如果我们再次配置它(例如.WithOne(t =&gt; t.ParentModel))它会出错。

导航属性上的virtual 修饰符也允许lazy loading

【讨论】:

  • 宾果游戏。谢谢。
  • 非常感谢。 .WithOne() 和明确的 HasForeignKey 是我案例的关键。
  • 我发现这个解决方案会导致 EF 不加载省略关系上的导航属性。即子模型不会填充 ParentModel 属性
  • @jflood.net 您是否忘记在查询中包含对 .Include 的调用?例如var childWithParent = context.ChildModel.Include(x =&gt; x.ParentModel).First();
【解决方案2】:

以防有人遇到这个问题。 这是更优雅的解决方案

public class Question
{
    public Guid Id { get; private set; }
    public IReadOnlyList<Variant> Variants { get; private set; }
    public Guid CorrectVariantId { get; private set; }
    public Guid? AnsweredVariantId { get; private set; }    
    public bool IsAnswerCorrect => CorrectVariantId == AnsweredVariantId;
    public bool IsAnswered => AnsweredVariantId != null;
}

public class Variant
{
    public Guid Id { get; private set; }
    public Guid QuestionId { get; private set; }
    public string HiddenUserLogin { get; private set; }
    public User HiddenUser { get; private set; }
}

// mapping
mb.Entity<Question>()
    .HasMany(q => q.Variants)
    .WithOne()
    .HasForeignKey(nameof(Variant.QuestionId))
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);

mb.Entity<Question>()
    .HasOne(typeof(Variant))
    .WithOne()
    .HasForeignKey<Question>(nameof(Question.AnsweredVariantId))
    .IsRequired(false) 
    .OnDelete(DeleteBehavior.Restrict);

// EF creates Unique Index for nullable fields
mb.Entity<Question>()
    .HasIndex(q => q.AnsweredVariantId)
    .IsUnique(false);

// create index instead of FK hence the cyclic dependency between Question and Variant
mb.Entity<Question>()
    .HasIndex(q => q.CorrectVariantId)
    .IsUnique();

【讨论】:

  • 您提到使用 nameof 作为属性名称,但后来没有这样做。这将是一个不错的编辑。
【解决方案3】:

这是 RC1 中的错误/不良行为,现已修复。

您应该为另一个关系创建另一个属性,例如 SecondQuestion。

public class Question
{
  public string Id { get; set; } = Guid.NewGuid().ToString();
  public List<Variant> Variants { get; set; }

  public string CorrectVariantId { get; set; }
  public Variant CorrectVariant { get; set; }
}

public class Variant
{
  public string Id { get; set; } = Guid.NewGuid().ToString();

  public string QuestionId { get; set; }
  public Question Question { get; set; }

  public Question SecondQuestion { get; set; }
}

你的 DbContext:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  modelBuilder.Entity<Question>()
    .HasOne(q => q.CorrectVariant)
    .WithOne(v => v.SecondQuestion)
    .HasForeignKey<Question>(q => q.CorrectVariantId);

  modelBuilder.Entity<Variant>()
      .HasOne(v => v.Question)
      .WithMany(a => a.Variants).HasForeignKey(x => x.QuestionId).OnDelete(DeleteBehavior.SetNull);

  base.OnModelCreating(modelBuilder);
}

使用方法:

using (var myDb = new MyDbContext())
{
  var variantFirst = new Variant();
  var variantSecond = new Variant();

  var question = new Question();
  variantFirst.Question = question;

  variantSecond.SecondQuestion = question;

  myDb.Variants.Add(variantFirst);
  myDb.Variants.Add(variantSecond);

  myDb.SaveChanges();
}

【讨论】:

  • 不喜欢这个解决方案,也看不到其他的。谢谢!
  • 有些人的“不良行为”显然是其他人的“不良行为”。 :|
  • 还是这样吗?我发现这太可怕了,必须定义两个始终指向同一个对象的属性,即使因为如果您将 SecondQuestion 显式设置为与 Question 不同的值,这可能会导致崩溃的场景
  • @FabioAngela 您可以传递字符串属性名称(使用nameof 运算符)。像这样pastebin.com/RsE4dzeR
  • 有什么更新吗?正如@FabioAngela 所说,我不喜欢两次“相同”属性的定义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-21
  • 2017-12-11
相关资源
最近更新 更多