Zpracování konfliktů souběžnosti (EF6)
Optimistická souběžnost zahrnuje optimistický pokus o uložení entity do databáze v naději, že se od načtení entity nezměnila data, která se nezměnila. Pokud se zjistí, že se data změnila, vyvolá se výjimka a před dalším pokusem o uložení je nutné konflikt vyřešit. Toto téma popisuje, jak tyto výjimky zpracovat v Entity Frameworku. Techniky uvedené v tomto tématu jsou rovnocenné pro modely vytvořené pomocí Code First a EF Designeru.
Tento příspěvek není vhodným místem pro úplnou diskuzi o optimistické souběžnosti. Níže uvedené části předpokládají určité znalosti řešení souběžnosti a ukazují vzory běžných úloh.
Mnoho z těchto vzorů využívá témata probíraná v části Práce s hodnotami vlastností.
Řešení problémů souběžnosti při použití nezávislých přidružení (kde cizí klíč není mapován na vlastnost ve vaší entitě) je mnohem obtížnější než při použití přidružení cizího klíče. Proto pokud budete ve své aplikaci provádět řešení souběžnosti, doporučujeme vždy mapovat cizí klíče do entit. Všechny následující příklady předpokládají, že používáte přidružení cizího klíče.
Funkce SaveChanges vyvolá výjimku DbUpdateConcurrencyException při zjištění optimistické výjimky souběžnosti při pokusu o uložení entity, která používá přidružení cizího klíče.
Řešení výjimek optimistické souběžnosti s opětovným načtením (databáze wins)
Metodu Reload lze použít k přepsání aktuálních hodnot entity hodnotami, které jsou nyní v databázi. Entita je pak obvykle vrácena uživateli v určitém formuláři a musí se pokusit provést změny znovu a znovu uložit. Příklad:
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Update the values of the entity that failed to save from the store
ex.Entries.Single().Reload();
}
} while (saveFailed);
}
Dobrým způsobem, jak simulovat výjimku souběžnosti, je nastavit zarážku ve volání SaveChanges a pak upravit entitu, která se ukládá do databáze, pomocí jiného nástroje, jako je SQL Server Management Studio. Můžete také vložit řádek před SaveChanges aktualizovat databázi přímo pomocí SqlCommand. Příklad:
context.Database.SqlCommand(
"UPDATE dbo.Blogs SET Name = 'Another Name' WHERE BlogId = 1");
Metoda Entries v DbUpdateConcurrencyException vrátí DbEntityEntry instance pro entity, které se nepodařilo aktualizovat. (Tato vlastnost v současné době vždy vrací jednu hodnotu pro problémy souběžnosti. Může vrátit více hodnot pro výjimky obecné aktualizace.) Alternativou pro některé situace může být získání položek pro všechny entity, které mohou být potřeba znovu načíst z databáze a volat znovu načtení pro každou z těchto entit.
Řešení výjimek optimistické souběžnosti při vítězství klienta
Výše uvedený příklad, který používá opětovné načtení, se někdy označuje jako databáze wins nebo wins, protože hodnoty v entitě jsou přepsány hodnotami z databáze. Někdy můžete chtít provést opačnou akci a přepsat hodnoty v databázi hodnotami, které jsou aktuálně v entitě. To se někdy označuje jako výhra klienta a dá se provést získáním aktuálních hodnot databáze a jejich nastavením jako původních hodnot entity. (Viz Práce s hodnotami vlastností pro informace o aktuálních a původních hodnotách.) Příklad:
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Update original values from the database
var entry = ex.Entries.Single();
entry.OriginalValues.SetValues(entry.GetDatabaseValues());
}
} while (saveFailed);
}
Vlastní řešení výjimek optimistické souběžnosti
Někdy můžete chtít zkombinovat hodnoty aktuálně v databázi s hodnotami aktuálně v entitě. Obvykle to vyžaduje určitou vlastní logiku nebo interakci uživatele. Můžete například uživateli prezentovat formulář obsahující aktuální hodnoty, hodnoty v databázi a výchozí sadu přeložených hodnot. Uživatel by pak podle potřeby upravoval vyřešené hodnoty a byly by tyto vyřešené hodnoty, které se uloží do databáze. To lze provést pomocí DbPropertyValues objekty vrácené z CurrentValues a GetDatabaseValues u položky entity. Příklad:
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Get the current entity values and the values in the database
var entry = ex.Entries.Single();
var currentValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
// Choose an initial set of resolved values. In this case we
// make the default be the values currently in the database.
var resolvedValues = databaseValues.Clone();
// Have the user choose what the resolved values should be
HaveUserResolveConcurrency(currentValues, databaseValues, resolvedValues);
// Update the original values with the database values and
// the current values with whatever the user choose.
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(resolvedValues);
}
} while (saveFailed);
}
public void HaveUserResolveConcurrency(DbPropertyValues currentValues,
DbPropertyValues databaseValues,
DbPropertyValues resolvedValues)
{
// Show the current, database, and resolved values to the user and have
// them edit the resolved values to get the correct resolution.
}
Vlastní řešení výjimek optimistické souběžnosti pomocí objektů
Výše uvedený kód používá instance DbPropertyValues pro předávání aktuálních, databázových a vyřešených hodnot. Někdy může být pro tento typ entity jednodušší používat instance vašeho typu entity. To lze provést pomocí ToObject a SetValues metody DbPropertyValues. Příklad:
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Get the current entity values and the values in the database
// as instances of the entity type
var entry = ex.Entries.Single();
var databaseValues = entry.GetDatabaseValues();
var databaseValuesAsBlog = (Blog)databaseValues.ToObject();
// Choose an initial set of resolved values. In this case we
// make the default be the values currently in the database.
var resolvedValuesAsBlog = (Blog)databaseValues.ToObject();
// Have the user choose what the resolved values should be
HaveUserResolveConcurrency((Blog)entry.Entity,
databaseValuesAsBlog,
resolvedValuesAsBlog);
// Update the original values with the database values and
// the current values with whatever the user choose.
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(resolvedValuesAsBlog);
}
} while (saveFailed);
}
public void HaveUserResolveConcurrency(Blog entity,
Blog databaseValues,
Blog resolvedValues)
{
// Show the current, database, and resolved values to the user and have
// them update the resolved values to get the correct resolution.
}