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

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

  •  09-06-2019
  •  | 
  •  

Pregunta

Al ejecutar Subitchanges en el DataContext después de actualizar un par de propiedades con una conexión LINQ a SQL (contra SQL Server Compact Edition) obtengo una "fila no encontrada o cambiada". 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 cargar el registro, confirmé 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 confirmé que esta excepción se produce exactamente con 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 diferente de código y genera el siguiente SQL y de hecho actualiza mi base de datos de 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 se han identificado los valores de los campos primarios adecuados tanto en el esquema de la base de datos como en el DBML que genera las clases LINQ.

Supongo que esta es casi una pregunta de 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 simplemente la clave principal?

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

¿Fue útil?

Solución

Eso es desagradable, pero simple:

Compruebe si los tipos de datos de todos los campos del O/R-Designer coinciden con los tipos de datos de su tabla SQL.¡Verifique dos veces si se pueden anular! Una columna debe ser anulable tanto en O/R-Designer como en SQL, o no ser anulable en ambos.

Por ejemplo, una columna NVARCHAR "título" está marcada como NULLable en su base de datos y contiene el valor NULL.Aunque la columna está marcada como NOT NULLable en su O/R-Mapping, LINQ la cargará correctamente y establecerá la cadena de columna en nula.

  • Ahora cambias algo y llamas a submitchanges ().
  • Linq generará una consulta SQL que contenga "donde [el título] es nulo", para asegurarse de que el título no haya sido cambiado por otra persona.
  • Linq busca las propiedades del [título] en el mapeo.
  • LINQ encontrará [título] NO NULLable.
  • Dado que [el título] no es anulable, ¡por lógica nunca podría ser nulo!
  • Entonces, optimizando la consulta, Linq lo 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 que los leyó.

Otros consejos

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 asistente para envolver tus cambios sumbit:

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
}

Hay un método en DataContext llamado Actualizar lo que puede ayudar aquí.Le permite recargar el registro de la base de datos antes de enviar los cambios y ofrece diferentes modos para determinar qué valores conservar."KeepChanges" parece el más inteligente para mis propósitos, su objetivo es fusionar mis cambios con cualquier cambio no conflictivo que haya ocurrido en la base de datos mientras tanto.

Si lo entiendo correctamente.:)

Resolví este error volviendo a arrastrar una tabla desde el explorador del servidor al diseñador y reconstruyéndola.

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 maneras que parecen impredecibles, porque el usuario se utiliza en ambos contextos, se modifica y se guarda en uno y luego se guarda en el otro.La representación en memoria del usuario propietario de "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 alguna vez pueda ser llamado como método de biblioteca de tal manera que requiera 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);
        }
    }
}

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

No sé si encontró 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 provocaba una excepción ChangeConflictException para cada actualización realizada con Linq a Sql.Puedes consultar mi publicación en aquí.

Lo arreglé agregando (UpdateCheck = UpdateCheck.Never) a todos [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 donde se elimina una fila.

Esto es en Windows Phone 7.5.

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

Sé que esta pregunta ya ha sido respondida hace mucho tiempo, pero 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.

Recientemente encontré este error y descubrí que el problema no estaba en mi contexto de datos, sino en 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 desencadenante intentaba actualizar un campo que no acepta 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.

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

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

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

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

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

y esto rompe las comprobaciones de Linq para las filas afectadas (por lo que puedo descifrar a partir de fuentes .NET), lo que lleva a Cambio de excepción de conflicto

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

Después de emplear la respuesta de qub1n, descubrí que el problema para mí era que sin darme cuenta había declarado 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 porción decimal.Esto resultó en el problema del cambio de fila.

Solo agrego esto si alguien más tiene un problema similar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top