EF6: saving to same entity using dbContext in async tasks

async-await asynchronous c# entity-framework-6

Question

I bring up an entity or object from the database:

var db = new DbContext();

//...

var item = db.Items.First();

Then I want to run two asynchronous actions that, when finished, will change the item's data:

var task1 = Function1(db, item); 
var task2 = Function2(db, item); 
await Task.WhenAll(new Task[] { task1 , task2 });

The two methods will each include code that retrieves, modifies, and saves an item's (different) attribute as follows:

var orderId = await CallApi();
item.OrderId = orderId;
db.Entry(item).State = EntityState.Modified;
await db.SaveChangesAsync();

But I'm receiving the following problem since they're operating asyncronously:A second operation started on this context before a previous asynchronous operation completed .

I attempted to create a new dbContext in the Functions, but I keep receiving the error.An entity object cannot be referenced by multiple instances of IEntityChangeTracker .

I know why I'm getting both of these issues; what code style would be the most effective in fixing this?


EDIT Ok, so the above was a simplified example. In reality, there is a lot more work that goes on in the functions, so it would be difficult to move all that logic out of the function and into the calling method. Additionally, I want all of that work to stand alone.

The residences on theitem that Function1 and Function2 updates are spaced out. To make sure that the save doesn't overwrite every field, I'm using the these books code.

1
0
7/28/2018 7:20:45 AM

Accepted Answer

ZZZ_tmp
1
4/15/2019 3:17:17 PM

Popular Answer

Never allow two threads to work on the same dbContext. The things that were fetched are tracked by a dbContext. Unwanted outcomes may occur if two threads simultaneously retrieve and modify objects on the same DbContext.

Additionally: if you retrieved anItem just alter the desired properties. The state does not have to be modified. A dbContext can determine whether it still has the original value.

In order to do this, write functions that construct their own DbContext, get the needed data, alter the data, save the changed data, and then dispose of the DbContext.

async Task Function1(...)
{
    using (var dbcontext = new MyDbContext(...))
    {
        // Start Fetching the item that must be changed; don't await yet
        var taskFetchItemToChange = dbContext.Items
            .Where(...)
            .FirstOrDefaultAsync();

        // Start fetching the orderId that must be changed; don't await yet
        var taskFetchOrderId = this.CallApiAsync();

        // await until both item and orderId are fetched:
        await Task.WhenAll(new Task[] {taskFetchItemToChange, taskFetchOrderId});

        var fetchedItemToChange = taskFetchItemToChange.Result;
        var fetchedOrderId = taskFetchOrderId.Result;
        fetchedItemToChange.OrderId = fetchedOrderId;
        // or do this in one big unreadable unmaintainable untestable step

        await dbContext.SaveChangesAsync();
    }
}

You will have a Function2 that is comparable to Function1, or maybe Function1 with different parameters.

var taskFunction1 = Function1();
var taskFunction2 = Function2();
await Task.WhenAll( new Task[] {taskFunction1, taskFunction2});


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow