Entity Framework – Tratando o problema da concorrência de dados – Aplicando a concorrência otimista – Parte 06
Neste artigo vamos mostrar como podemos resolver os problemas relacionados com a concorrência envolvendo o Entity Framework usando a concorrência otimista.
Vamos concluir esta série de artigos mostrando uma aplicação da concorrência otimista. As preocupações com a concorrência em torno do acesso compartilhado dos arquivos de dados são muitas vezes o motivo pelo qual os desenvolvedores se voltam para sistemas de banco de dados↳Banco de dados134 conteúdosSQL ou NoSQL: eis a questão!!Data · mar 2020Banco de dados: como organizar e dar segurança para milhões de dados de loteriasData · mai 20215 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data → relacionais para suportar suas aplicações.
Muitas das preocupações de concorrência se dissipam quando um aplicativo se baseia em um banco de dados relacional para o seu armazenamento de dados. As preocupações que permanecem geralmente envolvem a detecção e controle quando o estado de um objeto é diferente na memória do que na base de dados.
Nestes casos, ocorre uma competição pela atualização da informação e pode haver conflitos que devem ser tratados.
Neste artigo vamos mostrar como podemos resolver os problemas relacionados com a concorrência envolvendo o Entity Framework usando a concorrência otimista.
Recursos usados :
- Visual Studio 2012 Express for Windows Desktop
- Entity Framework 6.0
Aplicando a concorrência otimista
Vamos supor que temos um modelo conforme mostrado na figura abaixo:
A entidade Produto descreve os produtos em sua aplicação. Você quer lançar uma exceção se ocorrer uma atualização entre o momento em que você recuperar uma entidade produto e o tempo no qual uma atualização for realizada no banco de dados.
Para implementar esse comportamento, faça o seguinte:
1. Na janela DataBase Explorer selecione a tabela Produtos e inclua uma coluna com o nome TimeStamp e com o tipo de dados rowversion;
A seguir, abra o descritor do Entity Data Model que exibe a entidade Produto e clique com o botão direito do mouse sobre a entidade e, a seguir, clique em Update Model from Database.
Ainda na entidade Produto selecione a propriedade TimeStamp e altere sua propriedade Concurrency Mode para Fixed:
Agora vamos definir o código abaixo em nosso projeto:
using System;
using System.Data.Entity.Infrastructure;
using System.Linq;
namespace EF_ConcorrenciaOtimista
{
class Program
{
static void Main(string[] args)
{
using (var context = new CadastroEntities())
{
context.Produtos.Add(new Produto
{
Nome = "Calculadora HP 12C",
Preco = 199.95M
});
context.SaveChanges();
}
using (var context = new CadastroEntities())
{
// pega um produto
var produto = context.Produtos.SingleOrDefault(c=> c.Id == 5);
Console.WriteLine("{0} Preco: {1}", produto.Nome, produto.Preco.ToString());
// atualiza por fora
context.Database.ExecuteSqlCommand(@"update produtos set preco = 229.95 where Id = @p0", produto.Id);
// atualiza o poduto via Model
produto.Preco = 239.95M;
Console.WriteLine("Alterando preço de {0} para {1}", produto.Nome, produto.Preco.ToString());
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
Console.WriteLine("Exceção de Concorrência! {0}", ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Exceção {0}", ex.Message);
}
}
}
}
}
Executando o projeto iremos obter o seguinte resultado:
Vamos ver entender o que esta acontecendo:
A concorrência otimista é uma estratégia de concorrência de baixa contenção porque os registros não são bloqueados quando eles são atualizados. A desvantagem da concorrência otimista é a grande probabilidade de ocorrer uma sobreposição de dados já atualizados, caso o aplicativo não verifique se há alterações nos dados antes de atualizar o banco.
No código acima incluímos um novo produto no banco de dados e logo a seguir realizamos uma consulta para obter o projeto que foi incluído. A seguir realizamos uma atualização do produto usando o método ExecuteSqlCommand() para enviar uma instrução SQL update para o banco de dados alterar a linha.
Esta atualização simula dois usuários atualizando a mesma linha simultaneamente. Do lado do banco de dados esta atualização altera o preço do produto para R$ 229,95 e a colunaTimeStamp de forma automática no banco de dados. Logo após alteramos o preço do produto via contexto para R$ 239,95, neste ponto o contexto do banco de dados acredita (de forma incorreta) que ele possui os valores mais recentes para o produto, incluindo uma atualização do preço do produto em 239,95.
Quando invocamos o método SaveChanges(), o Entity Framework gera uma instrução update com uma cláusula Where que inclui os valores do Id do produto e do TimeStamp que temos para o produto. O valor para este TimeStamp é aquele retornado quando nós lemos o produto do banco de dados antes da primeira atualização.
Como esta atualização alterou a coluna TimeStamp, o valor desta coluna no banco de dados vai estar diferente do valor da propriedade TimeStamp na entidade Produto no contexto do banco de dados. A instrução irá falhar porque nenhuma linha será encontrada na tabela com o mesmo valores de Id e TimeStamp. O Entity Framework irá responder realizando um roll back da transação inteira e lançar a exceção DbUpdateConcurrencyException.
Aplicação exibe a mensagem correspondente a esta exceção.
Como podemos tratar este problema de concorrência?
O objeto DbUpdateConcurrencyException possui uma propriedade Entries, que contém uma instância de dbEntityEntry para cada entidade que falhar na atualização.
A classe DbEntityEntry define o método Reload(), que fará com que a entrada seja atualizada com os valores do banco de dados e todas as alterações feitas para a entrada no contexto do banco de dados serão desprezadas.
É possível, no entanto, sobrescrever a propriedade OriginalValues da entrada, chamando SaveChanges() no contexto do banco de dados sem violar a concorrência.
O código abaixo mostra como contornar a violação da concorrência :
using System;
using System.Data.Entity.Infrastructure;
using System.Linq;
namespace EF_ConcorrenciaOtimista
{
class Program
{
static void Main(string[] args)
{
using ( var context = new CadastroEntities())
{
context.Produtos.Add(new Produto
{
Nome = "Calculadora HP 12C",
Preco = 199.95M
});
context.SaveChanges();
}
using (var context = new CadastroEntities())
{
// pega o produto
var produto = context.Produtos.SingleOrDefault(c=> c.Id==6);
Console.WriteLine("{0} Preco: {1}", produto.Nome, produto.Preco.ToString());
// atualiza por fora
context.Database.ExecuteSqlCommand(@"update produtos set preco = 229.95 where Id = @p0", produto.Id);
// atualiza o poduto via Model
produto.Preco = 239.95M;
Console.WriteLine("Alterando preço de {0} para {1}", produto.Nome, produto.Preco.ToString());
bool saveChangesFalhou;
do
{
saveChangesFalhou = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
//Console.WriteLine("Exceção de Concorrência! {0}", ex.Message);
saveChangesFalhou = true;
var entry = ex.Entries.Single();
entry.OriginalValues.SetValues(entry.GetDatabaseValues());
Console.WriteLine("Exceção de Concorrência tratada");
}
catch (Exception ex)
{
Console.WriteLine("Exceção {0}", ex.Message);
}
} while (saveChangesFalhou);
}
Console.ReadKey();
}
}
}
Abaixo vemos o resultado da execução do projeto com o código acima:
Pegue o projeto completo aqui: EF_ConcorrenciaOtimista.zip (sem as referências).
Concluímos aqui essa série de artigos sobre o tratamento da concorrência usando o Entity Framework. Lembrando que o assunto não foi esgotado; pelo contrário: trouxemos à tona apenas os problemas mais comuns envolvidos na concorrência.
Espero que essa série de artigos tenha chamado a sua atenção para o problema da concorrência, encorajando-o a saber mais sobre o assunto.














