0

I have an issue when adding entities with multiple one-to-one relationship with the same table in Entity Framework Core. Based on this question I have the following things:

public class Article
{
    public int Id { get; set; }
    public int? PreviousId { get; set; }
    public Article Previous { get; set; }
    public int? NextId { get; set; }
    public Article Next { get; set; }
}

In the OnModelCreating of the DbContext as I have:

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

    modelBuilder.HasDefaultSchema("ab");
    modelBuilder.ApplyConfiguration(new ArticleEntityTypeConfiguration());
}

public class ArticleEntityTypeConfiguration : IEntityTypeConfiguration<Article>
{
    public void Configure(EntityTypeBuilder<Article> builder)
    {
        builder.ToTable("Articles");

        builder.HasKey(table => table.Id);
        builder.Property(table => table.Id).ValueGeneratedOnAdd();

        builder.HasOne(table => table.Previous).WithOne().HasForeignKey<Article>(table => table.PreviousId);
        builder.HasOne(table => table.Next).WithOne().HasForeignKey<Article>(table => table.NextId);
    }
}

And when adding a new article I get a stack overflow error and the app crashes. I add an article with the following code:

public Task AddNewArticleAsync(Article article)
{
    var newArticle = new Article();

    article.Next = newArticle;
    newArticle.Previous = article;

    await _dbContext.Programs.AddAsync(article);
}

Do you know how can I avoid that error? Thanks!

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
joacoleza
  • 775
  • 1
  • 9
  • 26
  • 1
    Not sure if this helps, but did you try [InversePropertyAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.schema.inversepropertyattribute?redirectedfrom=MSDN&view=netcore-3.1)? – Kit Mar 09 '20 at 21:30

2 Answers2

0

if you add a new article,the PreviousId/NextId should be null or an existing Article.Id.If your Artice.Id is increasing, the previousId is the max id of the existing records.

Below is a demo to save new articles:

[HttpPost]
public async Task<IActionResult> Create(Article article)
{
    if (ModelState.IsValid)
    {
        int minArticleId = _context.Articles.Max(a => a.Id);
        var previous = await _context.Articles.Where(s => s.Id == minArticleId).FirstOrDefaultAsync();
        article.PreviousId = minArticleId;
        _context.Add(article);
        await _context.SaveChangesAsync();

        previous.NextId = article.Id;          
        _context.Update(previous);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }

    return View(article);
}
Ryan
  • 19,118
  • 10
  • 37
  • 53
  • Actually the navigation is not like that. There are kind of threads so the PreviousId is not necessary the min id in the database. That's why the previous article, or at least the previous article id, must be an input of the operation. – joacoleza Mar 10 '20 at 12:34
0

So I continue investigating about this and found this answer which helped me to fix the issue. The trick was to change the OnModelCreating of the DbContext to:

public void Configure(EntityTypeBuilder<Article> builder)
{
    builder.ToTable("Articles");

    builder.HasKey(table => table.Id);
    builder.Property(table => table.Id).ValueGeneratedOnAdd();

    builder
        .HasOne(table => table.Previous)
        .WithOne() // <-- one of this must be empty
        .HasForeignKey<Article>(table => table.PreviousId)
        .OnDelete(DeleteBehavior.Restrict);

    builder
        .HasOne(table => table.Next)
        .WithOne(table => table.Previous)
        .HasForeignKey<Article>(table => table.NextId);
}
joacoleza
  • 775
  • 1
  • 9
  • 26