Dev (Back & Front)ARTIGO

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 dadosBanco 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:

ef_tpc61

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;

ef_tpc62

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.

ef_tpc63

Ainda na entidade Produto selecione a propriedade TimeStamp e altere sua propriedade Concurrency Mode para Fixed:

ef_tpc64

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:

ef_tpc65

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:

ef_tpc66

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.

é referência em Visual Basic no Brasil e autor dos livros "Aprenda Rápido: ASP" e "ASP, ADO e Banco de Dados na Internet". Mantenedor do site macoratti.net.

Ver perfil