Freigeben über


Behandeln von Nebenläufigkeitskonflikten (EF6)

Optimistische Nebenläufigkeit erfordert optimistisches Versuchen, Ihre Entität in der Datenbank zu speichern, in der Hoffnung, dass sich die Daten dort seit dem Laden der Entität nicht geändert haben. Wenn sich herausstellt, dass sich die Daten geändert haben, wird eine Ausnahme ausgelöst, und Sie müssen den Konflikt lösen, bevor Sie das Speichern erneut versuchen. In diesem Thema wird erläutert, wie solche Ausnahmen in Entity Framework behandelt werden. Die in diesem Thema dargestellten Techniken gelten jeweils für Modelle, die mit Code First und dem EF-Designer erstellt wurden.

Dieser Beitrag ist nicht der richtige Ort für eine vollständige Diskussion über optimistische Nebenläufigkeit. Die folgenden Abschnitte setzen einige Kenntnisse über die Nebenläufigkeitsauflösung voraus und zeigen Muster für gängige Aufgaben.

Viele dieser Muster verwenden die Themen, die in Arbeiten mit Eigenschaftswertendiskutiert werden.

Das Beheben von Nebenläufigkeitsproblemen, wenn Sie unabhängige Zuordnungen verwenden (bei denen der Fremdschlüssel nicht einer Eigenschaft in Ihrer Entität zugeordnet ist) ist viel schwieriger, als wenn Sie Fremdschlüsselzuordnungen verwenden. Wenn Sie in Ihrer Anwendung die Nebenläufigkeitsauflösung ausführen werden, ist es daher ratsam, dass Sie in Ihren Entitäten immer Fremdschlüssel zuordnen. In allen folgenden Beispielen wird davon ausgegangen, dass Sie Fremdschlüsselzuordnungen verwenden.

Eine DbUpdateConcurrencyException wird von SaveChanges ausgelöst, wenn beim Versuch, eine Entität zu speichern, die Fremdschlüsselzuordnungen verwendet, eine optimistische Nebenläufigkeitsausnahme erkannt wird.

Auflösen optimistischer Nebenläufigkeitsausnahmen mit Reload (Datenbank gewinnt)

Die Reload-Methode kann verwendet werden, um die aktuellen Werte der Entität mit den Werten zu überschreiben, die sich jetzt in der Datenbank befinden. Die Entität wird dann in der Regel dem Benutzer in irgendeiner Form zurückbegeben, und er muss versuchen, seine Änderungen erneut vorzunehmen und zu speichern. Beispiel:

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

Eine gute Möglichkeit zum Simulieren einer Nebenläufigkeitsausnahme besteht darin, einen Haltepunkt für den SaveChanges-Aufruf festzulegen und dann eine Entität zu ändern, die in der Datenbank mithilfe eines anderen Tools wie z. B. SQL Server Management Studio gespeichert wird. Sie können auch vor SaveChanges eine Zeile einfügen, um die Datenbank direkt mithilfe von SqlCommand zu aktualisieren. Beispiel:

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

Die Entries-Methode für DbUpdateConcurrencyException gibt die DbEntityEntry-Instanzen für die Entitäten zurück, die nicht aktualisiert werden konnten. (Diese Eigenschaft gibt derzeit immer einen einzelnen Wert für Nebenläufigkeitsprobleme zurück. Es können mehrere Werte für allgemeine Update-Ausnahmen zurückgeben werden.) Eine Alternative für einige Situationen kann das Abrufen von Einträgen für alle Entitäten sein, die möglicherweise aus der Datenbank neu geladen werden müssen, und für jede dieser Entitäten ein erneutes Laden aufzurufen.

Auflösen optimistischer Nebenläufigkeitsausnahmen, wenn der Client gewinnt

Das obige Beispiel, in dem Reload verwendet wird, wird manchmal als „Datenbank gewinnt“ oder „Speicher gewinnt“ bezeichnet, da die Werte in der Entität durch Werte aus der Datenbank überschrieben werden. Manchmal möchten Sie das Gegenteil tun und die Werte in der Datenbank mit den Werten überschreiben, die sich derzeit in der Entität befinden. Dies wird manchmal als „Client gewinnt“ bezeichnet und kann durch Abrufen der aktuellen Datenbankwerte und Festlegen als ursprüngliche Werte für die Entität erfolgen. (Siehe Arbeiten mit Eigenschaftswerten für Informationen zu aktuellen und ursprünglichen Werten.) Zum Beispiel:

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

Benutzerdefinierte Auflösung optimistischer Nebenläufigkeitsausnahmen

Manchmal möchten Sie die Werte, die sich derzeit in der Datenbank befinden, mit den Werten kombinieren, die sich derzeit in der Entität befinden. Dies erfordert in der Regel eine benutzerdefinierte Logik oder Benutzerinteraktionen. Beispielsweise könnten Sie dem Benutzer ein Formular mit den aktuellen Werten, den Werten in der Datenbank und einem Standardsatz aufgelöster Werte präsentieren. Der Benutzer würde dann die aufgelösten Werte nach Bedarf bearbeiten, und es wären diese aufgelösten Werte, die in der Datenbank gespeichert werden. Dies kann mithilfe der DbPropertyValues-Objekte geschehen, die von CurrentValues und GetDatabaseValues für den Eintrag der Entität zurückgegeben werden. Beispiel:

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

Benutzerdefinierte Auflösung optimistischer Nebenläufigkeitsausnahmen mithilfe von Objekten

Der obige Code verwendet DbPropertyValues-Instanzen zum Übergeben aktueller Werte, Datenbankwerte und aufgelöster Werte. Manchmal kann es einfacher sein, dafür Instanzen Ihres Entitätstyps zu verwenden. Dies kann mithilfe der ToObject- und SetValues-Methoden von DbPropertyValues erfolgen. Beispiel:

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