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;
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:
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 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.
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;
Teremos as duas instâncias do formulário AtualizaRegistro, conforme mostra a figura a seguir:
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.
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.