Cosa posso fare per risolvere un'eccezione "Riga non trovata o modificata" in LINQ to SQL su un database di SQL Server Compact Edition?


96

Quando si esegue SubmitChanges in DataContext dopo l'aggiornamento di un paio di proprietà con una connessione LINQ to SQL (rispetto a SQL Server Compact Edition) viene visualizzato il messaggio "Riga non trovata o modificata". 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();

La query genera il seguente 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

Il problema ovvio è WHERE 0 = 1 , dopo che il record è stato caricato, ho confermato che tutte le proprietà in "deviceSessionRecord" sono corrette per includere la chiave primaria. Inoltre, quando si rileva "ChangeConflictException" non sono disponibili ulteriori informazioni sul motivo per cui ciò non è riuscito. Ho anche confermato che questa eccezione viene generata con esattamente un record nel database (il record che sto tentando di aggiornare)

La cosa strana è che ho un'istruzione di aggiornamento molto simile in una diversa sezione di codice e genera il seguente SQL e aggiorna effettivamente il mio database di SQL Server Compact Edition.

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

Ho confermato che i valori corretti dei campi primari sono stati identificati sia nello schema del database che nel DBML che genera le classi LINQ.

Immagino che questa sia quasi una domanda in due parti:

  1. Perché viene generata l'eccezione?
  2. Dopo aver esaminato il secondo set di SQL generato, sembra che per rilevare i conflitti sarebbe bello controllare tutti i campi, ma immagino che questo sarebbe abbastanza inefficiente. È così che funziona sempre? Esiste un'impostazione per controllare solo la chiave primaria?

Ho combattuto con questo nelle ultime due ore, quindi qualsiasi aiuto sarebbe apprezzato.


FWIW: ho ricevuto questo errore quando ho chiamato involontariamente il metodo due volte. Avrebbe avuto luogo alla seconda chiamata.
Kris,

Ottime informazioni di base su c-sharpcorner.com/article/…
CAK2

Risposte:


189

È brutto, ma semplice:

Verificare se i tipi di dati per tutti i campi in O / R-Designer corrispondono ai tipi di dati nella tabella SQL. Doppio controllo per nullable! Una colonna dovrebbe essere annullabile sia in O / R-Designer che in SQL o non annullabile in entrambi.

Ad esempio, una colonna NVARCHAR "title" è contrassegnata come NULLable nel database e contiene il valore NULL. Anche se la colonna è contrassegnata come NOT NULLable nella mappatura O / R, LINQ la caricherà correttamente e imposterà la stringa della colonna su null.

  • Ora cambi qualcosa e chiami SubmitChanges ().
  • LINQ genererà una query SQL contenente "WHERE [titolo] IS NULL", per assicurarsi che il titolo non sia stato modificato da qualcun altro.
  • LINQ cerca le proprietà di [titolo] nella mappatura.
  • LINQ troverà [titolo] NOT NULLable.
  • Poiché [titolo] NON è NULLabile, per logica non potrebbe mai essere NULLO!
  • Quindi, ottimizzando la query, LINQ la sostituisce con "dove 0 = 1", l'equivalente SQL di "mai".

Lo stesso sintomo apparirà quando i tipi di dati di un campo non corrispondono al tipo di dati in SQL o se mancano dei campi, poiché LINQ non sarà in grado di assicurarsi che i dati SQL non siano cambiati dalla lettura dei dati.


4
Ho avuto un problema simile, anche se leggermente diverso, e il tuo consiglio di ricontrollare il nullable mi ha salvato la giornata! Ero già calvo, ma questo problema mi sarebbe sicuramente costato un'altra capigliatura se ne avessi avuto uno .. grazie!
Rune Jacobsen

7
Assicurati di impostare la proprietà "Nullable" nella finestra delle proprietà su True. Stavo modificando la proprietà "Tipo di dati server", cambiandola da VARCHAR(MAX) NOT NULLa VARCHAR(MAX) NULLe aspettandomi che funzionasse. Errore molto semplice.

Ho dovuto votare questo. Mi ha fatto risparmiare un sacco di tempo. Stavo esaminando i miei livelli di isolamento perché pensavo che fosse un problema di concorrenza
Adrian

3
Avevo una NUMERIC(12,8)colonna mappata su una Decimalproprietà. Ho dovuto precisare il DbType nell'attributo Colonna [Column(DbType="numeric(12,8)")] public decimal? MyProperty ...
Costo

3
Un modo per identificare i campi / colonne problematici è salvare le classi di entità Linq-to-SQL correnti, che si trovano nel file .dbml, in un file separato. Quindi, elimina il tuo modello corrente e rigeneralo dal database (usando VS), che genererà un nuovo file .dbml. Quindi, esegui semplicemente un comparatore come WinMerge o WinDiff sui due file .dbml per individuare le differenze del problema.
david.barkhuizen

24

Innanzitutto, è utile sapere qual è la causa del problema. La soluzione di Google dovrebbe aiutare, puoi registrare i dettagli (tabella, colonna, vecchio valore, nuovo valore) sul conflitto per trovare una soluzione migliore per risolverlo in seguito:

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

Crea un aiuto per avvolgere il tuo sumbitChanges:

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

Quindi chiama il codice di invio delle modifiche:

Datamodel.SubmitChangesWithDetailException();

Infine, registra l'eccezione nel tuo gestore di eccezioni globale:

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

3
Ottima soluzione! Ho una tabella che ha circa 80 campi e ci sono numerosi trigger sulla tabella che aggiornano vari campi durante gli inserimenti e gli aggiornamenti. Ho ricevuto questo errore durante l'aggiornamento del datacontext utilizzando L2S, ma ero abbastanza sicuro che fosse causato da uno dei trigger che aggiornava un campo, facendo sì che il contesto dei dati fosse diverso dai dati nella tabella. Il tuo codice mi ha aiutato a vedere esattamente quale campo stava causando la mancata sincronizzazione del contesto dati con la tabella. Grazie mille !!
Jagd

1
Questa è un'ottima soluzione per tavoli di grandi dimensioni. Per gestire i valori nulli, modificare 'col.XValue.ToString ()' in 'col.XValue == null? "null": col.XValue.ToString () 'per ciascuno dei tre campi valore.
humbads

Idem per la protezione dai riferimenti nulli durante la stringa OriginalValue, CurrentValue e DatabaseValue.
Floyd Kosch

16

C'è un metodo su DataContext chiamato Refresh che può aiutare qui. Consente di ricaricare il record del database prima che le modifiche vengano inviate e offre diverse modalità per determinare quali valori mantenere. "KeepChanges" sembra il più intelligente per i miei scopi, ha lo scopo di unire le mie modifiche con qualsiasi modifica non in conflitto avvenuta nel database nel frattempo.

Se ho capito bene. :)


5
Questa risposta ha risolto il problema nel mio caso: dc.Refresh(RefreshMode.KeepChanges,changedObject);prima di dc.SubmitChanges
HugoRune

Ho riscontrato questo problema durante l'applicazione di ReadOnlyAttribute alle proprietà in un sito Web di Dynamic Data. Gli aggiornamenti hanno smesso di funzionare e ho ricevuto l'errore "Riga non trovata o modificata" (gli inserti andavano bene però). La correzione di cui sopra ha risparmiato un sacco di fatica e tempo!
Chris Cannon

Potresti spiegare i valori RefreshMode, ad esempio cosa significa KeepCurrentValues? Che cosa fa? Grazie molto. Potrei creare una domanda ...
Chris Cannon

Ho avuto problemi con le transazioni simultanee che non venivano completate in tempo perché un'altra transazione iniziasse sulle stesse righe. KeepChanges mi ha aiutato qui, quindi forse interrompe la transazione corrente (mantenendo i valori salvati) e avvia quella nuova (onestamente non ne ho idea)
Erik Bergstedt

11

Ciò può anche essere causato dall'utilizzo di più di un DbContext.

Quindi per esempio:

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

Questo codice fallirà di volta in volta, in modi che sembrano imprevedibili, perché l'utente viene utilizzato in entrambi i contesti, modificato e salvato in uno, quindi salvato nell'altro. La rappresentazione in memoria dell'utente che possiede "Something" non corrisponde a ciò che è nel database, e quindi ottieni questo bug in agguato.

Un modo per evitare ciò è scrivere qualsiasi codice che potrebbe essere chiamato come metodo di libreria in modo tale da richiedere un DbContext opzionale:

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

Quindi ora il tuo metodo prende un database opzionale e, se non ce n'è uno, ne fa uno da solo. Se è presente, riutilizza semplicemente ciò che è stato passato. Il metodo di supporto semplifica il riutilizzo di questo modello nella tua app.


10

Ho risolto questo errore trascinando di nuovo una tabella dall'esploratore del server al progettista e ricostruendo.


Il nuovo trascinamento della tabella incriminata da Esplora server al designer e la ricostruzione hanno risolto anche questo problema.
rstackhouse

4

Questo è ciò di cui hai bisogno per ignorare questo errore sul codice C #:

            try
            {
                _db.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException e)
            {
                foreach (ObjectChangeConflict occ in _db.ChangeConflicts)
                {
                    occ.Resolve(RefreshMode.KeepChanges);
                }
            }

Ho pianificato elementi inviati da un front-end dell'applicazione al database. Questi attivano l'esecuzione in un servizio, ciascuno su thread diversi. L'utente può premere un pulsante "Annulla" che modifica lo stato di tutti i comandi in sospeso. Il servizio termina ciascuno di essi ma rileva che "In sospeso" è stato modificato in "Annullato" e non può modificarlo in "Completato". Questo ha risolto il problema per me.
pwrgreg007

2
Controlla anche le altre enumerazioni di RefreshMode, come KeepCurrentValues. Nota che devi chiamare di nuovo SubmitChanges dopo aver utilizzato questa logica. Vedi msdn.microsoft.com/en-us/library/… .
pwrgreg007

3

Non so se hai trovato risposte soddisfacenti alla tua domanda, ma ho pubblicato una domanda simile e alla fine ho risposto io stesso. Si è scoperto che l'opzione di connessione predefinita NOCOUNT era attivata per il database, il che causava un'eccezione ChangeConflictException per ogni aggiornamento effettuato con Linq a Sql. Puoi fare riferimento al mio post qui .


3

Ho risolto questo problema aggiungendo (UpdateCheck = UpdateCheck.Never)a tutte le [Column]definizioni.

Tuttavia, non sembra una soluzione appropriata. Nel mio caso sembra essere correlato al fatto che questa tabella ha un'associazione a un'altra tabella da cui viene eliminata una riga.

Questo è su Windows Phone 7.5.


1

Nel mio caso, l'errore è stato generato quando due utenti con contesti dati LINQ-to-SQL diversi hanno aggiornato la stessa entità nello stesso modo. Quando il secondo utente ha tentato l'aggiornamento, la copia che aveva nel contesto dei dati era obsoleta anche se è stata letta dopo il completamento del primo aggiornamento.

Ho scoperto la spiegazione e la soluzione in questo articolo di Akshay Phadke: https://www.c-sharpcorner.com/article/overview-of-concurrency-in-linq-to-sql/

Ecco il codice che ho principalmente sollevato:

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

Quando ho guardato la mia finestra di output durante il debug, ho potuto vedere che il valore corrente corrispondeva al valore del database. Il "valore originale" è sempre stato il colpevole. Questo era il valore letto dal contesto dei dati prima di applicare l'aggiornamento.

Grazie a MarceloBarbosa per l'ispirazione.


0

So che questa domanda ha avuto risposta da tempo, ma qui ho passato le ultime ore a sbattere la testa contro un muro e volevo solo condividere la mia soluzione che si è rivelata non correlata a nessuno degli elementi in questo thread:

Caching!

La parte select () del mio oggetto dati utilizzava la cache. Quando si trattava di aggiornare l'oggetto, veniva visualizzato un errore di riga non trovata o modificata.

Molte delle risposte hanno menzionato l'utilizzo di diversi DataContext e in retrospettiva questo è probabilmente ciò che stava accadendo, ma non mi ha portato immediatamente a pensare che il caching, quindi spero che questo possa aiutare qualcuno!


0

Recentemente ho riscontrato questo errore e ho scoperto che il problema non era con il mio contesto dati, ma con un'istruzione di aggiornamento che si attivava all'interno di un trigger dopo che Commit era stato chiamato sul contesto. Il trigger stava tentando di aggiornare un campo non annullabile con un valore null e causava un errore del contesto con il messaggio menzionato sopra.

Aggiungo questa risposta esclusivamente per aiutare gli altri ad affrontare questo errore e non trovare una soluzione nelle risposte precedenti.


0

Ho anche ricevuto questo errore a causa dell'utilizzo di due contesti diversi. Ho risolto questo problema utilizzando un singolo contesto di dati.


0

Nel mio caso il problema era con le opzioni utente a livello di server. A seguire:

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

Ho abilitato l'opzione NOCOUNT nella speranza di ottenere alcuni vantaggi in termini di prestazioni:

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

e questo risulta rompere i controlli di Linq per le righe interessate (per quanto posso capirlo da fonti .NET), portando a ChangeConflictException

Il ripristino delle opzioni per escludere i 512 bit ha risolto il problema.


0

Dopo aver utilizzato la risposta di qub1n, ho scoperto che il problema per me era che avevo inavvertitamente dichiarato una colonna del database come decimale (18,0). Stavo assegnando un valore decimale, ma il database lo stava cambiando, eliminando la parte decimale. Ciò ha comportato il problema della modifica della riga.

Aggiungendolo solo se qualcun altro si imbatte in un problema simile.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.