¿Qué puedo hacer para resolver una excepción de "Fila no encontrada o modificada" en LINQ to SQL en una base de datos SQL Server Compact Edition?

96

Al ejecutar SubmitChanges al DataContext después de actualizar un par de propiedades con una conexión LINQ to SQL (contra SQL Server Compact Edition) obtengo una "Fila no encontrada o modificada". 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 consulta genera el siguiente 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

El problema obvio es el DONDE 0 = 1. Después de que se cargó el registro, he confirmado que todas las propiedades en "deviceSessionRecord" son correctas para incluir la clave principal. Además, cuando se detecta la "ChangeConflictException", no hay información adicional sobre por qué falló. También he confirmado que esta excepción se lanza con exactamente un registro en la base de datos (el registro que estoy intentando actualizar)

Lo extraño es que tengo una declaración de actualización muy similar en una sección de código diferente y genera el siguiente SQL y, de hecho, actualiza mi base de datos 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

He confirmado que los valores de los campos primarios adecuados se han identificado tanto en el esquema de la base de datos como en el DBML que genera las clases LINQ.

Supongo que esta es una pregunta de casi dos partes:

  1. ¿Por qué se lanza la excepción?
  2. Después de revisar el segundo conjunto de SQL generado, parece que para detectar conflictos sería bueno verificar todos los campos, pero imagino que esto sería bastante ineficiente. ¿Es así como siempre funciona? ¿Existe una configuración para verificar la clave principal?

He estado luchando con esto durante las últimas dos horas, por lo que cualquier ayuda sería apreciada.

Kevin
fuente
FWIW: Recibí este error al llamar involuntariamente al método dos veces. Ocurriría en la segunda llamada.
Kris
Excelente información de antecedentes que se puede encontrar en c-sharpcorner.com/article/…
CAK2

Respuestas:

189

Eso es desagradable, pero simple:

Verifique si los tipos de datos para todos los campos en O / R-Designer coinciden con los tipos de datos en su tabla SQL. ¡Verifique si acepta valores NULL! Una columna debe ser anulable en O / R-Designer y SQL, o no anular en ambos.

Por ejemplo, un "título" de la columna NVARCHAR se marca como NULLable en su base de datos y contiene el valor NULL. A pesar de que la columna está marcada como NOT NULLable en su O / R-Mapping, LINQ la cargará correctamente y establecerá la columna-String en nula.

  • Ahora cambia algo y llama SubmitChanges ().
  • LINQ generará una consulta SQL que contiene "DONDE [título] ES NULO", para asegurarse de que nadie más haya cambiado el título.
  • LINQ busca las propiedades de [título] en la asignación.
  • LINQ encontrará [título] NOT NULL.
  • Dado que [título] NO es NULL, ¡por lógica nunca podría ser NULL!
  • Entonces, optimizando la consulta, LINQ la reemplaza con "donde 0 = 1", el equivalente SQL de "nunca".

El mismo síntoma aparecerá cuando los tipos de datos de un campo no coincidan con el tipo de datos en SQL, o si faltan campos, ya que LINQ no podrá asegurarse de que los datos SQL no hayan cambiado desde la lectura de los datos.

Sam
fuente
4
Tuve un problema similar, aunque ligeramente diferente, ¡y su consejo de verificar dos veces si acepta valores NULL me salvó el día! Ya era calvo, pero este problema seguramente me habría costado otra cabellera si tuviera una ... ¡gracias!
Rune Jacobsen
7
Asegúrese de establecer la propiedad 'Nullable' en la ventana de propiedades en True. Estaba editando la propiedad 'Tipo de datos del servidor', cambiándola de VARCHAR(MAX) NOT NULLa VARCHAR(MAX) NULLy esperando que funcione. Error muy simple.
Tuve que votar a favor de esto. Me ahorró mucho tiempo. Estaba mirando mis niveles de aislamiento porque pensé que era un problema de concurrencia
Adrian
3
Tenía una NUMERIC(12,8)columna asignada a una Decimalpropiedad. Tuve que precisar el DbType en el atributo Column [Column(DbType="numeric(12,8)")] public decimal? MyProperty ...
Costo
3
Una forma de identificar los campos / columnas problemáticos es guardar sus clases de entidad Linq-to-SQL actuales, ubicadas en el archivo .dbml, en un archivo separado. Luego, elimine su modelo actual y vuelva a generarlo de la base de datos (usando VS), lo que generará un nuevo archivo .dbml. Luego, simplemente ejecute un comparador como WinMerge o WinDiff en los dos archivos .dbml para localizar las diferencias del problema.
david.barkhuizen
24

Primero, es útil saber qué está causando el problema. Buscar una solución en Google debería ayudar, puede registrar los detalles (tabla, columna, valor anterior, valor nuevo) sobre el conflicto para encontrar una mejor solución para resolver el conflicto más adelante:

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 ayudante para envolver tu sumbitChanges:

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

Y luego llame al código de envío de cambios:

Datamodel.SubmitChangesWithDetailException();

Finalmente, registre la excepción en su controlador de excepciones global:

protected void Application_Error(object sender, EventArgs e)
{         
    Exception ex = Server.GetLastError();
    //TODO
}
Tomas Kubes
fuente
3
¡Excelente solución! Tengo una tabla que tiene alrededor de 80 campos y hay numerosos desencadenantes en la tabla que actualizan varios campos durante las inserciones y actualizaciones. Recibía este error al actualizar el contexto de datos usando L2S, pero estaba bastante seguro de que estaba siendo causado por uno de los desencadenantes que actualizaba un campo, lo que provocaba que el contexto de datos fuera diferente de los datos de la tabla. Su código me ayudó a ver exactamente qué campo estaba causando que el contexto de datos no estuviera sincronizado con la tabla. ¡¡Gracias una tonelada!!
Jagd
1
Esta es una gran solución para mesas grandes. Para manejar valores nulos, cambie 'col.XValue.ToString ()' a 'col.XValue == null? "nulo": col.XValue.ToString () 'para cada uno de los tres campos de valor.
humbads
Lo mismo ocurre con la protección contra referencias nulas al especificar OriginalValue, CurrentValue y DatabaseValue.
Floyd Kosch
16

Hay un método en DataContext llamado Refresh que puede ayudar aquí. Le permite volver a cargar el registro de la base de datos antes de que se envíen los cambios y ofrece diferentes modos para determinar qué valores conservar. "KeepChanges" parece el más inteligente para mis propósitos, está destinado a fusionar mis cambios con cualquier cambio no conflictivo que haya ocurrido en la base de datos mientras tanto.

Si lo entiendo correctamente. :)

Matt Sherman
fuente
5
Esta respuesta solucionó el problema en mi caso: dc.Refresh(RefreshMode.KeepChanges,changedObject);antes de dc.SubmitChanges
HugoRune
Tuve este problema al aplicar ReadOnlyAttribute a propiedades en un sitio web de datos dinámicos. Las actualizaciones dejaron de funcionar y recibía el error "Fila no encontrada o modificada" (aunque las inserciones estaban bien). ¡La solución anterior ahorró mucho esfuerzo y tiempo!
Chris Cannon
¿Podría explicar los valores de RefreshMode, por ejemplo, qué significa KeepCurrentValues? ¿Qué hace? Muchas gracias. Podría crear una pregunta ...
Chris Cannon
Tuve problemas con las transacciones simultáneas que no se completaban a tiempo para que otra transacción comenzara en las mismas filas. KeepChanges me ayudó aquí, así que tal vez simplemente aborta la transacción actual (mientras mantiene los valores guardados) y comienza la nueva (honestamente, no tengo idea)
Erik Bergstedt
11

Esto también puede deberse al uso de más de un DbContext.

Así por ejemplo:

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

Este código fallará de vez en cuando, de formas que parecen impredecibles, porque el usuario se usa en ambos contextos, se cambia y se guarda en uno y luego se guarda en el otro. La representación en memoria del usuario que posee "Algo" no coincide con lo que hay en la base de datos, por lo que aparece este error al acecho.

Una forma de evitar esto es escribir cualquier código que pueda ser llamado como método de biblioteca de tal manera que tome un DbContext opcional:

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

Así que ahora su método toma una base de datos opcional, y si no hay una, va y crea una. Si lo hay, simplemente reutiliza lo que se pasó. El método auxiliar facilita la reutilización de este patrón en la aplicación.

Chris Moschini
fuente
10

Resolví este error volviendo a arrastrar sobre una tabla desde el explorador del servidor hasta el diseñador y reconstruyendo.


fuente
Volver a arrastrar la tabla ofensiva desde el Explorador de servidores al diseñador y reconstruir me solucionó esto también.
rstackhouse
4

Esto es lo que necesita para anular este error en el código C #:

            try
            {
                _db.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException e)
            {
                foreach (ObjectChangeConflict occ in _db.ChangeConflicts)
                {
                    occ.Resolve(RefreshMode.KeepChanges);
                }
            }
Marcelo Barbosa
fuente
He programado elementos enviados por un front-end de aplicación a la base de datos. Estos desencadenan la ejecución en un servicio, cada uno en diferentes subprocesos. El usuario puede presionar un botón 'cancelar' que cambia el estado de todos los comandos pendientes. El servicio finaliza cada uno, pero descubre que 'Pendiente' se cambió a 'Cancelado' y no puede cambiarlo a 'Completado'. Esto me solucionó el problema.
pwrgreg007
2
También verifique las otras enumeraciones de RefreshMode, como KeepCurrentValues. Tenga en cuenta que debe volver a llamar a SubmitChanges después de usar esta lógica. Consulte msdn.microsoft.com/en-us/library/… .
pwrgreg007
3

No sé si ha encontrado alguna respuesta satisfactoria a su pregunta, pero publiqué una pregunta similar y finalmente la respondí yo mismo. Resultó que la opción de conexión predeterminada NOCOUNT estaba activada para la base de datos, lo que provocó una excepción ChangeConflictException para cada actualización realizada con Linq a Sql. Puede consultar mi publicación aquí .

Michael Nero
fuente
3

Arreglé esto agregando (UpdateCheck = UpdateCheck.Never)a todas las [Column]definiciones.

Sin embargo, no parece una solución adecuada. En mi caso, parece estar relacionado con el hecho de que esta tabla tiene una asociación con otra tabla de la que se elimina una fila.

Esto está en Windows Phone 7.5.

Johan Paul
fuente
1

En mi caso, el error se generó cuando dos usuarios con diferentes contextos de datos LINQ-to-SQL actualizaron la misma entidad de la misma manera. Cuando el segundo usuario intentó la actualización, la copia que tenía en su contexto de datos estaba obsoleta a pesar de que se leyó después de que se completó la primera actualización.

Descubrí la explicación y la solución en este artículo de Akshay Phadke: https://www.c-sharpcorner.com/article/overview-of-concurrency-in-linq-to-sql/

Aquí está el código que levanté principalmente:

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

Cuando miré mi ventana de salida mientras depuraba, pude ver que el valor actual coincidía con el valor de la base de datos. El "Valor Original" fue siempre el culpable. Ese fue el valor leído por el contexto de datos antes de aplicar la actualización.

Gracias a MarceloBarbosa por la inspiración.

CAK2
fuente
0

Sé que esta pregunta ha sido respondida hace mucho tiempo, pero aquí he pasado las últimas horas golpeándome la cabeza contra la pared y solo quería compartir mi solución que resultó no estar relacionada con ninguno de los elementos de este hilo:

Almacenamiento en caché

La parte select () de mi objeto de datos estaba usando almacenamiento en caché. Cuando se trataba de actualizar el objeto, aparecía un error de Fila no encontrada o modificada.

Varias de las respuestas mencionaron el uso de diferentes DataContext y, en retrospectiva, esto es probablemente lo que estaba sucediendo, pero no me llevó instantáneamente a pensar en el almacenamiento en caché, ¡así que espero que esto ayude a alguien!

rtpHarry
fuente
0

Recientemente encontré este error y descubrí que el problema no estaba en mi contexto de datos, sino con una declaración de actualización que se activaba dentro de un disparador después de que se llamaba a Commit en el contexto. El disparador intentaba actualizar un campo que no aceptaba valores NULL con un valor nulo y provocaba que el contexto fallara con el mensaje mencionado anteriormente.

Estoy agregando esta respuesta únicamente para ayudar a otros a lidiar con este error y no encontrar una solución en las respuestas anteriores.

jamisonLikeCode
fuente
0

También tengo este error debido al uso de dos contextos diferentes. Resolví este problema utilizando un contexto de datos únicos.

srinivas vadlamudi
fuente
0

En mi caso, el problema estaba en las opciones de usuario de todo el servidor. Siguiendo:

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

Activé la opción NOCOUNT con la esperanza de obtener algunos beneficios de rendimiento:

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

y esto resulta que rompe los controles de Linq para las Filas Afectadas (tanto como puedo averiguarlo de las fuentes .NET), lo que lleva a ChangeConflictException

Restablecer las opciones para excluir los 512 bits solucionó el problema.

Wojtek
fuente
0

Después de emplear la respuesta de qub1n, descubrí que el problema para mí era que había declarado inadvertidamente que una columna de la base de datos era decimal (18,0). Estaba asignando un valor decimal, pero la base de datos lo estaba cambiando, eliminando la parte decimal. Esto resultó en el problema de cambio de fila.

Solo agregue esto si alguien más se encuentra con un problema similar.

John Pasquet
fuente
0

solo ve con Linq2DB, mucho mejor

nam vo
fuente