Was kann ich tun, um die Ausnahme „Zeile nicht gefunden oder geändert“ in LINQ to SQL in einer SQL Server Compact Edition-Datenbank aufzulösen?

StackOverflow https://stackoverflow.com/questions/45045

  •  09-06-2019
  •  | 
  •  

Frage

Bei der Ausführung von SubMitchanges in den DataContext nach der Aktualisierung einiger Eigenschaften mit einem LINQ an SQL -Verbindung (gegen SQL Server Compact Edition), erhalte ich eine "Zeile nicht gefunden oder geändert". 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 das 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 das WO 0=1, Nachdem der Datensatz geladen wurde, habe ich bestätigt, dass alle Eigenschaften im „deviceSessionRecord“ korrekt sind und den Primärschlüssel enthalten.Auch beim Abfangen der „ChangeConflictException“ gibt es keine zusätzlichen Informationen darüber, warum dies fehlgeschlagen ist.Ich habe außerdem bestätigt, dass diese Ausnahme bei genau einem Datensatz in der Datenbank ausgelöst wird (dem Datensatz, den ich aktualisieren möchte).

Das Seltsame ist, dass ich eine sehr ähnliche Update-Anweisung in einem anderen Codeabschnitt habe, die 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ärfeldwerte sowohl im Datenbankschema als auch im DBML, das die LINQ-Klassen generiert, identifiziert wurden.

Ich denke, das ist fast eine zweiteilige Frage:

  1. Warum wird die Ausnahme ausgelöst?
  2. Nach Durchsicht des zweiten Satzes generierter SQL scheint es für die Konflikterkennung sinnvoll 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 ich für jede Hilfe dankbar.

War es hilfreich?

Lösung

Das ist böse, aber einfach:

Überprüfen Sie, ob die Datentypen aller Felder im O/R-Designer mit den Datentypen in Ihrer SQL-Tabelle übereinstimmen.Überprüfen Sie noch einmal, ob null zulässig ist! Eine Spalte sollte entweder im O/R-Designer und in SQL nullbar oder in beiden nicht nullbar sein.

Beispielsweise ist eine NVARCHAR-Spalte „title“ in Ihrer Datenbank als NULL-fähig markiert und enthält den Wert NULL.Auch wenn die Spalte in Ihrer O/R-Zuordnung als NICHT NULL-fähig markiert ist, lädt LINQ sie erfolgreich und setzt den Spalten-String auf Null.

  • Jetzt ändern Sie etwas und rufen Submitchanges () an.
  • LINQ generiert eine SQL -Abfrage, die "wobei [Titel] null ist" enthält, um sicherzustellen, dass der Titel nicht von jemand anderem geändert wurde.
  • Linq schaut in der Zuordnung die Eigenschaften von [Titel] nach.
  • LINQ findet [title] NOT NULLable.
  • Da [Titel] nicht nullbar ist, könnte es nach Logik nie null sein!
  • Wenn Sie die Abfrage optimieren, ersetzt Linq sie durch "wobei 0 = 1", das SQL -Äquivalent von "Never".

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

Andere Tipps

Zunächst ist es hilfreich zu wissen, was das Problem verursacht.Das Googeln der Lösung sollte helfen. Sie können die Details (Tabelle, Spalte, alter Wert, neuer Wert) zum Konflikt protokollieren, um später eine bessere Lösung für die Lösung des Konflikts 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 Verpacken Ihrer sumbitChanges:

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

Und dann rufen Sie den Code zum Senden von Änderungen auf:

Datamodel.SubmitChangesWithDetailException();

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

protected void Application_Error(object sender, EventArgs e)
{         
    Exception ex = Server.GetLastError();
    //TODO
}

Es gibt eine Methode namens DataContext Aktualisierung was hier vielleicht weiterhilft.Es ermöglicht Ihnen, den Datenbankeintrag neu zu laden, bevor Änderungen übermittelt werden, und bietet verschiedene Modi, um zu bestimmen, welche Werte beibehalten werden sollen.„KeepChanges“ scheint für meine Zwecke am intelligentesten 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.:) :)

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

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 scheinbar unvorhersehbare Weise fehl, da der Benutzer in beiden Kontexten verwendet, in einem geändert und gespeichert und dann im anderen gespeichert wird.Die speicherinterne Darstellung des Benutzers, dem „Etwas“ gehört, stimmt nicht mit dem in der Datenbank überein, sodass dieser lauernde Fehler auftritt.

Eine Möglichkeit, dies zu verhindern, besteht darin, Code, der jemals als Bibliotheksmethode aufgerufen werden könnte, so zu schreiben, dass er einen optionalen DbContext annimmt:

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 benötigt Ihre Methode eine optionale Datenbank, und wenn keine vorhanden ist, erstellt sie selbst eine.Wenn ja, wird einfach das, was übergeben wurde, wiederverwendet.Mit der Hilfsmethode können Sie dieses Muster ganz einfach in Ihrer App wiederverwenden.

Ich weiß nicht, ob Sie zufriedenstellende Antworten auf Ihre Frage gefunden haben, aber ich habe eine ähnliche Frage gepostet und sie schließlich selbst beantwortet.Es stellte sich heraus, dass die Standardverbindungsoption NOCOUNT für die Datenbank aktiviert war, was bei jeder mit Linq auf Sql durchgeführten Aktualisierung eine ChangeConflictException verursachte.Sie können auf meinen Beitrag verweisen unter Hier.

Ich habe dies durch Hinzufügen behoben (UpdateCheck = UpdateCheck.Never) an alle [Column] Definitionen.

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

Dies ist auf Windows Phone 7.5.

Folgendes benötigen Sie, 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);
                }
            }

Ich weiß, dass diese Frage längst beantwortet wurde, aber hier habe ich die letzten Stunden damit verbracht, meinen Kopf gegen die Wand zu schlagen, und ich wollte nur meine Lösung mitteilen, die, wie sich herausstellte, mit keinem der Punkte in diesem Thread zusammenhängt:

Caching!

Der select()-Teil meines Datenobjekts verwendete Caching.Beim Aktualisieren des Objekts trat die Fehlermeldung „Zeile nicht gefunden oder geändert“ auf.

In mehreren Antworten wurde die Verwendung unterschiedlicher DataContexts erwähnt, und im Nachhinein ist das wahrscheinlich der Fall, aber es hat mich nicht sofort auf die Idee gebracht, an Caching zu denken, also wird das hoffentlich jemandem helfen!

Ich bin kürzlich auf diesen Fehler gestoßen und habe festgestellt, dass das Problem nicht an meinem Datenkontext lag, sondern an einer Update-Anweisung, die innerhalb eines Triggers ausgelöst wurde, nachdem Commit für den Kontext aufgerufen wurde.Der Auslöser versuchte, ein nicht nullbares Feld mit einem Nullwert zu aktualisieren, und verursachte einen Kontextfehler mit der oben genannten Meldung.

Ich füge diese Antwort nur hinzu, um anderen bei der Behebung dieses Fehlers zu helfen und in den obigen Antworten keine Lösung zu finden.

Ich habe diesen Fehler auch erhalten, weil ich zwei verschiedene Kontexte verwendet habe.Ich habe dieses Problem durch die Verwendung eines einzelnen Datenkontexts gelöst.

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

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

Ich habe die Option NOCOUNT aktiviert, in der Hoffnung, einige Leistungsvorteile zu erzielen:

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

und es stellt sich heraus, dass Linqs Prüfungen für die betroffenen Zeilen unterbrochen werden (soweit ich es aus .NET-Quellen herausfinden kann), was dazu führt ChangeConflictException

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

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 (18,0) deklariert hatte.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, falls jemand anderes auf ein ähnliches Problem stößt.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top