使用EF例外更新实体

时间:2018-08-01 14:22:03

标签: asp.net-mvc entity-framework entity-framework-6

我尝试更新实体,但收到此常见异常:

  

存储更新,插入或删除语句影响了意外   行数(0)。自此以来,实体可能已被修改或删除   实体已加载。看到   http://go.microsoft.com/fwlink/?linkid=472540,以获取有关的信息   了解和处理乐观并发异常。

我有此代码:

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

我不知道为什么会出现这种异常。我在另一个控制器中做了同样的事情,并且工作正常,但是现在,它并不想工作。

我尝试不更新该属性,但会出现相同的例外情况。

2 个答案:

答案 0 :(得分:0)

我发现了问题。我已经使用拦截器将数据放入审核列,但我不知道为什么它在这里不起作用:

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

但是我在另一个系统中使用了它,并且效果很好。我将分析为什么它在这里不起作用。

编辑

我找到了原因,但是我不知道为什么会如此。我将guid(外键)更改为int(pk)来搜索该行,它的工作原理就像一个超级按钮。奇怪,我将对此进行更多分析,以了解它不适用于guid。

答案 1 :(得分:0)

问题可能是(就像在我的项目中一样)使用returning的构造函数的DbUpdateCommandTree参数。如果您使用null并且在实体上计算了列(HasDatabaseGeneratedOption),则更新将失败并抛出DbUpdateConcurrencyException

尝试:

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