Was kann ich tun, um eine Ausnahme "Zeile nicht gefunden oder geändert" in LINQ to SQL in einer SQL Server Compact Edition-Datenbank zu beheben?

95

Beim Ausführen von SubmitChanges an den DataContext nach dem Aktualisieren einiger Eigenschaften mit einer LINQ-zu-SQL-Verbindung (gegen SQL Server Compact Edition) wird die Meldung "Zeile nicht gefunden oder geändert" angezeigt. ChangeConflictException.

var ctx = new Data.MobileServerDataDataContext(Common.DatabasePath);
var deviceSessionRecord = ctx.Sessions.First(sess => sess.SessionRecId == args.DeviceSessionId);

deviceSessionRecord.IsActive = false;
deviceSessionRecord.Disconnected = DateTime.Now;

ctx.SubmitChanges();

Die Abfrage generiert die folgende SQL:

UPDATE [Sessions]
SET [Is_Active] = @p0, [Disconnected] = @p1
WHERE 0 = 1
-- @p0: Input Boolean (Size = 0; Prec = 0; Scale = 0) [False]
-- @p1: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:12:02 PM]
-- Context: SqlProvider(SqlCE) Model: AttributedMetaModel Build: 3.5.21022.8

Das offensichtliche Problem ist WHERE 0 = 1. Nachdem der Datensatz geladen wurde, habe ich bestätigt, dass alle Eigenschaften im "deviceSessionRecord" korrekt sind, um den Primärschlüssel einzuschließen. Auch beim Abfangen der "ChangeConflictException" gibt es keine zusätzlichen Informationen darüber, warum dies fehlgeschlagen ist. Ich habe auch bestätigt, dass diese Ausnahme mit genau einem Datensatz in der Datenbank ausgelöst wird (der Datensatz, den ich zu aktualisieren versuche).

Was seltsam ist, ist, dass ich eine sehr ähnliche Update-Anweisung in einem anderen Codeabschnitt habe und sie das folgende SQL generiert und tatsächlich meine SQL Server Compact Edition-Datenbank aktualisiert.

UPDATE [Sessions]
SET [Is_Active] = @p4, [Disconnected] = @p5
WHERE ([Session_RecId] = @p0) AND ([App_RecId] = @p1) AND ([Is_Active] = 1) AND ([Established] = @p2) AND ([Disconnected] IS NULL) AND ([Member_Id] IS NULL) AND ([Company_Id] IS NULL) AND ([Site] IS NULL) AND (NOT ([Is_Device] = 1)) AND ([Machine_Name] = @p3)
-- @p0: Input Guid (Size = 0; Prec = 0; Scale = 0) [0fbbee53-cf4c-4643-9045-e0a284ad131b]
-- @p1: Input Guid (Size = 0; Prec = 0; Scale = 0) [7a174954-dd18-406e-833d-8da650207d3d]
-- @p2: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:20:50 PM]
-- @p3: Input String (Size = 0; Prec = 0; Scale = 0) [CWMOBILEDEV]
-- @p4: Input Boolean (Size = 0; Prec = 0; Scale = 0) [False]
-- @p5: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:20:52 PM]
-- Context: SqlProvider(SqlCE) Model: AttributedMetaModel Build: 3.5.21022.8

Ich habe bestätigt, dass die richtigen primären Feldwerte sowohl im Datenbankschema als auch in der DBML identifiziert wurden, die die LINQ-Klassen generiert.

Ich denke, das ist fast eine zweiteilige Frage:

  1. Warum wird die Ausnahme ausgelöst?
  2. Nach der Überprüfung des zweiten Satzes von generiertem SQL scheint es für die Erkennung von Konflikten schön zu sein, alle Felder zu überprüfen, aber ich stelle mir vor, dass dies ziemlich ineffizient wäre. Funktioniert das immer so? Gibt es eine Einstellung, um nur den Primärschlüssel zu überprüfen?

Ich habe in den letzten zwei Stunden damit gekämpft, daher wäre jede Hilfe dankbar.

Kevin
quelle
FWIW: Ich habe diesen Fehler erhalten, als ich die Methode ungewollt zweimal aufgerufen habe. Es würde beim zweiten Anruf auftreten.
Kris
Hervorragende Hintergrundinformationen finden Sie unter c-sharpcorner.com/article/…
CAK2

Antworten:

188

Das ist böse, aber einfach:

Überprüfen Sie, ob die Datentypen für alle Felder im O / R-Designer mit den Datentypen in Ihrer SQL-Tabelle übereinstimmen. Überprüfen Sie noch einmal auf nullable! Eine Spalte sollte entweder sowohl im O / R-Designer als auch in SQL nullwertfähig oder in beiden nicht nullwertfähig sein.

Beispielsweise wird eine NVARCHAR-Spalte "title" in Ihrer Datenbank als NULLable markiert und enthält den Wert NULL. Obwohl die Spalte in Ihrer O / R-Zuordnung als NICHT NULL-fähig markiert ist, lädt LINQ sie erfolgreich und setzt die Spaltenzeichenfolge auf null.

  • Jetzt ändern Sie etwas und rufen SubmitChanges () auf.
  • LINQ generiert eine SQL-Abfrage mit "WHERE [title] IS NULL", um sicherzustellen, dass der Titel nicht von einer anderen Person geändert wurde.
  • LINQ sucht die Eigenschaften von [title] im Mapping.
  • LINQ findet [title] NICHT NULLable.
  • Da [title] NICHT NULL ist, könnte es logischerweise niemals NULL sein!
  • Um die Abfrage zu optimieren, ersetzt LINQ sie durch "wobei 0 = 1", das SQL-Äquivalent von "nie".

Das gleiche Symptom tritt auf, wenn die Datentypen eines Felds nicht mit dem Datentyp in SQL übereinstimmen oder wenn Felder fehlen, da LINQ nicht sicherstellen kann, dass sich die SQL-Daten seit dem Lesen der Daten nicht geändert haben.

Sam
quelle
4
Ich hatte ein ähnliches - wenn auch etwas anderes - Problem, und Ihr Rat, noch einmal auf nullable zu prüfen, hat mir den Tag gerettet! Ich hatte bereits eine Glatze, aber dieses Problem hätte mich sicherlich einen weiteren Haarschopf gekostet, wenn ich einen gehabt hätte. Danke!
Rune Jacobsen
7
Stellen Sie sicher, dass Sie die Eigenschaft 'Nullable' im Eigenschaftenfenster auf True setzen. Ich habe die Eigenschaft 'Server-Datentyp' bearbeitet, von VARCHAR(MAX) NOT NULLin VARCHAR(MAX) NULLgeändert und erwartet, dass sie funktioniert. Sehr einfacher Fehler.
Musste dies positiv bewerten. Es hat mir eine Menge Zeit gespart. Ich habe mir meine Isolationsstufen angesehen, weil ich gedacht hatte, es sei ein Problem mit der Parallelität
Adrian
3
Ich hatte eine NUMERIC(12,8)Spalte einer DecimalEigenschaft zugeordnet. Ich musste den DbType im Column-Attribut präzisieren [Column(DbType="numeric(12,8)")] public decimal? MyProperty ...
Costo
3
Eine Möglichkeit, die Problemfelder / -spalten zu identifizieren, besteht darin, Ihre aktuellen Linq-to-SQL-Entitätsklassen in der DBML-Datei in einer separaten Datei zu speichern. Löschen Sie dann Ihr aktuelles Modell und generieren Sie es aus der Datenbank neu (mithilfe von VS). Dadurch wird eine neue DBML-Datei generiert. Führen Sie dann einfach einen Komparator wie WinMerge oder WinDiff für die beiden .dbml-Dateien aus, um die Problemunterschiede zu ermitteln.
David.Barkhuizen
24

Zunächst ist es hilfreich zu wissen, was das Problem verursacht. Eine Google-Lösung sollte helfen. Sie können die Details (Tabelle, Spalte, alter Wert, neuer Wert) des Konflikts protokollieren, um später eine bessere Lösung für den Konflikt zu finden:

public class ChangeConflictExceptionWithDetails : ChangeConflictException
{
    public ChangeConflictExceptionWithDetails(ChangeConflictException inner, DataContext context)
        : base(inner.Message + " " + GetChangeConflictExceptionDetailString(context))
    {
    }

    /// <summary>
    /// Code from following link
    /// https://ittecture.wordpress.com/2008/10/17/tip-of-the-day-3/
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    static string GetChangeConflictExceptionDetailString(DataContext context)
    {
        StringBuilder sb = new StringBuilder();

        foreach (ObjectChangeConflict changeConflict in context.ChangeConflicts)
        {
            System.Data.Linq.Mapping.MetaTable metatable = context.Mapping.GetTable(changeConflict.Object.GetType());

            sb.AppendFormat("Table name: {0}", metatable.TableName);
            sb.AppendLine();

            foreach (MemberChangeConflict col in changeConflict.MemberConflicts)
            {
                sb.AppendFormat("Column name : {0}", col.Member.Name);
                sb.AppendLine();
                sb.AppendFormat("Original value : {0}", col.OriginalValue.ToString());
                sb.AppendLine();
                sb.AppendFormat("Current value : {0}", col.CurrentValue.ToString());
                sb.AppendLine();
                sb.AppendFormat("Database value : {0}", col.DatabaseValue.ToString());
                sb.AppendLine();
                sb.AppendLine();
            }
        }

        return sb.ToString();
    }
}

Erstellen Sie einen Helfer zum Umwickeln Ihrer sumbitChanges:

public static class DataContextExtensions
{
    public static void SubmitChangesWithDetailException(this DataContext dataContext)
    {   
        try
        {         
            dataContext.SubmitChanges();
        }
        catch (ChangeConflictException ex)
        {
            throw new ChangeConflictExceptionWithDetails(ex, dataContext);
        }           
    }
}

Rufen Sie dann den Änderungscode "Senden" auf:

Datamodel.SubmitChangesWithDetailException();

Protokollieren Sie abschließend die Ausnahme in Ihrem globalen Ausnahmebehandler:

protected void Application_Error(object sender, EventArgs e)
{         
    Exception ex = Server.GetLastError();
    //TODO
}
Tomas Kubes
quelle
3
Hervorragende Lösung! Ich habe eine Tabelle mit ungefähr 80 Feldern, und es gibt zahlreiche Trigger in der Tabelle, die verschiedene Felder während Einfügungen und Aktualisierungen aktualisieren. Ich habe diesen Fehler beim Aktualisieren des Datenkontexts mit L2S erhalten, war mir aber ziemlich sicher, dass er durch einen der Auslöser beim Aktualisieren eines Felds verursacht wurde, wodurch sich der Datenkontext von den Daten in der Tabelle unterscheidet. Ihr Code hat mir geholfen, genau zu erkennen, durch welches Feld der Datenkontext nicht mit der Tabelle synchronisiert wurde. Danke vielmals!!
Jagd
1
Dies ist eine großartige Lösung für große Tische. Ändern Sie 'col.XValue.ToString ()' in 'col.XValue == null? "null": col.XValue.ToString () 'für jedes der drei Wertefelder.
Humbads
Das Gleiche gilt für den Schutz vor Nullreferenzen beim Stringing von OriginalValue, CurrentValue und DatabaseValue.
Floyd Kosch
16

In DataContext gibt es eine Methode namens Refresh, die hier hilfreich sein kann. Sie können den Datenbankdatensatz neu laden, bevor Änderungen übermittelt werden, und es stehen verschiedene Modi zur Verfügung, um zu bestimmen, welche Werte beibehalten werden sollen. "KeepChanges" scheint für meine Zwecke am klügsten zu sein. Es soll meine Änderungen mit allen nicht widersprüchlichen Änderungen zusammenführen, die in der Zwischenzeit in der Datenbank vorgenommen wurden.

Wenn ich es richtig verstehe. :) :)

Matt Sherman
quelle
5
Diese Antwort hat das Problem in meinem Fall dc.Refresh(RefreshMode.KeepChanges,changedObject);behoben : vor dc.SubmitChanges
HugoRune
Ich hatte dieses Problem beim Anwenden des ReadOnlyAttribute auf Eigenschaften in einer Dynamic Data-Website. Updates funktionierten nicht mehr und ich erhielt die Fehlermeldung "Zeile nicht gefunden oder geändert" (Einfügungen waren jedoch in Ordnung). Das obige Update sparte viel Aufwand und Zeit!
Chris Cannon
Könnten Sie bitte die RefreshMode-Werte erläutern, z. B. was bedeutet KeepCurrentValues? was tut es? Danke vielmals. Ich könnte eine Frage erstellen ...
Chris Cannon
Ich hatte Probleme mit gleichzeitigen Transaktionen, die nicht rechtzeitig abgeschlossen wurden, damit eine andere Transaktion in denselben Zeilen beginnen konnte. KeepChanges hat mir hier geholfen, also bricht es vielleicht nur die aktuelle Transaktion ab (während die gespeicherten Werte beibehalten werden) und startet die neue (ehrlich gesagt habe ich keine Ahnung)
Erik Bergstedt
11

Dies kann auch durch die Verwendung von mehr als einem DbContext verursacht werden.

Also zum Beispiel:

protected async Task loginUser(string username)
{
    using(var db = new Db())
    {
        var user = await db.Users
            .SingleAsync(u => u.Username == username);
        user.LastLogin = DateTime.UtcNow;
        await db.SaveChangesAsync();
    }
}

protected async Task doSomething(object obj)
{
    string username = "joe";
    using(var db = new Db())
    {
        var user = await db.Users
            .SingleAsync(u => u.Username == username);

        if (DateTime.UtcNow - user.LastLogin >
            new TimeSpan(0, 30, 0)
        )
            loginUser(username);

        user.Something = obj;
        await db.SaveChangesAsync();
    }
}

Dieser Code schlägt von Zeit zu Zeit auf unvorhersehbare Weise fehl, da der Benutzer in beiden Kontexten verwendet, in einem geändert und gespeichert und dann in dem anderen gespeichert wird. Die speicherinterne Darstellung des Benutzers, der "Something" besitzt, stimmt nicht mit dem Inhalt der Datenbank überein, sodass Sie diesen lauernden Fehler erhalten.

Eine Möglichkeit, dies zu verhindern, besteht darin, Code, der jemals als Bibliotheksmethode aufgerufen werden könnte, so zu schreiben, dass ein optionaler DbContext erforderlich ist:

protected async Task loginUser(string username, Db _db = null)
{
    await EFHelper.Using(_db, async db =>
    {
        var user = await db.Users...
        ... // Rest of loginUser code goes here
    });
}

public class EFHelper
{
    public static async Task Using<T>(T db, Func<T, Task> action)
        where T : DbContext, new()
    {
        if (db == null)
        {
            using (db = new T())
            {
                await action(db);
            }
        }
        else
        {
            await action(db);
        }
    }
}

Jetzt nimmt Ihre Methode eine optionale Datenbank, und wenn es keine gibt, erstellt sie selbst eine. Wenn dies der Fall ist, wird nur das wiedergegeben, was übergeben wurde. Die Hilfsmethode erleichtert die Wiederverwendung dieses Musters in Ihrer App.

Chris Moschini
quelle
10

Ich habe diesen Fehler behoben, indem ich eine Tabelle vom Server-Explorer zum Designer verschoben und neu erstellt habe.


quelle
Das Umstellen der fehlerhaften Tabelle vom Server-Explorer auf den Designer und das Neuerstellen haben dies auch für mich behoben.
Stapelhaus
4

Folgendes müssen Sie tun, um diesen Fehler im C # -Code zu überschreiben:

            try
            {
                _db.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException e)
            {
                foreach (ObjectChangeConflict occ in _db.ChangeConflicts)
                {
                    occ.Resolve(RefreshMode.KeepChanges);
                }
            }
MarceloBarbosa
quelle
Ich habe Elemente geplant, die von einem Anwendungs-Frontend an die Datenbank gesendet wurden. Diese lösen die Ausführung in einem Dienst jeweils in unterschiedlichen Threads aus. Der Benutzer kann auf die Schaltfläche "Abbrechen" klicken, um den Status aller ausstehenden Befehle zu ändern. Der Dienst beendet jeden einzelnen, stellt jedoch fest, dass "Ausstehend" in "Abgebrochen" geändert wurde und nicht in "Abgeschlossen" geändert werden kann. Dies hat das Problem für mich behoben.
pwrgreg007
2
Überprüfen Sie auch die anderen Aufzählungen von RefreshMode, z. B. KeepCurrentValues. Beachten Sie, dass Sie SubmitChanges nach Verwendung dieser Logik erneut aufrufen müssen. Siehe msdn.microsoft.com/en-us/library/… .
pwrgreg007
3

Ich weiß nicht, ob Sie zufriedenstellende Antworten auf Ihre Frage gefunden haben, aber ich habe eine ähnliche Frage gestellt und sie schließlich selbst beantwortet. Es stellte sich heraus, dass die Standardverbindungsoption NOCOUNT für die Datenbank aktiviert war, was bei jedem mit Linq zu SQL vorgenommenen Update eine ChangeConflictException verursachte. Sie können auf meinen Beitrag hier verweisen .

Michael Nero
quelle
3

Ich habe dies behoben, indem ich (UpdateCheck = UpdateCheck.Never)allen [Column]Definitionen hinzugefügt habe .

Fühlt sich jedoch nicht nach einer angemessenen Lösung an. In meinem Fall scheint es damit zu tun zu haben, dass diese Tabelle einer anderen Tabelle zugeordnet ist, aus der eine Zeile gelöscht wird.

Dies ist unter Windows Phone 7.5.

Johan Paul
quelle
1

In meinem Fall wurde der Fehler ausgelöst, als zwei Benutzer mit unterschiedlichen LINQ-zu-SQL-Datenkontexten dieselbe Entität auf dieselbe Weise aktualisierten. Als der zweite Benutzer das Update versuchte, war die Kopie, die er in seinem Datenkontext hatte, veraltet, obwohl sie nach Abschluss des ersten Updates gelesen wurde.

Ich habe die Erklärung und Lösung in diesem Artikel von Akshay Phadke entdeckt: https://www.c-sharpcorner.com/article/overview-of-concurrency-in-linq-to-sql/

Hier ist der Code, den ich meistens aufgehoben habe:

try
{
    this.DC.SubmitChanges();
}
catch (ChangeConflictException)
{
     this.DC.ChangeConflicts.ResolveAll(RefreshMode.OverwriteCurrentValues);

     foreach (ObjectChangeConflict objectChangeConflict in this.DC.ChangeConflicts)
     {
         foreach (MemberChangeConflict memberChangeConflict in objectChangeConflict.MemberConflicts)
         {
             Debug.WriteLine("Property Name = " + memberChangeConflict.Member.Name);
             Debug.WriteLine("Current Value = " + memberChangeConflict.CurrentValue.ToString());
             Debug.WriteLine("Original Value = " + memberChangeConflict.OriginalValue.ToString());
             Debug.WriteLine("Database Value = " + memberChangeConflict.DatabaseValue.ToString());
         }
     }
     this.DC.SubmitChanges();
     this.DC.Refresh(RefreshMode.OverwriteCurrentValues, att);
 }

Beim Betrachten meines Ausgabefensters beim Debuggen konnte ich feststellen, dass der aktuelle Wert mit dem Datenbankwert übereinstimmt. Der "ursprüngliche Wert" war immer der Schuldige. Dies war der Wert, den der Datenkontext vor dem Anwenden des Updates gelesen hat.

Vielen Dank an MarceloBarbosa für die Inspiration.

CAK2
quelle
0

Ich weiß, dass diese Frage längst beantwortet wurde, aber hier habe ich die letzten Stunden damit verbracht, meinen Kopf gegen eine Wand zu schlagen, und ich wollte nur meine Lösung teilen, die sich als nicht mit einem der Elemente in diesem Thread verbunden herausstellte:

Caching!

Der select () Teil meines Datenobjekts verwendete Caching. Beim Aktualisieren des Objekts trat ein Fehler "Zeile nicht gefunden oder geändert" auf.

In mehreren Antworten wurde die Verwendung unterschiedlicher DataContext-Daten erwähnt, und im Nachhinein ist dies wahrscheinlich der Fall, aber es hat mich nicht sofort dazu gebracht, über das Caching nachzudenken. Hoffentlich hilft dies jemandem!

rtpHarry
quelle
0

Ich bin kürzlich auf diesen Fehler gestoßen und habe festgestellt, dass das Problem nicht bei meinem Datenkontext liegt, sondern bei einer Update-Anweisung, die in einem Trigger ausgelöst wird, nachdem Commit für den Kontext aufgerufen wurde. Der Trigger hat versucht, ein nicht nullwertfähiges Feld mit einem Nullwert zu aktualisieren, und der Kontext ist mit der oben genannten Nachricht fehlerhaft.

Ich füge diese Antwort nur hinzu, um anderen zu helfen, mit diesem Fehler umzugehen und in den obigen Antworten keine Lösung zu finden.

jamisonLikeCode
quelle
0

Ich habe diesen Fehler auch aufgrund der Verwendung von zwei verschiedenen Kontexten erhalten. Ich habe dieses Problem mithilfe eines einzelnen Datenkontexts behoben.

srinivas vadlamudi
quelle
0

In meinem Fall lag das Problem bei den serverweiten Benutzeroptionen. Folgende:

https://msdn.microsoft.com/en-us/library/ms190763.aspx

Ich habe die Option NOCOUNT aktiviert, um einige Leistungsvorteile zu erzielen:

EXEC sys.sp_configure 'user options', 512;
RECONFIGURE;

und dies führt dazu, dass Linqs Überprüfungen für die betroffenen Zeilen unterbrochen werden (soweit ich dies aus .NET-Quellen herausfinden kann), was zu ChangeConflictException führt

Das Zurücksetzen der Optionen zum Ausschließen des 512-Bit hat das Problem behoben.

Wojtek
quelle
0

Nachdem ich die Antwort von qub1n verwendet hatte, stellte ich fest, dass das Problem für mich darin bestand, dass ich versehentlich eine Datenbankspalte als dezimal deklariert hatte (18,0). Ich habe einen Dezimalwert zugewiesen, aber die Datenbank hat ihn geändert und den Dezimalteil entfernt. Dies führte zu dem Problem mit der Zeilenänderung.

Fügen Sie dies einfach hinzu, wenn jemand anderes auf ein ähnliches Problem stößt.

John Pasquet
quelle
0

Gehen Sie einfach mit Linq2DB, viel besser

nam vo
quelle