Wie kann ich ChangeConflicts in LINQ behandeln?

Veröffentlicht: 25. Mai 2009

Das Problem

Wenn ich Daten in meinem LINQtoSQL DataContext ändere und diese Änderungen an die Datenbank senden möchte kommt es zu ChangeConflicts. Wie kann ich diese behandeln?

Die Lösung

Zunächst legen Sie ein Windows Forms Projekt an. Fügen Sie dem Projekt dann über die Item Templates ein „LINQ to SQL Classes“ Element hinzu und nennen dieses Element „Northwind.dbml“.

Laden Sie sich anschließend die Northwind Datenbank herunter (http://www.microsoft.com/downloads/details.aspx?FamilyID=06616212-0356-46A0-8DA2-EEBC53A68034&displaylang=en) und fügen Sie diese Datenbank zu Ihrer SQL Server (Express) Installation hinzu.

In Visual Studio fügen Sie nun eine Verbindung zu dieser Datenbank über die Schaltflächen des Datenbank Explorers hinzu.

Anschließend öffnen Sie die eben erstellte Northwind.dbml per Doppelklick im Projektmappenexplorer, worauf hin der OR Designer von Visual Studio öffnet. Ziehen Sie nun die Tabelle „Customers“ per Drag&Drop auf die Designeroberfläche:

Im Hintergrund erstellt Visual Studio nun das Objektmodell für die ausgewählte Tabelle und den DataContext über den die Verbindung zur Datenbank hergestellt wird und die CRUD Operationen abgesetzt werden können. Um nun auf den erstellten DataContext zugreifen zu können, legen Sie in der Main Methode zwei Instanzen dieser Klasse an:

Dim db As New NorthwindDataContext()
Dim db2 As New NorthwindDataContext        
      

Fügen Sie nun der Main Methode die folgenden Zeilen ein:

'Teil 1 - Das manuelle Behandeln von Konflikten
db.Customers.First().CompanyName = "Company Eins"
db.Customers.First().ContactName = "Name Eins"

db2.Customers.First().ContactName = "Name Zwei"
db2.Customers.First().ContactTitle = "Verkauf"

db.SubmitChanges()
db2.SubmitChanges()        
      

Wenn Sie diesen Code ausführen, werden Sie eine Fehlermeldung erhalten, da über zwei unterschiedliche Verbindungen Änderungen am gleichen Datensatz gemacht wurden. In dem Moment, in dem die Änderungen über „db2“ an die Datenbank gesendet werden sollen, stellt der DataContext fest, dass sein Datenbestand nicht mehr aktuell ist und wirft eine Exception.

Um diese ChangeConflicts zu behandeln ersetzen Sie die Zeile mit „db2.SubmitChanges()“ durch den folgenden Code:

  Try
      db2.SubmitChanges(ConflictMode.ContinueOnConflict)
  Catch ex As ChangeConflictException
      For Each occ As ObjectChangeConflict In db2.ChangeConflicts
          For Each mcc As MemberChangeConflict In occ.MemberConflicts
              Console.WriteLine("1. Originalwert" & mcc.OriginalValue)
              Console.WriteLine("2. Datenbankwert" & mcc.DatabaseValue)
              Console.WriteLine("3. Aktueller Wert" & mcc.CurrentValue)

              Select Case Console.ReadLine
                  Case "1"
                      mcc.Resolve(mcc.OriginalValue)
                  Case "2"
                      mcc.Resolve(mcc.DatabaseValue)
                  Case "3"
                      mcc.Resolve(mcc.CurrentValue)
                  Case Else
                      mcc.Resolve(mcc.CurrentValue)
              End Select

          Next
      Next


  End Try
  db2.SubmitChanges(ConflictMode.ContinueOnConflict)        
      

Der obige Code befragt nun den Nutzer bei jedem Datenfeld, welches von einem ChangeConflict betroffen ist, welcher Wert (OriginalValue, DatabaseValue oder CurrentValue) in die Datenbank geschrieben werden soll.

Der OriginalValue ist dabei derjenige Wert, den das Feld zu dem Zeitpunkt des erzeugen des DataContextes hatte.

Der DatabaseValue ist der Wert, der aktuell in der Datenbank steht.

Der CurrentValue ist der Wert, der durch die Änderungen im DataContext (in unserem Fall also das Zuweisen von CompanyName, ContactName und ContactTitle) zustande gekommen sind.

Um Konflikte automatisch zu lösen ersetzen Sie einfach den Try-Catch Block von eben durch:

Try
    db2.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch ex As ChangeConflictException

    db2.Refresh(RefreshMode.OverwriteCurrentValues, db2.Customers)
End Try
db2.SubmitChanges(ConflictMode.ContinueOnConflict)        
      

Der obige Code bewirkt, dass alle Felder, welche konfliktbehaftet waren, auf die Werte aus der Datenbank zurückgesetzt werden. Ändert man den „RefreshMode“ auf „KeepCurrentValues“, so werden die konfliktbehafteten Felder beim nächsten „SubmitChanges“ in der Datenbank mit denen aus dem DataContext überschrieben. Setzt man den „RefreshMode“ auf „KeepChanges“ und führt danach „SubmitChanges“ aus, so werden diejenigen Felder, die im DataContext geändert wurden in die Datenbank übernommen und alle anderen Felder im DataContext werden auf den aktuellen Wert aus der Datenbank aktualisiert.

Der Code

Visual Basic

Imports System.Data.Linq

Module Module1
    Dim db As New NorthwindDataContext
    Dim db2 As New NorthwindDataContext
    Sub Main()

        'Teil 1 - Das manuelle Behandeln von Konflikten
        db.Customers.First().CompanyName = "Company Eins"
        db.Customers.First().ContactName = "Name Eins"

        db2.Customers.First().ContactName = "Name Zwei"
        db2.Customers.First().ContactTitle = "Verkauf"

        db.SubmitChanges()

        Try
            db2.SubmitChanges(ConflictMode.ContinueOnConflict)
        Catch ex As ChangeConflictException
            For Each occ As ObjectChangeConflict In db2.ChangeConflicts
                For Each mcc As MemberChangeConflict In occ.MemberConflicts
                    Console.WriteLine("1. Originalwert" & mcc.OriginalValue)
                    Console.WriteLine("2. Datenbankwert" & mcc.DatabaseValue)
                    Console.WriteLine("3. Aktueller Wert" & mcc.CurrentValue)

                    Select Case Console.ReadLine
                        Case "1"
                            mcc.Resolve(mcc.OriginalValue)
                        Case "2"
                            mcc.Resolve(mcc.DatabaseValue)
                        Case "3"
                            mcc.Resolve(mcc.CurrentValue)
                        Case Else
                            mcc.Resolve(mcc.CurrentValue)
                    End Select

                Next
            Next


        End Try
        db2.SubmitChanges(ConflictMode.ContinueOnConflict)

        'Teil 2 - Das automatische Behandeln von Konflikten
        db.Customers.First().CompanyName = "Company Eins"
        db.Customers.First().ContactName = "Name Eins"

        db2.Customers.First().ContactName = "Name Zwei"
        db2.Customers.First().ContactTitle = "Verkauf"

        db.SubmitChanges()

        Try
            db2.SubmitChanges(ConflictMode.ContinueOnConflict)
        Catch ex As ChangeConflictException

            db2.Refresh(RefreshMode.KeepChanges, db2.Customers)
        End Try
        db2.SubmitChanges(ConflictMode.ContinueOnConflict)

    End Sub

End Module
        
      

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
namespace ChangeConflicts_in_LINQ_behandeln
{
    

    static class Program
    {
        static NorthwindDataContext db = new NorthwindDataContext();
        static NorthwindDataContext db2 = new NorthwindDataContext();
        public static void Main()
        {

            //Teil 1 - Das manuelle Behandeln von Konflikten
            db.Customers.First().CompanyName = "Company Eins";
            db.Customers.First().ContactName = "Name Eins";

            db2.Customers.First().ContactName = "Name Zwei";
            db2.Customers.First().ContactTitle = "Verkauf";

            db.SubmitChanges();

            try
            {
                db2.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException ex)
            {
                foreach (ObjectChangeConflict occ in db2.ChangeConflicts)
                {
                    foreach (MemberChangeConflict mcc in occ.MemberConflicts)
                    {
                        Console.WriteLine("1. Originalwert" + mcc.OriginalValue);
                        Console.WriteLine("2. Datenbankwert" + mcc.DatabaseValue);
                        Console.WriteLine("3. Aktueller Wert" + mcc.CurrentValue);

                        switch (Console.ReadLine())
                        {
                            case "1":
                                mcc.Resolve(mcc.OriginalValue);
                                break;
                            case "2":
                                mcc.Resolve(mcc.DatabaseValue);
                                break;
                            case "3":
                                mcc.Resolve(mcc.CurrentValue);
                                break;
                            default:
                                mcc.Resolve(mcc.CurrentValue);
                                break;

                        }
                    }

                }
            }

            db2.SubmitChanges(ConflictMode.ContinueOnConflict);

            //Teil 2 - Das automatische Behandeln von Konflikten
            db.Customers.First().CompanyName = "Company Eins";
            db.Customers.First().ContactName = "Name Eins";

            db2.Customers.First().ContactName = "Name Zwei";
            db2.Customers.First().ContactTitle = "Verkauf";

            db.SubmitChanges();

            try
            {
                db2.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException ex)
            {

                db2.Refresh(RefreshMode.KeepChanges, db2.Customers);
            }

            db2.SubmitChanges(ConflictMode.ContinueOnConflict);
        }

    }

}