Entity Framework - Tratando o problema da concorrência de dados - III


Na segunda parte deste artigo apresentamos como podemos realizar o  tratamento da concorrência com foco no Entity Framework tratando a alteração dos dados.

Vamos continuar mostrando como implementar o tratamento da concorrência onde iremos realizar o tratamento a nível de campos específicos.

Usando o concorrência a nível de campo (Concurrency Mode)

É possível dizer ao Entity Framework para verificar a concorrência para você, e uma maneira de fazer isso é definir a propriedade Concurrency Mode para cada campo que você deseja verificar a concorrência.

A propriedade Concurrency Mode de uma entidade pode ter dois valores:
  • None – É o valor padrão e indica que a propriedade da entidade não esta envolvida na verificação da concorrência;
  • Fixed – Este valor indica que o valor origina da propriedade desta entidade será enviada como parte da cláusula WHERE em todas as atualizações e exclusões envolvendo este campo;

Este método funciona relativamente bem mesmo se o GBDR (DBMS) não suportar a estratégia da versão do registro (row version) mas apresenta os seguintes efeitos colaterais:

Mesmo assim essa abordagem é válida e você pode usá-la mesmo se o banco de dados não estiver configurado para o tratamento da concorrência a nível de versão de linha(registro).

Tudo o que você precisa fazer é modificar um pouco o modelo.

Vejamos a seguir os procedimentos para realizar esta tarefa.

Vamos continuar usando o nosso exemplo criado no primeiro artigo. Abra o projeto EF_TestandoConcorrencia no VS 2012 Express for desktop.

Abra o Entity Data Model gerado e representando pelo arquivo Concorrencia.edmx e a seguir selecione a propriedade nome da entidade Cliente;

A seguir na janela de propriedades defina o valor para Concorrency Mode como Fixed;

Repita esta operação selecionando os campos DataCompra e Quantidade na entidade Compra e definindo o valor de cada propriedade Concurrency Mode para Fixed (faça uma de cada vez).

Selecione o formulário form1.cs e inclua um novo botão de comando com name = btnVersaoLinha e Text = Concorrência - Versão de Linha conforme mostra a figura abaixo:

Agora clique duas vezes sobre o botão de comando incluído e no evento Click inclua o seguinte código:

   private void btnVersaoLinha_Click(object sender, EventArgs e)
  {
            // Cria um contexto para cada usuário
            ConcorrenciaContainer context1 = new ConcorrenciaContainer();
            ConcorrenciaContainer context2 = new ConcorrenciaContainer();
            // Pega o registro para o usuario A
            var UsuarioA = context1.Compras.First();
            // Pega o registro para o usuario B
            var UsuarioB = context2.Compras.First();
            // Realiza uma alteração e a salva para o usuario A
            UsuarioA.Quantidade = Convert.ToDecimal(1.00);
            context1.SaveChanges();
            // Realiza uma alteração e a salva para o usuario A
            UsuarioB.Quantidade = Convert.ToDecimal(2.00);
            context2.SaveChanges();
            // Exibe a mensagem de sucesso
            MessageBox.Show("Atualização realizada com sucesso !");
   }

Ao testar este tipo de concorrência esteja certo de que cada usuário possua um contexto diferente para utilizar. Observe que os usuários estão alterando o mesmo campo e que as alterações feitas pelo UsuarioA irão aparecer no banco de dados antes do UsuarioB iniciar a sua alteração. É importante que a alteração tenha sido salva para atualizar a concorrência e dessa forma o UsuarioB irá visualizar um registro diferente.

Vamos ver então como funciona :

Execute o projeto e clique no botão - Testando a Concorrência. Teremos as duas instâncias do formulário AtualizaRegistro conforme mostra a figura a seguir:

 

1- Selecione o formulário para o Usuario A e altere a quantidade de 1.99 para 4.99. Clique no botão - Concorrência a nível de Campo:

Você verá uma caixa de diálogo contendo o valor atual do banco de dados. Note que o valor da  quantidade agora é 4.99 o que confere com o valor atualizado pelo Usuario A.

2- Clique no botão OK para fechar a caixa de mensagem;

3- Clique no botão Cancela das duas janelas de forma a fechar as duas instâncias do formulário AtualizaRegistro.cs e retornar ao formulário Form1.cs;

4- No formulário Form1.cs clique no botão - Concorrência - Versão de Linha;

5- Você verá uma mensagem de erro não tratado exibindo a exceção DbUpdateConcurrencyException conforme a figura abaixo:

Interrompa a execução da aplicação e altere o código do evento Click do botão  Concorrência - Versão de Linha do formulário Form1.cs conforme abaixo:

 private void btnVersaoLinha_Click(object sender, EventArgs e)
        {
            // Cria um contexto para cada usuário
            ConcorrenciaContainer context1 = new ConcorrenciaContainer();
            ConcorrenciaContainer context2 = new ConcorrenciaContainer();
            // Pega o registro para o usuario A
            var UsuarioA = context1.Compras.First();
            // Pega o registro para o usuario B
            var UsuarioB = context2.Compras.First();

            // Realiza uma alteração e a salva para o usuario A
            UsuarioA.Quantidade = Convert.ToDecimal(1.00);
            context1.SaveChanges();

            try
            {
                // Realiza uma alteração e a salva para o usuario A
                UsuarioB.Quantidade = Convert.ToDecimal(2.00);
                context2.SaveChanges();
            }
            catch (DbUpdateConcurrencyException DUCEx)
            {
                // Display a message box.
                MessageBox.Show("Tentativa de alteração falhou !!!");
                // Obtém o contexto do objeto
                var ObjContext = ((IObjectContextAdapter)context2).ObjectContext;
                // Obtém a entra que falhou
                var Entry = DUCEx.Entries.Single();
                // Atualia o contexto do objeto de forma que você possa realizar a atualização
                ObjContext.Refresh(RefreshMode.ClientWins, Entry.Entity);

                // Salva as alterações
                context2.SaveChanges();
            }

            // Exibe a mensagem de sucesso
            MessageBox.Show("Atualização realizada com sucesso !");
        }

Você deve acrescentar também os seguintes namespaces no formulário Form1.cs:

using System.Data.Entity.Infrastructure;
using
System.Data.Objects;

Na versão 6.0 do Entity Framework (esta é a versão que estou usando) afim de atualizar o registro você tem que atualizar o contexto do objeto.

A exceção DbUpdateConcurrencyException disponibiliza a você a entidade que falhou durante a atualização e quando você chama o método Refresh você tem que fornecer uma estratégia para atualizar o banco de dados.

O passo final é chamar o método SaveChanges() e neste ponto a atualização será bem sucedida com uma abordagem client-wins.

Em um sistema projetado para alertá-lo dos conflitos, o sistema irá alertar quando o usuário for tentar salvar seus dados, indicando que o registro foi modificado desde o tempo em que foi inicialmente recuperado.

Essa abordagem é conhecida como ClientWins e não ignora a concorrência; neste cenário o EF emite um comando para atualizar TODOS os valores da entidade, mesmo aqueles que não tenham sido editados.

O impacto desta abordagem no cenário descrito do item anterior é que o campo X e todos os outros campos no registro do banco de dados serão alterados para refletir a versão do usuário dos dados.

Pressione F5 para executar a aplicação novamente;

Testando a Concorrência. Teremos as duas instâncias do formulário AtualizaRegistro conforme mostra a figura a seguir:

 

1- Selecione o formulário para o Usuario A e altere a quantidade de 1.99 para 4.99. Clique no botão - Concorrência a nível de Campo:

Você verá uma caixa de diálogo contendo o valor atual do banco de dados. Note que o valor da  quantidade agora é 4.99 o que confere com o valor atualizado pelo Usuario A.

2- Clique no botão OK para fechar a caixa de mensagem;

3- Clique no botão Cancela das duas janelas de forma a fechar as duas instâncias do formulário AtualizaRegistro.cs e retornar ao formulário Form1.cs;

4- No formulário Form1.cs clique no botão - Concorrência - Versão de Linha;

5- Você verá uma caixa de mensagem informando que a tentativa inicial de atualização falhou;

 

Clicando no botão OK você verá uma nova caixa de mensagem informando que a atualização foi realizada com sucesso !
 

 

Isso foi possível graças ao tratamento da exceção que fizemos usando a estratégia de concorrência a nível de campo usando a propriedade Concurrency Mode igual a Fixed.

 

Na continuação deste artigo  irei abordar a concorrência a nível de registro usando rowversion.

Joã 3:26 E foram ter com João e disseram-lhe: Rabi, aquele que estava contigo além do Jordão, do qual tens dado testemunho, eis que está batizando, e todos vão ter com ele.

Joã 3:27 Respondeu João: O homem não pode receber coisa alguma, se não lhe for dada do céu.

Joã 3:28 Vós mesmos me sois testemunhas de que eu disse: Não sou o Cristo, mas sou enviado adiante dele.

Referências:


José Carlos Macoratti