Update entity with EF exceptions

asp.net-mvc entity-framework entity-framework-6

Question

I try to update an entity but I'm receiving this common exception:

store update, insert, or delete statement affected an unexpected number of rows (0). entities may have been modified or deleted since entities were loaded. see http://go.microsoft.com/fwlink/?linkid=472540 for information on understanding and handling optimistic concurrency exceptions.

I have this code:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Atualizar(TipoDocumentoViewModel model)
    {
        if (!ModelState.IsValid)
            return PartialView("_Atualizar", model);

        var entity = await DbContext.TipoDocumento.FirstAsync(x => x.ChaveExterna == model.Id);
        entity.Nome = model.Nome;
        var entry = DbContext.Entry(entity);
        entry.State = EntityState.Modified;
        try
        {
            await DbContext.SaveChangesAsync();
        }
        catch (DbUpdateException uex)
        {
            ModelState.AddModelError("", @"Houve um erro ao tentar executar a ação. Tente novamente mais tarde.");
            return PartialView("_Atualizar", model);
        }
        catch (DbEntityValidationException ex)
        {
            AddErrors(ex.EntityValidationErrors);
            return PartialView("_Atualizar", model);
        }
        catch (Exception ex)
        {
            ModelState.AddModelError("", @"Houve um erro ao tentar executar a ação. Tente novamente mais tarde.");
            return PartialView("_Atualizar", model);
        }

        return Json(new { });
    }

I don't know why I got this exception. I made the same thing in another controllers and works fine, but now, It doesn't want to work.

I try to not update the property and I'd the same exception.

1
0
8/1/2018 2:44:49 PM

Accepted Answer

I found the problem. I've interceptors to put data in audit columns and I don't know why It doesn't work here:

/// <summary>
///     Esse interceptor foi obtido desta url:
///     http://marisks.net/2016/02/27/entity-framework-soft-delete-and-automatic-created-modified-dates/
///     <para> Ele foi modificado para adicionar o ID do usuário e a data de Criação/Atualização/Exclusão</para>
///     Essas são as informações de auditoria que a aplicação é responsável em obter.
/// </summary>
public class EntityFrameworkAuditInterceptor : IDbCommandTreeInterceptor
{
    private const string CreateUserColumnName = "UsuarioCriacaoId";
    private const string CreateDateColumName = "DataCriacao";
    private const string UpdateUserColumnName = "UsuarioEdicaoId";
    private const string UpdateDateColumnName = "DataEdicao";
    private const string DeleteUserColumnName = "UsuarioExclusaoId";
    private const string DeleteDateColumnName = "DataExclusao";
    private const string DeletedColumnName = "Deletado";

    public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
    {
        if (interceptionContext.OriginalResult.DataSpace != DataSpace.SSpace)
            return;

        if (interceptionContext.Result is DbQueryCommandTree queryCommand)
            interceptionContext.Result = HandleQueryCommand(queryCommand);

        else if (interceptionContext.Result is DbInsertCommandTree insertCommand)
            interceptionContext.Result = HandleInsertCommand(insertCommand);

        else if (interceptionContext.OriginalResult is DbUpdateCommandTree updateCommand)
            interceptionContext.Result = HandleUpdateCommand(updateCommand);

        else if (interceptionContext.OriginalResult is DbDeleteCommandTree deleteCommand)
            interceptionContext.Result = HandleDeleteCommand(deleteCommand);
    }

    private static DbCommandTree HandleInsertCommand(DbInsertCommandTree insertCommand)
    {
        var userId = Convert.ToInt32(((ClaimsIdentity)Thread.CurrentPrincipal.Identity).Claims
                                     .FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value ?? "0");
        var setClauses = insertCommand.SetClauses
            .Select(clause => clause.UpdateIfMatch(CreateUserColumnName, DbExpression.FromInt32(userId)))
            .Select(clause => clause.UpdateIfMatch(CreateDateColumName, DbExpression.FromDateTime(DateTime.Now)))
            .ToList();

        return new DbInsertCommandTree(insertCommand.MetadataWorkspace, insertCommand.DataSpace,
            insertCommand.Target, setClauses.AsReadOnly(), insertCommand.Returning);
    }

    private static DbCommandTree HandleUpdateCommand(DbUpdateCommandTree updateCommand)
    {
        var userId = Convert.ToInt32(((ClaimsIdentity)Thread.CurrentPrincipal.Identity).Claims
                                     .FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value ?? "0");
        var setClauses = updateCommand
            .SetClauses
            .Select(clause => clause.UpdateIfMatch(UpdateUserColumnName, DbExpression.FromInt32(userId)))
            .Select(clause => clause.UpdateIfMatch(UpdateDateColumnName, DbExpression.FromDateTime(DateTime.Now)))
            .ToList();

        return new DbUpdateCommandTree(updateCommand.MetadataWorkspace, updateCommand.DataSpace,
            updateCommand.Target, updateCommand.Predicate, setClauses.AsReadOnly(), null);
    }

    private static DbCommandTree HandleDeleteCommand(DbDeleteCommandTree deleteCommand)
    {
        var setClauses = new List<DbModificationClause>();
        var table = (EntityType)deleteCommand.Target.VariableType.EdmType;

        if (table.Properties.All(p => p.Name != DeletedColumnName))
            return deleteCommand;
        var userId = Convert.ToInt32(((ClaimsIdentity)Thread.CurrentPrincipal.Identity).Claims
                                     .FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value ?? "0");

        setClauses.Add(DbExpressionBuilder.SetClause(
            deleteCommand.Target.VariableType.Variable(deleteCommand.Target.VariableName)
                .Property(DeleteUserColumnName), DbExpression.FromInt32(userId)));
        setClauses.Add(DbExpressionBuilder.SetClause(
            deleteCommand.Target.VariableType.Variable(deleteCommand.Target.VariableName)
                .Property(DeleteDateColumnName), DbExpression.FromDateTime(DateTime.Now)));
        setClauses.Add(DbExpressionBuilder.SetClause(
            deleteCommand.Target.VariableType.Variable(deleteCommand.Target.VariableName)
                .Property(DeletedColumnName), DbExpression.FromBoolean(true)));

        return new DbUpdateCommandTree(deleteCommand.MetadataWorkspace, deleteCommand.DataSpace,
            deleteCommand.Target, deleteCommand.Predicate, setClauses.AsReadOnly(), null);
    }

    private static DbCommandTree HandleQueryCommand(DbQueryCommandTree queryCommand)
    {
        var newQuery = queryCommand.Query.Accept(new SoftDeleteQueryVisitor());
        return new DbQueryCommandTree(queryCommand.MetadataWorkspace, queryCommand.DataSpace, newQuery);
    }

    private class SoftDeleteQueryVisitor : DefaultExpressionVisitor
    {
        public override DbExpression Visit(DbScanExpression expression)
        {
            var table = (EntityType)expression.Target.ElementType;
            if (table.Properties.All(p => p.Name != DeletedColumnName))
                return base.Visit(expression);

            var binding = expression.Bind();
            return binding.Filter(binding.VariableType.Variable(binding.VariableName).Property(DeletedColumnName)
                .NotEqual(DbExpression.FromBoolean(true)));
        }
    }
}

But I use this in another systems and it works fine. I'll analyze why It doesn't work here.

EDIT

I found the reason, but I don't know why it's happining. I change the guid (external key) for int (pk) to search the row, and it works like a charm. It's strange, I will analyze this more to know what it doesn't work with guid.

0
8/5/2018 5:35:27 PM

Popular Answer

The problem could be (as it was in my project) with the returning parameter of the constructor of DbUpdateCommandTree. If you use null and you have computed columns (HasDatabaseGeneratedOption) on the entity, the update will fail and throw an DbUpdateConcurrencyException.

Try:

return new DbUpdateCommandTree(
    updateCommand.MetadataWorkspace,
    updateCommand.DataSpace,
    updateCommand.Target,
    updateCommand.Predicate,
    setClauses.AsReadOnly(),
    updateCommand.Returning);


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow