O que posso fazer para resolver uma exceção “Linha não encontrada ou alterada” no LINQ to SQL em um banco de dados SQL Server Compact Edition?


96

Ao executar SubmitChanges para o DataContext após atualizar algumas propriedades com uma conexão LINQ to SQL (em relação ao SQL Server Compact Edition), obtenho uma "Linha não encontrada ou alterada." 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();

A consulta gera o seguinte 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

O problema óbvio é WHERE 0 = 1. Depois que o registro foi carregado, confirmei que todas as propriedades em "deviceSessionRecord" estão corretas para incluir a chave primária. Além disso, ao capturar a "ChangeConflictException", não há informações adicionais sobre o motivo da falha. Também confirmei que essa exceção é lançada com exatamente um registro no banco de dados (o registro que estou tentando atualizar)

O que é estranho é que eu tenho uma instrução de atualização muito semelhante em uma seção diferente do código e ela gera o seguinte SQL e de fato atualiza meu banco de dados 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

Confirmei que os valores de campos primários adequados foram identificados no esquema de banco de dados e no DBML que gera as classes LINQ.

Eu acho que esta é quase uma questão de duas partes:

  1. Por que a exceção está sendo lançada?
  2. Depois de revisar o segundo conjunto de SQL gerado, parece que para detectar conflitos seria bom verificar todos os campos, mas imagino que isso seja bastante ineficiente. É assim que sempre funciona? Existe uma configuração para apenas verificar a chave primária?

Tenho lutado contra isso nas últimas duas horas, então qualquer ajuda seria apreciada.


FWIW: Eu estava recebendo este erro ao chamar acidentalmente o método duas vezes. Isso ocorreria na segunda chamada.
— Kris de

Excelentes informações básicas podem ser encontradas em c-sharpcorner.com/article/…
— CAK2

Respostas:


189

Isso é desagradável, mas simples:

Verifique se os tipos de dados para todos os campos no O / R-Designer correspondem aos tipos de dados em sua tabela SQL. Verifique novamente para anulável! Uma coluna deve ser anulável no O / R-Designer e no SQL ou não anulável em ambos.

Por exemplo, uma coluna NVARCHAR "título" é marcada como NULLable em seu banco de dados e contém o valor NULL. Mesmo que a coluna seja marcada como NOT NULLable em seu O / R-Mapping, LINQ irá carregá-la com sucesso e definir a string de coluna como nula.

  • Agora você muda algo e chama SubmitChanges ().
  • LINQ irá gerar uma consulta SQL contendo "WHERE [título] IS NULL", para garantir que o título não foi alterado por outra pessoa.
  • LINQ pesquisa as propriedades de [título] no mapeamento.
  • O LINQ encontrará [título] NÃO NULÁVEL.
  • Como [título] NÃO pode ser NULL, pela lógica ele nunca poderia ser NULL!
  • Portanto, otimizando a consulta, o LINQ a substitui por "onde 0 = 1", o equivalente SQL de "nunca".

O mesmo sintoma aparecerá quando os tipos de dados de um campo não corresponderem ao tipo de dados no SQL ou se houver campos ausentes, pois o LINQ não será capaz de garantir que os dados SQL não tenham mudado desde a leitura dos dados.


4
Tive um problema semelhante - embora um pouco diferente - e seu conselho para verificar se há anulação salvou meu dia! Eu já era careca, mas esse problema certamente teria me custado mais uma cabeleira se eu tivesse uma .. obrigado!
— Rune Jacobsen

7
Certifique-se de definir a propriedade 'Nullable' na janela de propriedades como True. Eu estava editando a propriedade 'Tipo de dados do servidor', alterando-a de VARCHAR(MAX) NOT NULLpara VARCHAR(MAX) NULLe esperando que funcionasse. Erro muito simples.

Tive que votar a favor disso. Isso me economizou muito tempo. Estava olhando para meus níveis de isolamento porque pensei que era um problema de simultaneidade
— Adrian

3
Eu tinha uma NUMERIC(12,8)coluna mapeada para uma Decimalpropriedade. Tive que precisar o DbType no atributo Coluna [Column(DbType="numeric(12,8)")] public decimal? MyProperty ...
— Costo

3
Uma maneira de identificar os campos / colunas do problema é salvar suas classes de entidade Linq-to-SQL atuais, localizadas no arquivo .dbml, em um arquivo separado. Em seguida, exclua seu modelo atual e gere-o novamente do banco de dados (usando o VS), o que irá gerar um novo arquivo .dbml. Em seguida, basta executar um comparador como WinMerge ou WinDiff nos dois arquivos .dbml para localizar as diferenças do problema.
— david.barkhuizen

24

Em primeiro lugar, é útil saber o que está causando o problema. Googling solução deve ajudar, você pode registrar os detalhes (tabela, coluna, valor antigo, novo valor) sobre o conflito para encontrar a melhor solução para resolver o conflito mais tarde:

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

Crie auxiliares para envolver seus sumbitChanges:

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

Em seguida, chame o código de alterações de envio:

Datamodel.SubmitChangesWithDetailException();

Finalmente, registre a exceção em seu manipulador de exceção global:

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

3
Excelente solução! Eu tenho uma tabela com cerca de 80 campos e vários gatilhos na tabela que estão atualizando vários campos durante inserções e atualizações. Eu estava recebendo este erro ao atualizar o datacontext usando L2S, mas tinha quase certeza de que era causado por um dos gatilhos atualizando um campo, fazendo com que o contexto de dados fosse diferente dos dados na tabela. Seu código me ajudou a ver exatamente qual campo estava fazendo com que o contexto de dados ficasse fora de sincronia com a tabela. Muito obrigado !!
— Jagd de

1
Esta é uma ótima solução para tabelas grandes. Para lidar com nulos, altere 'col.XValue.ToString ()' para 'col.XValue == null? "null": col.XValue.ToString () 'para cada um dos três campos de valor.
— humbads de

O mesmo vale para a proteção contra referências nulas ao stringificar OriginalValue, CurrentValue e DatabaseValue.
— Floyd Kosch

16

Existe um método no DataContext chamado Refresh que pode ajudar aqui. Ele permite que você recarregue o registro do banco de dados antes que as alterações sejam enviadas e oferece diferentes modos para determinar quais valores manter. "KeepChanges" parece ser o mais inteligente para os meus propósitos, tem como objetivo mesclar minhas alterações com qualquer alteração não conflitante que tenha acontecido no banco de dados nesse ínterim.

Se bem entendi. :)


5
Essa resposta corrigiu o problema no meu caso: dc.Refresh(RefreshMode.KeepChanges,changedObject);antes de dc.SubmitChanges
— HugoRune

Eu tive esse problema ao aplicar o ReadOnlyAttribute às propriedades em um site de dados dinâmicos. As atualizações pararam de funcionar e eu estava recebendo o erro "Linha não encontrada ou alterada" (as inserções funcionavam bem). A correção acima economizou muito tempo e esforço!
— Chris Cannon

Você poderia explicar os valores de RefreshMode, por exemplo, o que KeepCurrentValues ​​significa? O que isso faz? Muito Obrigado. Eu poderia fazer uma pergunta ...
— Chris Cannon

Tive problemas com transações simultâneas que não eram concluídas a tempo de outra transação começar nas mesmas linhas. KeepChanges me ajudou aqui, então talvez ele apenas aborte a transação atual (enquanto mantém os valores salvos) e inicia a nova (honestamente, não tenho ideia)
— Erik Bergstedt

11

Isso também pode ser causado pelo uso de mais de um DbContext.

Então, por exemplo:

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

Esse código falhará de vez em quando, de maneiras que parecem imprevisíveis, porque o usuário é usado em ambos os contextos, alterado e salvo em um e depois salvo no outro. A representação na memória do usuário que possui "Algo" não corresponde ao que está no banco de dados, então você obtém este bug oculto.

Uma maneira de evitar isso é escrever qualquer código que possa ser chamado como um método de biblioteca de forma que receba um 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);
        }
    }
}

Portanto, agora seu método pega um banco de dados opcional e, se não houver um, vai e cria um. Se houver, apenas reutiliza o que foi transmitido. O método auxiliar facilita a reutilização desse padrão em seu aplicativo.


10

Resolvi esse erro arrastando de novo uma mesa do explorador de servidores para o designer e reconstruindo.


Arrastar novamente a tabela ofensiva do Server Explorer para o designer e reconstruir corrigiu isso para mim também.
— rstackhouse

4

Isso é o que você precisa para substituir este erro no código C #:

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

Agendei itens enviados por um front-end de aplicativo ao banco de dados. Eles acionam a execução em um serviço, cada um em diferentes threads. O usuário pode clicar em um botão 'cancelar' que altera todos os status do comando pendente. O serviço termina cada um, mas descobre que 'Pendente' foi alterado para 'Cancelado' e não pode alterá-lo para 'Concluído'. Isso resolveu o problema para mim.
— pwrgreg007

2
Verifique também as outras enumerações de RefreshMode, como KeepCurrentValues. Observe que você deve chamar SubmitChanges novamente depois de usar essa lógica. Consulte msdn.microsoft.com/en-us/library/… .
— pwrgreg007

3

Não sei se você encontrou respostas satisfatórias para sua pergunta, mas postei uma pergunta semelhante e, eventualmente, respondi sozinho. Descobriu-se que a opção de conexão padrão NOCOUNT foi ativada para o banco de dados, o que causou uma ChangeConflictException para cada atualização feita com Linq para Sql. Você pode consultar minha postagem aqui .


3

Eu consertei isso adicionando (UpdateCheck = UpdateCheck.Never)a todas as [Column]definições.

Não parece uma solução apropriada, no entanto. No meu caso, parece estar relacionado ao fato de que esta tabela tem uma associação a outra tabela da qual uma linha é excluída.

Isso está no Windows Phone 7.5.


1

No meu caso, o erro foi gerado quando dois usuários com contextos de dados LINQ-to-SQL diferentes atualizaram a mesma entidade da mesma maneira. Quando o segundo usuário tentou a atualização, a cópia que eles tinham em seu contexto de dados estava desatualizada, embora tenha sido lida após a conclusão da primeira atualização.

Eu descobri a explicação e solução neste artigo de Akshay Phadke: https://www.c-sharpcorner.com/article/overview-of-concurrency-in-linq-to-sql/

Aqui está o código que eu mais elaborei:

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 olhei para minha janela de saída durante a depuração, pude ver que o valor atual correspondia ao valor do banco de dados. O "Valor Original" sempre foi o culpado. Esse foi o valor lido pelo contexto de dados antes de aplicar a atualização.

Obrigado ao MarceloBarbosa pela inspiração.


0

Eu sei que esta pergunta já foi respondida há muito tempo, mas aqui eu passei as últimas horas batendo minha cabeça contra a parede e eu só queria compartilhar minha solução que acabou não sendo relacionada a nenhum dos itens neste tópico:

Cache!

A parte select () do meu objeto de dados estava usando cache. Quando se tratava de atualizar o objeto, um erro de linha não encontrada ou alterada estava aparecendo.

Várias das respostas mencionaram o uso de diferentes DataContext e, em retrospecto, é provavelmente o que estava acontecendo, mas não me levou imediatamente a pensar em cache, então espero que isso ajude alguém!


0

Recentemente, encontrei esse erro e descobri que o problema não era com meu contexto de dados, mas com uma instrução de atualização disparada dentro de um gatilho depois que o commit foi chamado no contexto. O gatilho estava tentando atualizar um campo não anulável com um valor nulo e estava causando um erro no contexto com a mensagem mencionada acima.

Estou adicionando esta resposta apenas para ajudar outras pessoas a lidar com esse erro e não encontrar uma solução nas respostas acima.


0

Também recebi esse erro por usar dois contextos diferentes. Resolvi esse problema usando um único contexto de dados.


0

No meu caso, o problema estava nas opções do usuário em todo o servidor. Segue:

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

Ativei a opção NOCOUNT na esperança de obter alguns benefícios de desempenho:

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

e isso acaba quebrando as verificações do Linq para as linhas afetadas (tanto quanto eu posso descobrir a partir de fontes .NET), levando a ChangeConflictException

Redefinir as opções para excluir os 512 bits corrigiu o problema.


0

Depois de usar a resposta de qub1n, descobri que o problema para mim era que eu havia declarado inadvertidamente uma coluna do banco de dados como decimal (18,0). Eu estava atribuindo um valor decimal, mas o banco de dados o estava alterando, retirando a parte decimal. Isso resultou no problema de mudança de linha.

Basta adicionar isso se alguém tiver um problema semelhante.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.