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.

Kevin
fonte
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.

Sam
fonte
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
}
Tomas Kubes
fonte
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. :)

Matt Sherman
fonte
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.

Chris Moschini
fonte
10

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


fonte
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);
                }
            }
MarceloBarbosa
fonte
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 .

Michael Nero
fonte
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.

João paulo
fonte
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.

CAK2
fonte
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!

rtpHarry
fonte
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.

jamisonLikeCode
fonte
0

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

Srinivas vadlamudi
fonte
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.

Wojtek
fonte
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.

John Pasquet
fonte
0

basta ir com Linq2DB, muito melhor

nam vo
fonte