Pergunta

Ao fazer alterações usando SubmitChanges(), o LINQ às vezes morre com um ChangeConflictException exceção com a mensagem de erro Row not found or changed, sem qualquer indicação da linha que contém o conflito ou dos campos com alterações que estão em conflito, quando outro usuário alterou alguns dados nessa linha.

Existe alguma maneira de determinar qual linha tem um conflito e em quais campos eles ocorrem, e também há uma maneira de fazer com que o LINQ ignore o problema e simplesmente confirme os dados de qualquer maneira?

Além disso, alguém sabe se esta exceção ocorre quando qualquer os dados na linha foram alterados ou apenas quando os dados foram alterados em um campo que o LINQ está tentando alterar?

Foi útil?

Solução

Aqui está uma maneira de ver onde estão os conflitos (este é um exemplo do MSDN, então você precisará personalizar bastante):

try
{
    db.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e)
{
    Console.WriteLine("Optimistic concurrency error.");
    Console.WriteLine(e.Message);
    Console.ReadLine();
    foreach (ObjectChangeConflict occ in db.ChangeConflicts)
    {
        MetaTable metatable = db.Mapping.GetTable(occ.Object.GetType());
        Customer entityInConflict = (Customer)occ.Object;
        Console.WriteLine("Table name: {0}", metatable.TableName);
        Console.Write("Customer ID: ");
        Console.WriteLine(entityInConflict.CustomerID);
        foreach (MemberChangeConflict mcc in occ.MemberConflicts)
        {
            object currVal = mcc.CurrentValue;
            object origVal = mcc.OriginalValue;
            object databaseVal = mcc.DatabaseValue;
            MemberInfo mi = mcc.Member;
            Console.WriteLine("Member: {0}", mi.Name);
            Console.WriteLine("current value: {0}", currVal);
            Console.WriteLine("original value: {0}", origVal);
            Console.WriteLine("database value: {0}", databaseVal);
        }
    }
}

Para fazer com que ignore o problema e confirme mesmo assim:

db.SubmitChanges(ConflictMode.ContinueOnConflict);

Outras dicas

Estes (que você pode adicionar em uma classe parcial ao seu contexto de dados podem ajudá-lo a entender como isso funciona:

public void SubmitKeepChanges()
{
    try
    {
        this.SubmitChanges(ConflictMode.ContinueOnConflict);
    }
    catch (ChangeConflictException e)
    {
        foreach (ObjectChangeConflict occ in this.ChangeConflicts)
        {
            //Keep current values that have changed, 
//updates other values with database values

            occ.Resolve(RefreshMode.KeepChanges);
        }
    }
}

public void SubmitOverwrite()
{
    try
    {
        this.SubmitChanges(ConflictMode.ContinueOnConflict);
    }
    catch (ChangeConflictException e)
    {
        foreach (ObjectChangeConflict occ in this.ChangeConflicts)
        {
            // All database values overwrite current values with 
//values from database

            occ.Resolve(RefreshMode.OverwriteCurrentValues);
        }
    }
}

public void SubmitKeepCurrent()
{
    try
    {
        this.SubmitChanges(ConflictMode.ContinueOnConflict);
    }
    catch (ChangeConflictException e)
    {
        foreach (ObjectChangeConflict occ in this.ChangeConflicts)
        {
            //Swap the original values with the values retrieved from the database. No current value is modified
            occ.Resolve(RefreshMode.KeepCurrentValues);
        }
    }
}

Recebi esse erro em uma circunstância completamente não relacionada ao que a mensagem de erro descreve.

O que fiz foi carregar um objeto LINQ por meio de um DataContext e, em seguida, tentei SubmitChanges() para o objeto por meio de um DataContext diferente - ocorreu exatamente o mesmo erro.

O que eu tive que fazer foi chamar DataContext.Table.Attach(myOldObject) e depois chamar SubmitChanges(), funcionou perfeitamente.

Vale a pena dar uma olhada, especialmente se você acha que realmente não deveria haver nenhum conflito.

O erro "Linha não encontrada ou alterada" também aparecerá algumas vezes quando as colunas ou tipos no O/R-Designer não corresponderem às colunas no banco de dados SQL, especialmente se uma coluna for NULLable em SQL, mas não anulável no O/ R-Designer.

Portanto, verifique se o mapeamento da sua tabela no O/R-Designer corresponde ao seu banco de dados SQL!

Obrigado a @vzczc.Achei o exemplo que você deu muito útil, mas precisei chamar SubmitChanges novamente após resolver.Aqui estão meus métodos modificados - espero que ajude alguém.

    /// <summary>
    /// Submits changes and, if there are any conflicts, the database changes are auto-merged for 
    /// members that client has not modified (client wins, but database changes are preserved if possible)
    /// </summary>
    public void SubmitKeepChanges()
    {
        this.Submit(RefreshMode.KeepChanges);
    }

    /// <summary>
    /// Submits changes and, if there are any conflicts, simply overwrites what is in the database (client wins).
    /// </summary>
    public void SubmitOverwriteDatabase()
    {
        this.Submit(RefreshMode.KeepCurrentValues);
    }

    /// <summary>
    /// Submits changes and, if there are any conflicts, all database values overwrite
    /// current values (client loses).
    /// </summary>
    public void SubmitUseDatabase()
    {
        this.Submit(RefreshMode.OverwriteCurrentValues);
    }

    /// <summary>
    /// Submits the changes using the specified refresh mode.
    /// </summary>
    /// <param name="refreshMode">The refresh mode.</param>
    private void Submit(RefreshMode refreshMode)
    {
        bool moreToSubmit = true;
        do
        {
            try
            {
                this.SubmitChanges(ConflictMode.ContinueOnConflict);
                moreToSubmit = false;
            }
            catch (ChangeConflictException)
            {
                foreach (ObjectChangeConflict occ in this.ChangeConflicts)
                {
                    occ.Resolve(refreshMode);
                }
            }
        }
        while (moreToSubmit);

    }

linha não encontrada ou alterada é na maioria das vezes um problema de simultaneidade

Se um usuário diferente estiver alterando o mesmo registro, esses erros aparecerão porque o registro já foi alterado por esse outro usuário.Portanto, quando quiser eliminar esses erros, você deve lidar com a simultaneidade em seu aplicativo.Se você lidar bem com a simultaneidade, não receberá mais esses erros.Os exemplos de código acima são uma forma de lidar com erros de simultaneidade.O que falta é que em caso de erro de simultaneidade você deve colocar um refresh variável nesses métodos, então quando refresh é true os dados precisam ser atualizados na tela após a atualização para que você também veja a atualização feita pelo outro usuário.

    /// <remarks>
    ///     linq has optimistic concurrency, so objects can be changed by other users, while
    ///     submitted keep database changes but make sure users changes are also submitted
    ///     and refreshed with the changes already made by other users.
    /// </remarks>
    /// <returns>return if a refresh is needed.</returns>
    public bool SubmitKeepChanges()
    {
        // try to submit changes to the database.
        bool refresh = false;
        try
        {
            base.SubmitChanges(ConflictMode.ContinueOnConflict);
        }

        /* 
         * assume a "row not found or changed" exception, if thats the case:
         * - keep the database changes already made by other users and make sure
         * - this users changes are also written to the database
         */
        catch (ChangeConflictException)
        {
            // show where the conflicts are in debug mode
            ShowConflicts();

            // get database values and combine with user changes 
            base.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges);

            // submit those combined changes again to the database.
            base.SubmitChanges();

            // a refresh is needed
            refresh = true;
        }

        // return if a refresh is needed.
        return refresh;
    }

"e também existe uma maneira de fazer com que o LINQ ignore o problema e simplesmente confirme os dados de qualquer maneira?"

Você pode definir a propriedade 'Verificação de atualização' em sua entidade como 'Nunca' para impedir que esse campo seja usado para verificação de simultaneidade otimista.

Você também pode usar:

db.SubmitChanges(ConflictMode.ContinueOnConflict)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top