简体   繁体   English

EF7(Core)中同一个表的多个关系

[英]Multiple relationships to the same table in EF7(Core)

I have such models 我有这样的模特

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);

Which worked perfectly until I upgraded from EF RC1 to RTM . 在从EF RC1升级到RTM之前,这种方法非常有效。 But now it throws: 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. 但现在它抛出: 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. 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.

Is there any workaround for this problem without just deleting Variants property from the Question model? 如果没有从Question模型中删除Variants属性,是否有解决此问题的方法?

In case someone will run into this question. 万一有人会遇到这个问题。 Here is more elegant solution 这是更优雅的解决方案

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();

That was a bug/undesired behaviour in RC1 and it has been fixed. 这是RC1中的一个错误/不受欢迎的行为,它已得到修复。

You should create another property, say SecondQuestion for the other relationship. 你应该创建另一个属性,比如说另一个关系的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; }
}

Your DbContext: 你的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);
}

How to use it: 如何使用它:

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();
}

The two examples given already got me part of the way there, but I wanted a collection and a single item of the same object type and therefore the same table on my model like in the original question. 给出的两个例子已经让我成为了那里的一部分,但是我想要一个集合和一个具有相同对象类型的项目,因此我的模型上的表格与原始问题中的相同。 I've tried to provide a simple example of this below that works for .NET Core 2.2: 我试图提供一个适用于.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);
    }
}

The key to avoiding the Navigation properties can only participate in a single relationship. 避免Navigation properties can only participate in a single relationship.的关键Navigation properties can only participate in a single relationship. error is to configure the navigation property back to the parent table only once. 错误是仅将导航属性配置回父表一次。 We configure this on for the ChildModels collection on the ParentTable using .WithOne(t => t.ParentModel) . 我们使用.WithOne(t => t.ParentModel)为ParentTable上的ChildModels集合配置此项。 We then don't bother configuring the other side of the relationship for the subsequent relationships by calling .WithOne() empty, because if we did configure it again (eg .WithOne(t => t.ParentModel) ) it would error. 然后,我们不会通过调用.WithOne()为后续关系配置关系的另一面,因为如果我们再次配置它(例如.WithOne(t => t.ParentModel) ),它将会出错。

Also the virtual modifiers on the navigation properties are to allow for lazy loading . 此外,导航属性上的virtual修饰符允许延迟加载

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM