DbUpdateConcurrencyException Correct Handling
This is from the docs about the lifetime of DBContext:
When working with Windows Presentation Foundation (WPF) or Windows Forms, use a context instance for each form. This allows you to use the change tracking functionality that the context provides.
I read that the lifetime DBContext
should be as short as possible, but obviously in the case WinForms
(which I am using) this rule does not apply and something else is recommended.
Current setting
Also my application follows a pattern MVC
and in my controller I have initialized DBContext
. Now that the form is closed, by the way, the controller instance gets deleted as well, and this is where the context dies. Also I use Database First approach if that matters.
Now that the context is always open (for each form), it seems like it has its own conveniences (you can easily track changes in the context), but for example I find myself (as a newbie at EF
) in some doubt ... Look at this situations:
I have a IList
user entity that is initialized when the form is created. And I am running two (or even more) instances of my application because it is designed to run in a multi-user environment. Now, suppose user A and user B are loading the same entry (user management screen, so they are both administrators) ... Now that everything goes like this:
the user A
makes changes (updates) to some records, the user B
makes changes (updates) to the same record and saves it to the database. User A
tries to keep the exception DbUpdateConcurrencyException
...
Now this is okay, I know the term "optimistic concurrency" and I say that both users A
and B
updated the same row (or the same column), I would handle an exception like this (correct me if I am missing something ):
catch (DbUpdateConcurrencyException ex)
{
ex.Entries.Single().Reload();
//Here I reload UI with a new changes, and ask a user to try again
}
Now it seems to work ... Since my context opens while my form is alive, when I do Reload () on ex.Entries.Single (), a specific user from the list (remember I mentioned the list of users before) is updated from with the actual database values, so when I reload the view, the user gets the current state.
Actual problem
So far so good ... But, for example, in this situation, where:
user A
deletes line
user B
tries to update and save changes in the same (already deleted row)
I'm facing a lot of problems and I don't know what would be the correct way to handle this.
I think I should update the list of users, but I don't know how to do this without throwing away the current context. So that would be a question ... How to reload the list of users from the database without having context in this block of code:
catch (DbUpdateConcurrencyException ex)
{
// Reload the user list with navigation properties fully initialized (note that user list is what would context.korisnik.ToList() would return)
// Reload the UI
}
How do I properly handle optimistic concurrency errors in situations like this?
source to share
Here is my iteration logic to handle the DbUpdateConcurrencyException. The trick is to clone the CurrentValues, call Reload, then set the CurrentValues ββwith the original cloned values.
bool saved = false;
do
{
try
{
await ctx.SaveChangesAsync();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry?.Entity is Person)
{
//clone to preserve values, Reload below will reset props
var proposedValues = entry.CurrentValues.Clone();
await entry.ReloadAsync();
entry.CurrentValues.SetValues(proposedValues);
}
}
}
} while (!saved);
source to share