1

I load an object/entity from the database:

var db = new DbContext();

//...

var item = db.Items.First();

Then I want to perform two asynchronous tasks, that when returned, update data on the item:

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

The two functions will have some code that gets, sets & saves a (different) property on the item, like so:

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

However, as they are running asynchonously, I'm getting the error: A second operation started on this context before a previous asynchronous operation completed.

I tried newing up a dbContext in the Functions, but then I get the error An entity object cannot be referenced by multiple instances of IEntityChangeTracker.

I understand why I'm getting both of these errors, my question is: what coding pattern would best resolve 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. I also want that all that work to be self-contained.

The properties on the item that Function1 and Function2 update are discrete. I'm using this library to ensure that the save doesn't overwrite all the fields.

6
  • You can move the code db.Entry(item).State = EntityState.Modified; await db.SaveChangesAsync(); after the Task.whenall(). The job of those 2 functions is to update the properties of that object. Commented Jul 27, 2018 at 10:30
  • 2
    You can't share dbcontext between task... Create one for each task Commented Jul 27, 2018 at 10:32
  • 2
    I don't think it makes much sense to update the same database record in parallel tasks. What do you think to gain by that? Commented Jul 27, 2018 at 10:32
  • 1
    Also, by marking the entire item as modified each task will update all of its properties and the task that happens to be the last wins, erasing all changes from the other tasks. Commented Jul 27, 2018 at 10:36
  • @GertArnold I updated the question to explain a bit more. Commented Jul 28, 2018 at 7:21

2 Answers 2

1

You can move the code

db.Entry(item).State = EntityState.Modified; 
await db.SaveChangesAsync();

after the Task.Whenall(). The job of those 2 functions is to update the properties of that object.

Sign up to request clarification or add additional context in comments.

3 Comments

What's the point? That's the same as not doing anything in parallel and calling await db.SaveChangesAsync() once at the end. Which, btw, is what the OP should have done in the first place
That's not obvious from the answer as it is now
That's so simple - not sure why I didn't see it! A small concern is that I want the values from the Api calls saved to the database ASAP, where this option would only save when both calls complete. I'll give this a try, though.
1

Don't let two threads perform operations on the same dbContext. A dbContext keeps track of the fetched items. If two threads are fetching and changing items on the same DbContext at the same time, it might lead to undesired results.

Furthermore: if you fetched an Item, just change the properties you want. You don't need to set the state to Modified. A dbContext can check whether it has the original value or not.

So create functions that create their own DbContext, Fetch the requested data, change the data, save the data and finally Dispose 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 similar Function2, or maybe the same Function1 with different parameters.

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

1 Comment

Thanks for your answer. My concern about this approach is that the code runs in a loop many times in a second. Optimising the code performance was important. So I didn't want to new up a dbContext / fetch data every time. I should have mentioned that up front.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.