简体   繁体   中英

Entity Framework Core1.1 - Bulk Insert or Update - InvalidOperationException

I'm running into a problem with inserting OR updating roughly 950 entities.

var coins = JsonConvert.DeserializeObject<List<Currency>>(json);
var sw = new Stopwatch();
sw.Start();
using (var ctx = CryptoContext.Get)
{
    var existingCoins = ctx.Coins.ToList();
    foreach (var coin in coins)
    {
        var existing = existingCoins.FirstOrDefault(c => c.CMC_Id == coin.CMC_Id);
        if (existing != null)
        {
            ctx.Entry<Currency>(coin).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
        } else
        {
            ctx.Entry<Currency>(coin).State = Microsoft.EntityFrameworkCore.EntityState.Added;
        }

    }
    ctx.SaveChanges();
    var el = sw.ElapsedMilliseconds;
}

The code runs in the background of my netcoreapp1.1, with SQLite, and retrieves a list of currencies. This is done every 5 minutes with FluentScheduler. Because they're not entirely large objects I do all comparisons in memory, and try to add or update each one. My entity has a database-given ID of Id, and the API I'm retrieving from guarantees that CMC_Id is unique.

The initial insertion works fine. I get an error on the second "Update". I believe what's happening is that I'm tracking multiple entities as modified that each have an Id of 0

I was trying to follow this: https://msdn.microsoft.com/en-us/library/jj592676(v=vs.113).aspx

And the error I get is: "The instance of entity type 'Currency' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (ie if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context."

I am unsure how to proceed with updating each row.

Issue here multiple entities with same key are asked to be tracked.

When you set EntityEntry.State to something then EF Core will start tracking the entity in the specific state. Since in your code, you are querying the database to find out existing entity, EF Core will start tracking the entity with given key therefore it throws above exception while setting the EntityEntry.State because there is already entity with same key being tracked.

More precisely you are trying to AddOrUpdate . There are multiple ways to achieve the behavior. Which one is the best depends on if you are adding one entity without relation or a complex graph.

The simplest method would be to just check existence instead of tracking the entity from database. Options for that would be to use AsNoTracking in your query so that EF does not start tracking it. Even more optimized way would be to just get count from database. If you are querying on PK property then count will be either 0 (non-existent) or 1 (existing entity). If it does not exist then you call Add otherwise Update .

var updatedBlog = new Blog { Id = 1, Title = "Updated" };
var exist = db.Blogs.Count(b => b.Id == updatedBlog.Id) != 0;
if (exist)
{
    db.Update(updatedBlog);
}
else
{
    db.Add(updatedBlog);
}
db.SaveChanges();

Since Add or Update methods start tracking whole graph, if your graph is in one consistent state, (all entities are new or all are being modified) then it would work just fine.

If your graph is somewhat inconsistent that state of each node in graph can be different (eg Updating a blog but it has new posts). Then you should use EntityEntry.State on individual entity. This makes sure that state is applied to only given entity and no other related entity in graph. Though you need to do above kind of check for each node in the graph. Another alternative is to use Attach method to attach whole graph in Unchanged state and then set state for individual node.

If you are having auto-generated Key values then probably you will have PK value set only when it is update else it would be CLR default. For single entity without relations, you can make that check yourself instead of querying database like above code and make decision. For graphs, you can use

db.ChangeTracker.TrackGraph(updatedBlog, n => n.Entry.State = n.Entry.IsKeySet ? EntityState.Modified : EntityState.Added);

This will set state of each node based on PK value being set or not.

Hope this helps :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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