Udostępnij za pośrednictwem


Obsługa konfliktów współbieżności (EF6)

Optymistyczna współbieżność polega na optymistycznej próbie zapisania jednostki w bazie danych w nadziei, że dane, które nie uległy zmianie od czasu załadowania jednostki. Jeśli okaże się, że dane uległy zmianie, zostanie zgłoszony wyjątek i musisz rozwiązać konflikt przed podjęciem próby zapisania ponownie. W tym temacie opisano sposób obsługi takich wyjątków w programie Entity Framework. Techniki przedstawione w tym temacie dotyczą modeli utworzonych przy użyciu podejścia „najpierw kod” i narzędzia EF Designer.

Ten wpis nie jest odpowiednim miejscem na pełną dyskusję na temat optymistycznej współbieżności. W poniższych sekcjach przyjęto założenie, że pewne informacje na temat rozwiązywania współbieżności i przedstawiają wzorce typowych zadań.

Wiele z tych wzorców korzysta z tematów omówionych w temacie Praca z wartościami właściwości.

Rozwiązywanie problemów ze współbieżnością podczas korzystania z niezależnych skojarzeń (gdzie klucz obcy nie jest mapowany na właściwość w jednostce) jest znacznie trudniejsze niż w przypadku używania skojarzeń kluczy obcych. W związku z tym, jeśli zamierzasz wykonać rozwiązanie współbieżności w aplikacji, zaleca się, aby zawsze mapować klucze obce na jednostki. We wszystkich poniższych przykładach przyjęto założenie, że używasz skojarzeń kluczy obcych.

Wyjątek DbUpdateConcurrencyException jest zgłaszany przez program SaveChanges, gdy podczas próby zapisania jednostki używającej skojarzeń klucza obcego zostanie wykryty optymistyczny wyjątek współbieżności.

Rozwiązywanie optymistycznych wyjątków współbieżności za pomocą funkcji Ponowne ładowanie (wins bazy danych)

Metoda Reload może służyć do zastępowania bieżących wartości jednostki wartościami teraz w bazie danych. Jednostka jest następnie zwykle zwracana użytkownikowi w jakiejś formie i musi spróbować ponownie wprowadzić zmiany i ponownie zapisać. Przykład:

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);
}

Dobrym sposobem symulowania wyjątku współbieżności jest ustawienie punktu przerwania w wywołaniu SaveChanges, a następnie zmodyfikowanie jednostki, która jest zapisywana w bazie danych przy użyciu innego narzędzia, takiego jak SQL Server Management Studio. Możesz również wstawić wiersz przed poleceniem SaveChanges, aby zaktualizować bazę danych bezpośrednio przy użyciu polecenia SqlCommand. Przykład:

context.Database.SqlCommand(
    "UPDATE dbo.Blogs SET Name = 'Another Name' WHERE BlogId = 1");

Metoda Entrys w dbUpdateConcurrencyException zwraca wystąpienia DbEntityEntry dla jednostek, których nie można zaktualizować. (Ta właściwość obecnie zawsze zwraca pojedynczą wartość problemów ze współbieżnością. Może zwracać wiele wartości dla ogólnych wyjątków aktualizacji). Alternatywą dla niektórych sytuacji może być pobranie wpisów dla wszystkich jednostek, które mogą wymagać ponownego załadowania z bazy danych i ponownego załadowania wywołania dla każdego z nich.

Rozwiązywanie optymistycznych wyjątków współbieżności, gdy klient wygrywa

W powyższym przykładzie używanym do ponownego ładowania są czasami nazywane zwycięstwami bazy danych lub magazynu, ponieważ wartości w jednostce są zastępowane przez wartości z bazy danych. Czasami możesz chcieć wykonać odwrotnie i zastąpić wartości w bazie danych wartościami aktualnie w jednostce. Jest to czasami nazywane zwycięstwem klienta i może być wykonywane przez pobranie bieżących wartości bazy danych i ustawienie ich jako oryginalnych wartości dla jednostki. (Zobacz Praca z wartościami właściwości w celu uzyskania informacji o bieżących i oryginalnych wartościach). Na przykład:

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);
}

Niestandardowe rozpoznawanie optymistycznych wyjątków współbieżności

Czasami warto połączyć wartości obecnie w bazie danych z wartościami aktualnie w jednostce. Zwykle wymaga to pewnej logiki niestandardowej lub interakcji użytkownika. Na przykład możesz przedstawić użytkownikowi formularz zawierający bieżące wartości, wartości w bazie danych i domyślny zestaw rozpoznanych wartości. Następnie użytkownik edytuje rozpoznane wartości w razie potrzeby i będzie to te rozpoznane wartości, które zostaną zapisane w bazie danych. Można to zrobić przy użyciu obiektów DbPropertyValues zwracanych z wartości CurrentValues i GetDatabaseValues we wpisie jednostki. Przykład:

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.
}

Niestandardowe rozpoznawanie optymistycznych wyjątków współbieżności przy użyciu obiektów

Powyższy kod używa wystąpień DbPropertyValues do przekazywania bieżących, baz danych i rozpoznanych wartości. Czasami może być łatwiej używać wystąpień typu jednostki dla tego typu. Można to zrobić przy użyciu metod ToObject i SetValues dbPropertyValues. Przykład:

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.
}