Home > Software design >  How to remove child entities from parent collection on parent update in Entity Framework Core?
How to remove child entities from parent collection on parent update in Entity Framework Core?

Time:07-22

I have a following problem: when I try to update Question and provide a new keyword in Keywords property and the keyword isn't yet related with the question it is being properly added. Lets say there are two keywords mapped to the question. Then I send the update request with one keywords that is not related to the question yet. It results in Question being mapped to three keywords. When I send empty list of keywords, nothing changes. What I would like to happen is when I send one keyword - then it is just this one related to the question and rest is removed. How can I achieve the following ? Already I have tried adding "QuestionKeyword" segment inside Configure method for Keyword but it didn't change much. Also what happens is that keywords are being removed from the entity inside C# application, but when I add a dummy line of code with the same question being fetched from context again, then its question loads again after calling Include() - since they were never removed.

Consider the code below:

public class Keyword
{
        public string Name { get; set; }
        public List<Question> Questions { get; set; }
}

public class Question : AuditableEntity
{
        public int Id { get; set; }
        public string Content { get; set; }
        public string Answer { get; set; }
        public int Rating { get; set; } 
        public IList<Keyword> Keywords { get; set; } = new List<Keyword>(); 
}

This entities are configured in the following way:

public void Configure(EntityTypeBuilder<Keyword> builder)
{
        builder.Ignore(entity => entity.DomainEvents);
        builder.HasKey(entity => entity.Name);

        builder.HasMany(entity => entity.Questions)
            .WithMany(entity => entity.Keywords)
            .UsingEntity<Dictionary<string, object>>(
                       "QuestionKeyword",
                       j => j.HasOne<Question>().WithMany().OnDelete(DeleteBehavior.Cascade),
                       j => j.HasOne<Keyword>().WithMany().OnDelete(DeleteBehavior.Cascade));
 } 


public void Configure(EntityTypeBuilder<Question> builder)
{ 
        builder.HasKey(entity => entity.Id);
}

And this is handle method which updates the question. It was simplified so it just removes the question keywords for presentation purposes.

public async Task<Unit> Handle(UpdateQuestionCommand request, CancellationToken cancellationToken)
{
        Question question = await _context.Questions.Include(q => q.Keywords).AsNoTracking().SingleOrDefaultAsync(q => q.Id == request.Id, cancellationToken);

        if (question == null)
            throw new NotFoundException($"Id = {request.Id}");
        
        question.Keywords = null;
        _context.Questions.Update(question);
        
        question.DomainEvents.Add(new QuestionUpdatedEvent(question));

        await _context.SaveChangesAsync(cancellationToken);
        return Unit.Value;
} 

CodePudding user response:

You must clear all keywords of the question then add new keywords into your question like this:

Question question = await _context.Questions.Include(q => q.Keywords).SingleOrDefaultAsync(q => q.Id == request.Id, cancellationToken);

question.Keywords.Clear();

....

But notice that AsNoTracking() is removed!

Using AsNoTracking() means that fetched objects changes dont matter for you and EF will ignore changes!

  • Related