question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to update an entity with collection property that has either new, updated or deleted entities?

See original GitHub issue

I have the following models:

public class SubscriptionLine
{
	public Guid Id { get; set; }
}

public class Subscription 
{
	public Guid Id { get; set; }
	
	public IEnumerable<SubscriptionLine> SubscriptionLines { get; set; }
}

I’m syncing these models to a database from an external source where a subscription comes in with a bunch of subscription lines. All the Ids are generated by the external source. Therefore:

  1. I know whether the subscription is in the database or not. If it is not, I add it to the database using dbContext.Add, which will also add all the subscription lines. That’s just fine.
  2. If the subscription is already known to the database (i.e. it has been changed in the external source), I will have to update it, including the subscription lines. Every subscription line either
    • Is new, in which it has to be inserted
    • Is changed, in which case it has to be updated
    • Is no longer part of the SubscriptionLines IEnumerable, in which case it has to be removed from the database.

I’m not sure how to accomplish this, as I read in the documentation that a call to Update sets the state of the Subscription entity and all the SubscriptionLines to Modified. However, this means that entities are being updated that are not yet part of the database, which will fail of course.

When the subscription entity is known to the database, I can of course remove it, which will remove all the subscription lines, and then reinsert it. However, this looks like an overkill strategy. I read that updating a collection i.e. clearing it and than adding the new items does not work.

I’m asking this here because I think this is a use case that makes sense to be documented in the official documentation.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:11 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
leonarddercommented, Nov 29, 2021

Thanks for pointing me at these docs, I must have missed them.

Code is now as follows:

    public async Task<Int32> CreateOrUpdateEntitiesAsync(List<TModel> entities, CancellationToken cancellationToken)
    {
        TId[]? existingIds = await this.GetExistingIdsAsync(entities, cancellationToken);
        foreach (TModel? entity in entities)
        {
            TId? idValue = this.ModelInfo.IdentifierValue<TModel, TId>(entity);
            if (existingIds.Contains(idValue))
            {
                EntityEntry<TModel> entry = this._dbContext.Update(entity);
                foreach (var collection in entry.Collections)
                {
                    if (collection.CurrentValue is null)
                    {
                        continue;
                    }
                    await collection.LoadAsync(cancellationToken);
                    IEnumerable<EntityEntry> subEntries = collection.CurrentValue.Cast<Object>().Select(e => this._dbContext.Entry(e));
                    foreach (var subEntry in subEntries)
                    {
                        if (subEntry.State == EntityState.Unchanged)
                        {
                            // This is a deleted sub entry.
                            subEntry.State = EntityState.Deleted;
                        }
                        else if ((await subEntry.GetDatabaseValuesAsync(cancellationToken)) is null)
                        {
                            subEntry.State = EntityState.Added;
                        }
                    }
                }
            }
            else
            {
                this._dbContext.Add(entity);
            }
        }

LoadAsync turned out to be very helpful to filter out the entities to delete. There’s one major disadvantage to the current code, namely that I need to call subEntry.GetDatabaseValuesAsync to find out whether the entity has to be added or modified, even though I should already have this info in memory somehow by calling LoadAsync on the collection. @ajcvickers May be you have a suggestion to work around that?

1reaction
ajcvickerscommented, Nov 26, 2021

@leonardder There is no easy answer to this. EF needs to know whether each entity is new or not, but if the entities come to EF with pre-generated keys, then there is no way for EF to automatically distinguish new entities from existing ones. Further, there is no way for EF to know if an entity has been removed from a collection as opposed to never being there in the first place. This is discussed in Explicitly Tracking Entities in the documentation.

In general, the ways to deal with this are:

  • Query for the existing graph and use it as the source of truth for what is currently in the databases, adding/attaching/removing as needed.
  • Keep track of new/deleted/modified entities in the external source, and send this information back to EF to be used to add/attach/remove as appropriate.

Also, consider voting to Turn-key Disconnected Entities Solution.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Disconnected Entities - EF Core
One way to deal with this is to use "soft deletes" such that the entity is marked as deleted rather than actually being...
Read more >
Modifying data via the DbContext
The approach that you adopt to update/modify entities depends on whether the context is currently tracking the entity being modified or not.
Read more >
How to add/update child entities when updating a parent ...
One thing to add: Within the foreach of update and insert children, you can't do existingParent.Children.Add(newChild) because then the ...
Read more >
Modifying Data with Entity Famework Core
In this article, we are going to talk about modifying data with EF Core by using differnet approaches and different actions.
Read more >
Update Data in Disconnected Scenario in Entity ...
UpdateRange method to attach a collection or array of entities to the DbContext and set their EntityState to Modified in one go. var...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found