Entity Framework - Tratando o problema da concorrência de dados - Aplicando a concorrência otimista - (final)


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 relacionais para suportar suas aplicações.

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

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 um 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, se o aplicativo não verificar se há alterações nos dados antes de atualizar o banco de dados.

 

No código acima incluímos um novo produto no banco de dados e logo a seguir realizando 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 coluna TimeStamp 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 a qual 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 destacado em azul 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 assim 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 com essa série de artigos tenha chamada a sua atenção para o problema da concorrência encorajando-o a saber mais sobre o assunto.

Gálatas 6:2 Levai as cargas uns dos outros, e assim cumprireis a lei de Cristo.
Gálatas 6:3
Pois, se alguém pensa ser alguma coisa, não sendo nada, engana-se a si mesmo.

Referências:


José Carlos Macoratti