Neste artigo vou apresentar os conceitos sobre a concorrência de dados na utilização do Entity Framework (se quiser ver as partes anteriores, acesse este link).
O Entity Framework suporta a concorrência otimista por padrão, e, neste modelo, o EF salva a entidade para o banco de dados assumindo que os dados não foram alterados desde que a entidade foi carregada. Se os dados forem alterados, será lançada uma exceção e você precisa resolver o conflito antes de tentar salvar novamente.
Vamos ver como tratar a concorrência otimista usando a entidade Aluno do nosso modelo de entidades, definido na aula 03 do curso.
Vamos incluir uma coluna com o nome RowVersion do tipo timestamp na tabela Aluno para tratar a concorrência com a entidade Aluno.
Nota: o valor de RowVersion será incrementado automaticamente pelo banco de dados durante a inclusão ou atualização.
O RowVersion é um tipo de dados no SQL Server que gera automaticamente números binários únicos sempre que uma operação de atualização ou inclusão for realizada na tabela. Assim, o tipo de dados rowversion é apenas um número auto incrementado sendo similar ao tipo de dados timestamp.
Após incluir a coluna tabela, vamos atualizar o nosso modelo de entidades. Abra o modelo e clique com o botão direito sobre a entidade Aluno e selecione a opção Update Model From DataBase e, a seguir, selecione a guia Refresh e clique no botão Finish:
Após isso você deverá ver a propriedade RowVersion mapeada para a entidade Aluno:
Agora precisamos definir a propriedade Concurrency Mode para Fixed.
Clique na propriedade RowVersion na entidade Aluno e na janela de propriedades altere o valor da propriedade Concurrency Mode de None para Fixed.
Agora o Entity Framework irá incluir a coluna RowVersion na cláusula Where sempre que você realizar uma operação de atualização de dados e, se o valor de rowversion for diferente do valor da cláusula where, então, será lançada a exceção DbUpdateConcurrencyExection.
Vamos fazer um teste usando um cenário onde o Usuario1 e Usuario2, obtendo o mesmo aluno e atualizando o nome do aluno ao mesmo tempo.
Preparando o ambiente
Vamos usar a solução criada nesta aula para testar o cenário da concorrência descrito acima.
Abra a solução EF6_EscolaDB e, a seguir, clique no menu FILE -> Add -> New Project.
Selecione a linguagem Visual C# e o template Console Application, informando, em seguida, o nome EF6_TestandoConcorrencia:
Para referenciar o EF no projeto incluído, clique, no menu TOOLS, em Nuget Package Manager -> Manage Nuget Packages for solution.
A seguir, clique em Installed packages, depois no botão Manage e marque o projeto que incluímos. A seguir, no botão OK.
A seguir, inclua uma referência neste projeto ao projeto EF6_EscolaDB e atualize também o arquivo App.Config com a string de conexão para a entidade:
... <connectionStrings> <add name="EscolaDBEntities" connectionString="metadata=res://*/EscolaDB.csdl|res://*/EscolaDB.ssdl|res://*/EscolaDB.msl;provider=System.Data.SqlClient;provider connection string="data source=.\SQLEXPRESS;initial catalog=EscolaDB;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" /></connectionStrings> ...
Testando a concorrência
A seguir, no método Main() do arquivo Program.cs, inclua o código abaixo:
using EF6_EscolaDB; using System.Linq; namespace EF6_TestandoCorrencia { class Program { static void Main(string[] args) { Aluno Usuario1 = null; Aluno Usuario2 = null; //Usuario1 pega o Aluno using (var context = new EscolaDBEntities()) { context.Configuration.ProxyCreationEnabled = false; Usuario1 = context.Alunos.Where(s => s.AlunoId == 1).Single(); } //Usuario2 pega o mesmo Aluno using (var context = new EscolaDBEntities()) { context.Configuration.ProxyCreationEnabled = false; Usuario2 = context.Alunos.Where(s => s.AlunoId == 1).Single(); } //Usuario1 atualiza o nome do aluno Usuario1.AlunoNome = "Editado pelo Usuario1"; //Usuario2 também atualiza o nome do aluno Usuario2.AlunoNome = "Editado pelo Usuario2"; } } }
Vamos agora concluir o cenário, definindo que o Usuario1 salve as alterações antes do Usuario2. Dessa forma, quando o Usuario2 tentar salvar suas alterações, ele vai obter um exceção causada pela concorrência.
Inclua o código destacado em negrito abaixo para refletir esse cenário:
using EF6_EscolaDB; using System; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq; namespace EF6_TestandoCorrencia { class Program { static void Main(string[] args) { Aluno Usuario1 = null; Aluno Usuario2 = null; //Usuario1 pega o Aluno using (var context = new EscolaDBEntities()) { context.Configuration.ProxyCreationEnabled = false; Usuario1 = context.Alunos.Where(s => s.AlunoId == 1).Single(); } //Usuario2 pega o mesmo Aluno using (var context = new EscolaDBEntities()) { context.Configuration.ProxyCreationEnabled = false; Usuario2 = context.Alunos.Where(s => s.AlunoId == 1).Single(); } //Usuario1 atualiza o nome do aluno Usuario1.AlunoNome = "Editado pelo Usuario1"; //Usuario2 também atualiza o nome do aluno Usuario2.AlunoNome = "Editado pelo Usuario2"; //-------------------------------------------------------------- //Usuario1 salva as alterações primeiro <strong>using (var context = new EscolaDBEntities())</strong> <strong> {</strong> <strong> try</strong> <strong> {</strong> <strong> context.Entry(Usuario1).State = EntityState.Modified;</strong> <strong> context.SaveChanges();</strong> <strong> }</strong> <strong> catch (DbUpdateConcurrencyException ex)</strong> <strong> {</strong> <strong> Console.WriteLine("Usuario1 :: Ocorreu uma exceção de concorrência Otimista : " + ex.Message);</strong> <strong> }</strong> <strong> }</strong> //Usuario2 salva as alterações depois do Usuario1 //Usuario2 vai obter uma exceção de concorrência //porque CreateOrModifiedDate é diferente no banco de dados <strong>using (var context = new EscolaDBEntities())</strong> <strong> {</strong> <strong> try</strong> <strong> {</strong> <strong> context.Entry(Usuario2).State = EntityState.Modified;</strong> <strong> context.SaveChanges();</strong> <strong> }</strong> <strong> catch (DbUpdateConcurrencyException ex)</strong> <strong> {</strong> <strong> Console.WriteLine("Usuario2 :: Ocorreu uma exceção de concorrência Otimista : " + ex.Message);</strong> <strong> }</strong> <strong> }</strong> } } }
Executando o projeto iremos obter uma exceção exibida no console ocorrida com o Usuario2:
Na próxima aula veremos como usar stored procedures no Entity Framework.