Back-End

1 jul, 2014

Entity Framework – Tratando o problema da concorrência de dados – Parte 03

Publicidade

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 está envolvida na verificação da concorrência;
  • Fixed – Este valor indica que o valor originada 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:

  • Para qualquer campo que você não definir corretamente o tratamento da concorrência não será verificada a concorrência;
  • Esta abordagem tende a fazer com que sua aplicação se torne mais lenta, pois a consulta resultante é maior que você usaria em uma estratégia usando a versão do registro.

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 e depois abra o Entity Data Model gerado e representando pelo arquivo Concorrencia.edmx; a seguir selecione a propriedade nome da entidade Cliente.

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

ef_tpc31

Repita esta operação selecionando os campos DataCompra e Quantidade na entidade Compra, 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:

ef_tpc33

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:

ef_tpc34

1. Selecione o formulário para o UsuarioA 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 UsuarioA.

ef_tpc35

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:

ef_tpc36

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;

Teremos as duas instâncias do formulário AtualizaRegistro, conforme mostra a figura a seguir:

ef_tpc34 (1)

1. Selecione o formulário para o UsuarioA 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 UsuarioA.

ef_tpc35 (1)

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;

ef_tpc37

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

ef_tpc38

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.