Neste artigo vamos mostrar como atualizar um grafo de entidades (um conjunto de entidades ou objetos) no cenário desconectado usando o DBContext (veja aula anterior).
Atualizar um grafo de entidades com todas as novas entidades é uma tarefa complexa no cenário desconectado.
O problema na atualização de um grafo de entidades no cenário desconectado é que o contexto não conhece qual operação foi realizada do lado do cliente.
Na figura abaixo temos a representação desse fato, onde o contexto não conhece o estado de cada entidade após uma operação ter sido realizada no cliente:
Precisamos identificar o estado de cada entidade no grafo de entidades antes de chamar o método SaveChanges().
Existem diferentes padrões para identificar o estado de uma entidade que precisamos considerar na concepção da camada de dados do Entity Framework.
Padrões para identificar o estado de uma entidade no cenário desconectado
Existem diversas formas de identificar o estado de uma entidade em um cenário desconectado. Dentre elas citamos:
-
Usar a propriedade Primarykey (Id) de uma entidade;
-
Usando a propriedade state no conjunto de entidades (entity set);
Nota: Você pode usar o seu próprio método para identificar o estado da entidade baseado na sua arquitetura.
1. Usando a propriedade Id (primary Key) de uma entidade
Você pode usar a chave primária – PrimaryKey (Id) de cada entidade para determinar o seu estado. Porém, você tem que decidir qual das seguintes regras de arquitetura vai usar:
-
Cada tipo de entidade deve possuir uma propriedade Id (chave primária);
-
O valor padrão da propriedade Id deve ser 0.
A seguir, temos a figura que mostra como usar a propriedade Id para verificar o estado das entidades no cenário desconectado:
No cenário desconectado, o Contexto 2 não conhece o estado de cada entidade. Ele tem que determinar o estado da entidade padrão usando a propriedade PadraoId e o estado do Professor usando a propriedade ProfessorId.
Se os valores de PadraoId e ProfessorId forem iguais a zero, isso significa que elas são uma nova entidade e se o valor não for zero, então, significa que as entidades foram modificadas.
Na figura temos que Padrao, Professor 1 e Professor 2 possuem um valor diferente de zero para a propriedade Id, de forma que eles são marcados como Modified e o Professor 3 possui o valor zero para o Id e, assim, ele é marcado como Added.
A seguir veremos o código que representa essa operação.
Atualizando um grafo de entidades aluno usando o Id
Vamos usar a solução criada na aula anterior.
Abra a solução e no arquivo Program.cs e inclua o código abaixo neste arquivo:
static void Atualizando_GrafoEntidades() { Padrao PadraoDesconectado = null; using (var contexto = new EscolaDBEntities()) { // contexto.Configuration.ProxyCreationEnabled = false; // PadraoDesconectado = contexto.Padraos.Where(s => s.PadraoId == 58).Include(s => s.Professores).FirstOrDefault<Padrao>(); } // Atualiza a entidade Padrao no modo desconectado PadraoDesconectado.PadraoNome = "Nome do Padrão editado e alterado"; // Atualiza a coleção Professores editando o primeiro o professor e depois incluindo um novo professor PadraoDesconectado.Professores.ElementAt(0).ProfessorNome = "Nome do professor Alterado"; PadraoDesconectado.Professores.Add(new Professor() { ProfessorNome = "Novo Professor", PadraoId = PadraoDesconectado.PadraoId }); using (var novoContexto = new EscolaDBEntities()) { //marca o padrao baseado no PadraoId novoContexto.Entry(PadraoDesconectado).State = PadraoDesconectado.PadraoId == 0 ? EntityState.Added : EntityState.Modified; //marca o Professor baseado no PadraodId foreach (Professor tchr in PadraoDesconectado.Professores) novoContexto.Entry(tchr).State = tchr.ProfessorId == 0 ? EntityState.Added : EntityState.Modified; novoContexto.SaveChanges(); } }
Vejamos quais as vantagens dessa abordagem:
- Não precisamos código ou processamento extra para determinar o estado da entidade;
- Bom desempenho.
E as desvantagens são:
- Cada tipo de entidade precisa possuir uma propriedade Id;
- Uma entidade não modificada (Unchanged) não pode ser determinada. Ela será definida como Modified mesmo se a entidade não for alterada, de forma que haverá uma instrução update para cada entidade não modificada no banco de dados;
- Não é possível tratar uma entidade deletada. A exclusão deve ser trata separadamente.
2. Usando a propriedade State em cada entidade
Outra maneira de determinar o estado de uma entidade é ter uma propriedade State em cada tipo de entidade e definir o estado apropriado a partir do lado do cliente no modo desconectado. Dessa forma, precisamos definir o estado da entidade como a propriedade State do objeto da entidade e chamar o método SaveChanges no novo Contexto.
Primeiro vamos criar uma interface IEntityObjectState com uma propriedade enum chamada EntityObjectState:
public enum EntityObjectState { Added, Modified, Deleted, Unchanged } interface IEntityObjectState { EntityObjectState ObjectState { get; set; } }
A seguir, vamos implementar a interface IEntityObjectSate em cada entidade. Abaixo vemos um exemplo para as entidades Padrao e Professor:
public partial class Padrao : IEntityObjectState { public Padrao() { this.Alunos = new HashSet<Aluno>(); this.Professores = new HashSet<Professor>(); } public int PadraoId { get; set; } public string PadraoNome { get; set; } public string Descricao { get; set; } public virtual ICollection<Aluno> Alunos { get; set; } public virtual ICollection<Professor> Professores { get; set; } [NotMapped] public EntityObjectState ObjectState { get; set; } } public partial class Professor : IEntityObjectState { public Professor() { this.Cursos = new HashSet<Curso>(); } public int ProfessorId { get; set; } public string ProfessorNome { get; set; } public Nullable<int> PadraoId { get; set; } public virtual ICollection<Curso> Cursos { get; set; } public virtual Padrao Padrao { get; set; } [NotMapped] public EntityObjectState ObjectState { get; set; } }
Defina a propriedade State no modo desconectado do lado do cliente e defina o estado da entidade como definido por ObjectState antes de chamarSaveChanges:
static void Atualizando_GrafoEntidades_2() { Professor professorExistente = null; using (var context = new EscolaDBEntities()) { context.Configuration.ProxyCreationEnabled = false; professorExistente = context.Professores.FirstOrDefault<Professor>(); } Padrao PadraoDesconectado = new Padrao() { PadraoNome = "New Standard", ObjectState = EntityObjectState.Added }; professorExistente.ObjectState = EntityObjectState.Modified; //adiciona um professor existente ao Padrao PadraoDesconectado.Professores.Add(professorExistente); //adiciona um novo Padrao PadraoDesconectado.Professores.Add(new Professor() { ProfessorNome = "New teacher", PadraoId = PadraoDesconectado.PadraoId, ObjectState = EntityObjectState.Added }); // using (var newContext = new EscolaDBEntities()) { // verifica a propriedade ObjectState e marca o apropriedado EntityState if (PadraoDesconectado.ObjectState == EntityObjectState.Added) newContext.Entry(PadraoDesconectado).State = System.Data.Entity.EntityState.Added; else if (PadraoDesconectado.ObjectState == EntityObjectState.Modified) newContext.Entry(PadraoDesconectado).State = System.Data.Entity.EntityState.Modified; else if (PadraoDesconectado.ObjectState == EntityObjectState.Deleted) newContext.Entry(PadraoDesconectado).State = System.Data.Entity.EntityState.Deleted; else newContext.Entry(PadraoDesconectado).State = System.Data.Entity.EntityState.Unchanged; //verifica a propriedade ObjectState de cada professor e marca o apropriedado EntityState foreach (Professor tchr in PadraoDesconectado.Professores) { if (tchr.ObjectState == EntityObjectState.Added) newContext.Entry(tchr).State = System.Data.Entity.EntityState.Added; else if (tchr.ObjectState == EntityObjectState.Modified) newContext.Entry(tchr).State = System.Data.Entity.EntityState.Modified; else if (tchr.ObjectState == EntityObjectState.Deleted) newContext.Entry(tchr).State = System.Data.Entity.EntityState.Deleted; else newContext.Entry(tchr).State = System.Data.Entity.EntityState.Unchanged; } //salva as mudanças newContext.SaveChanges(); } }
As vantagens dessa abordagem são:
- Nenhum código ou processamento extra é preciso para determinar o estado da entidade;
- Ocorre o tratamento dos estados Added, Modified, Deleted e Unchanged;
- Não é preciso chamar a atualização para entidades não alteradas (Unchanged).
Desvantagens:
- Precisamos definir os estados apropriados de cada entidade no modo desconectado, tomando muito cuidado com esta operação;
Apresentando as duas abordagens, avalie qual se ajusta melhor ao seu cenário e a utilize quando for preciso.
Na próxima aula veremos a concorrência no Entity Framework.