Back-End

15 dez, 2015

Curso Entity Framework – Atualizando um grafo de entidade usando o DBContext no modo desconectado – Parte 02

Publicidade

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:

ef_curb231

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:

  1. Usar a propriedade Primarykey (Id) de uma entidade;

  2. 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:

  1. Cada tipo de entidade deve possuir uma propriedade Id (chave primária);

  2. 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:

ef_curb232

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.