EF 4.1 - Entendendo o ciclo de vida das entidades


Neste artigo eu vou tratar do ciclo de vida das entidades no Entity Framework 4.1.

Então, antes de continuar a leitura é importante que você tenha noções sobre o modelo de objetos, o Entity Data Model, o mapeamento OR/M e como realizar consultas para obter dados e transformar dados em objetos.

O EDM - Entity Data Model -  é um modelo entidades - relacionamentos onde:
  • Entidades - são instâncias de tipos de entidades como Clientes, Produtos os quais estão estruturados em registros e chaves;
  • Relacionamentos - são instâncias de tipos de relacionamentos que são associações entre dois ou mais tipos de entidades;

É a partir do modelo de entidades que podemos escrever código usando as diferentes APIs, como o provedor EntityClient ou o Object Services com LINQ to Entities.

Para melhor compreendermos os conceitos relacionados com a EDM, podemos separá-los em dois conjuntos:

  • Conceitos para definição de tipos ou estruturas;
  • Conceitos para gerenciamento de instâncias destes tipos;
Entity Data Model (EDM) :  O  Entity Data Model  é constituído de 3 partes:
  • Conceptual schema definition language (CSDL) : Declara e define entidades, associações, herança, etc,. As Entity classes são geradas a partir deste esquema;
  • Store schema definition language (SSDL) : Metadados que descreve o container de armazenamento (=banco de dados) que persiste os dados;
  • Mapping specification language (MSL) : Mapeia as entidades no arquivo CSDL para tabelas descritas no arquivo SSDL;

 

Vou iniciar examinando o ciclo de vida de uma entidade e seus estados.

1 - O ciclo de vida de uma entidade

Durante o seu tempo de vida, uma entidade possui um estado e antes de mostrar como retornar o estado de um entidade vamos examinar o que é um estado de uma entidade.

O estado de uma entidade é uma enumeração do tipo System.Data.EntityState que declara os seguintes valores:

Neste artigo vamos tentar responder as seguintes questões:

Mas o que representam estes estados ?
E com que conceito eles estão relacionados ?
Como uma entidade passa de um estado para outro ?
Como o estado de uma entidade afeta o banco de dados ?

Compreendendo o estado de um entidade

Quando usamos o Entity Framework para criar o modelo de entidades temos que o contexto trata uma referência para com todos os objetos retornados do banco de dados. Neste cenário o contexto trata o estado de uma entidade e mantém as modificações feitas para as propriedades da entidade. Este recurso é conhecido como object tracking ou change tracking;

Se você criar uma entidade fora do contexto o seu estado inicial é Detached pois o contexto não pode tratar esta entidade.

Nota: De maneira mais precisa uma entidade fora do contexto não possui estado. A atribuição Detached é usada pois na versão 1.0 do Entity Framework este era o estado quando a entidade não era tratada pelo contexto.

Se você anexar(attach) uma entidade ao contexto ela terá o seu estado alterado para Unchaged.

Se você retornar uma entidade do banco de dados e então liberar a entidade do contexto, o estado da entidade será Detached.

Se você retornar uma entidade, liberá-la do contexto, e, criar um novo contexto e adicionar a entidade a ele, a entidade terá o estado Added.

O estado é o relacionamento entre a entidade e o contexto que trata uma referência para a entidade.

Você poderia pensar que o estado da entidade reflete o estado da entidade comparado com linha correspondente do banco de dados mas este conceito esta incorreto.

O contexto representa a aplicação de banco de dados e é por isso que as entidades relacionam seu estado com o contexto e não como banco de dados.

Suponha que você tenha dois métodos em um web service:

  1. Retorna dados do cliente;
  2. Atualiza os dados;

O cliente usa o primeiro método para retorna dados e exibi-los no formulário.

Neste método você cria um contexto que retorna os dados e então destrói o contexto. Após isso o cliente modifica alguns dados, e então os salva invocando o segundo método e passando os dados atualizados. No segundo método você cria um novo contexto e anexa a entidade.

O novo contexto não conhece que os dados foram modificados pelo usuário a menos que ele vá ao banco de dados e faça uma comparação dos dados recebidos com os dados gravados. Como fazer uma consulta ao banco de dados consome recursos isto não é feito de forma automática o que significa que quando a entidade é anexada ao contexto ela recebe o estado Unchaged pois o contexto não sabe nada sobre alterações feitas na entidade. Se a entidade refletisse o seu estado atual em comparação com o banco de dados e não com o contexto seu estado seria Modified mas não é este o caso.

Dessa forma tenha em mente que o estado de uma entidade reflete o seu relacionamento com o contexto e não com o banco de dados.

Como o estado de uma entidade afeta o banco de dados

O estado representa não somente o estado da entidade no contexto mas também como os dados serão persistidos no banco de dados.

Para cada estado existe um comando SQL correspondente.

Os estados Detached e Unchaged não possuem impacto no banco de dados pois uma entidade Detached não esta sendo tratada pelo contexto e não pode ser persistida ao passo que uma entidade no estado Unchaged não possui alterações a serem persistidas.

Durante seu tempo de vida uma entidade pode sofrer alterações no seu estado vejamos como isso ocorre e qual API podemos usar para fazer isso de forma manual.

Mudanças de estado no ciclo de vida de uma entidade

O estado de uma entidade é algumas vezes definido automaticamente pelo contexto e também pode ser modificado manualmente pelo desenvolvedor. Embora todas as combinações de alterações de estado sejam possíveis algumas não terão sentido, por exemplo alterar o estado de Added para Delete ou vice-versa.

A figura a seguir mostra todos os estados e como as entidades podem passar de um estado para outro:

Os diferentes estados das entidades e os métodos do contexto
que podem alterar o estado de uma entidade;

Os métodos exibidos na figuram pertencem ao ObjectContext ou as classe ObjectSet<T> (ObjectSet(Of T)).

Vejamos então os detalhes envolvidos em cada mudança de estado.

Estado Detached

Quando uma entidade esta Detached ela não esta vinculada ao contexto e o seu estado não esta sendo monitorado.

Ela pode ser liberada, modificada, usada em combinação com outras classe, ou usada da forma que você precisar; como não existe um contexto vinculada a ela ela não tem significado para o Entity Framework.

Como consequência, o estado Detached é o estado padrão para uma entidade criada (usando o construtor new) porque o contexto não pode monitorar a criação de qualquer objeto no seu código. Isto é verdade mesmo se você instanciar a entidade dentro de um bloco Using do contexto. O estado Detached também é o estado das entidades retornadas do banco de dados quando o monitoramento estiver desabilitado.

Estado Unchanged

Quando uma entidade esta no estado Unchaged, ela esta vinculada ao contexto mas não foi modificada. Por padrão, uma entidade retornada do banco de dados esta neste estado.

Quando uma entidade esta anexada ao contexto (usando o método Attach) ela também esta no estado Unchanged. Como o contexto não pode monitorar alterações em objetos que ele não referencia, quando um objeto esta anexado ele assume que ele esta no estado Unchanged.

Estado Added

Quando uma entidade esta no estado Added, você pode somente alterar o seu estado para Detached.

Mesmo que você faça qualquer alteração na entidade , o seu estado continua como Added, pois alterar o estado para Modified, Unchanged ou Deleted não faria sentido visto que uma nova entidade não possui correspondência com nenhuma linha do banco de dados.

Estado Modified

Quando uma entidade esta no estado Modified significa que ela estava no estado Unchanged e sofreu alguma alteração.

Depois que uma entidade entra no estado Modified ela pode ter o seu estado alterado para Detached ou Deleted mas após isso não pode ter o seu estado retornado para Unchanged mesmo que você restaure manualmente os valores originais (a menos que você altere o estado para Detached e em seguida anexe a entidade ao contexto).

Você não pode alterar o estado de Modified para Added (a menos que voce altere o estado para Detached e inclua a entidade ao contexto) pois uma linha com o ID da entidade já existe no banco de dados e se fizer isso irá obter um erro em tempo de execução quando for persistir a entidade.

Estado Deleted

Uma entidade entre no estado Delete porque ela estava no estado Unchanged ou Modified e então o método DeleteObject foi utilizado.

Este é o estado mais restritivo pois because é inútil mudar este estado para qualquer outro estado a não ser para Detached.

Gerenciando o estado das entidades

A alteração do estado Unchanged para o estado Modified é a única mudança de estado que é automaticamente feita pelo contexto. Todas as demais alterações de estado precisam ser feitas explicitamente usando o método apropriado.

Os métodos usados para alteração de estado são:

Estes métodos são expostos pelas classes ObjectContext e ObjectSet<T> com exceção do método AttachTo, o qual é definido somente pelo contexto e ChangeState o qual é definido pelo gerenciado de estado (state manager). Os métodos de ObjectSet<T> invocam internamente os métodos do contexto logo não existe diferenças entre eles.

Na primeira versão do Entity Framework somente existiam os métodos do ObjectContext e nas versões posteriores foram incluídos os métodos para ObjectSet<T> aumentando a usabilidade e simplificando a legibilidade do código. Por causa dessas vantagens recomenda-se utilizar os métodos de ObjectSet<T> e considerar os métodos do contexto como obsoletos.

Vamos dar uma rápida introdução a cada um desses métodos.

Método AddObject

Este método permite incluir uma entidade no contexto com o estado Added e com isso ela passa a ser monitorada pelo contexto para modificações.

Quando o processo de persistência for iniciado, o contexto salva o objeto como uma nova linha na tabela usando a instrução SQL INSERT.

Vamos ver um exemplo de como usar este método primeiro analisando o método do contexto e a seguir o método do entity-set de forma a compreender porque uma nova API foi criada.

O método AddObject recebe dois argumentos : o nome do entity set e a entidade;

C#    VB
public void AddObject(string entitySetName, object entity)        Public Sub AddObject(ByVal entitySetName As String,ByVal entity As Object)

Existem dois problemas com este código:

  1. O nome do entity set é passado como uma string o que pode causar erros de digitação podendo causar um erro em tempo de execução;
  2. O argumento entity é do tipo Object o que significa que você pode passar qualquer objeto CLR e receber um erro em tempo de execução se o objeto estiver incorreto.

Para resolver este problema foi introduzido uma API equivalente na interface entity set que aceita como argumento somente o objeto que precisa ser incluído no contexto:

C#    VB
public void AddObject(TEntity entity)      Public Sub AddObject(ByVal entity As TEntity)

Onde TEntity é o tipo da entidade mantida pelo entity set.

Lembrando que um entity set é uma instância de um tipo implementando a interface IObjectSet<T>. Devido a esta tipagem você não consegue passar um objeto do tipo incorreto para o método e além disso o entity set conhece o seu nome e você não precisa especificá-lo.

Abaixo temos um exemplo de sintaxe usada pela nova API que adiciona uma entidade ao contexto usando o método da classe ObjectSet<T>:

C#    VB
ctx.Empresas.AddObject(new Produto() { ... });   ctx.Empresas.AddObject(New Produto() With { ... })

Incluir uma entidade ao contexto é muito útil quando você precisa persistir o objeto como uma nova linha no banco de dados mas quando o objeto já possuir uma correspondência com uma linha no banco de dados ele precisa ser persistido usando o comando UPDATE.

Método Attach

O método Attach anexa um objeto ao contexto em um estado Unchanged.Quando a entidade está ligada ao contexto, ela é rastreada pelo contexto para as modificações feitas nas propriedades escalares.

Em um aplicativo do mundo real, há uma abundância de situações em que uma entidade precisa ser anexada.

Aplicações web e serviços web são exemplos típicos. Vamos supor um exemplo onde um cliente atualiza seus dados.

O método Attach do contexto recebe dois argumentos: o nome da entity set e a entidade.

Este método sofre as mesmas limitações do método AddObject e assim tornou-se obsoleto.

Em seu lugar, você pode usar o método Attach do entity set, que precisa somente do objeto a ser anexado, como você pode ver no exemplo abaixo:

C#    VB
var p = new Produto{ empresaid= 1 };  
...
ctx.Empresas.Attach(p);
 
  Dim p as New Produto With { .empresaid = 1 }
  ...
  ctx.Empresas.Attach(p)

Você anexa um objeto ao contexto quando você quer que os seus dados sejam atualizados (ou deletados) no banco de dados.

O objeto precisa ter uma correspondência com uma linha no banco de dados e isto é identificado pelas propriedades : Column e primary key.

Lembre-se que o contexto não vai verificar no banco de dados se o a chave primária do objeto possui uma correspondência com a linha do banco de dados. Quando você anexa um objeto ao contexto, a coluna chave primária deve ser definida, ou você irá obter um erro do tipo InvalidOperationException em tempo de execução. Além disso durante a fase de persistência se o comando UPDATE não afetar nenhuma linha o contexto lançara uma exceção.

Os métodos ApplyCurrentValues e ApplyOriginalValues

Como uma entidade anexada tem seu estado como Unchanged você precisa saber como marcar a entidade como Modified para persisti-la no banco de dados.

A maneira mais simples de fazer isso é realizar uma consulta ao banco de dados , obter os dados mais recentes e compará-lo com a entidade de entrada.

Já sabemos que o contexto do objeto mantém uma referência para cada entidade lida a partir do banco de dados ou que é adicionada por meio do método AddObject ou do método Attach.

Além disso ele mantém na memória os valores originais e os valores atuais das propriedades escalares quando a entidade está vinculada ao contexto.

O método ApplyOriginalValues toma uma entidade de entrada (a que vem do banco de dados) e então recupera na memória do contexto um objeto do mesmo tipo e com a mesma chave como a entidade de entrada (vamos nos referir a esta entidade como a entidade do contexto).

Finalmente, o método copia os valores das propriedades escalares da entidade de entrada para os valores originais das propriedades escalares da entidade de contexto.

Neste momento os valores originais das propriedades escalares mantidas no contexto contém os dados do banco de dados, enquanto os valores atuais das propriedades escalares mantidas no contexto contém valores da entidade (a partir do serviço web). Se os valores originais são diferentes dos valores atuais, a entidade é definida com o estado Modified, caso contrário ela permanece com o estado Unchanged.

Podemos seguir o caminho inverso onde ao invés de anexar a entidade e consultar o banco de dados podemos consultar o banco de dados e aplicar as modificações a partir da entidade do serviço web.

É isto o que o método ApplyCurrentValues faz.

Ele toma uma entidade como de entrada (a partir do serviço web, neste caso) e recupera na memória do contexto um objeto do mesmo tipo e com a mesma chave que a entidade de entrada (esta é a entidade do contexto).

Finalmente, o método copia os valores das propriedades escalares da entidade de entrada para o valor atual das propriedades escalares da entidade de contexto.

Neste ponto, os valores atuais mantidos no contexto contém dados da entidade do web serviço, e os valores originais são os valores do banco de dados. Se forem diferentes, a entidade é definida como estado Modified, caso contrário ela permanece no estado Unchanged.

Quando a persistência é acionada, se a entidade está no estado Modified, é utilizando o Comandos UPDATE.

Os métodos ApplyOriginalValues e applyCurrentValues pertencem as classes ObjectContext e ObjectSet<T>, e recomenda-se usar os métodos expostos por esta última, conforme mostrado abaixo:

C#    VB
Dim entityFromDb = GetEntityFromDb(entityFromServico.empresaId);
ctx.Empresas.Attach(entityFromServico);
ctx.Empresas.ApplyOriginalValues(entityFromDb);
ctx.Empresas.First(c => c.empresaId == entityFromServico.empresaId);
ctx.Empresas.ApplyCurrentValues(entityFromServico);
 Dim entityFromDb = GetEntityFromDb(entityFromServico.empresaId)
ctx.Empresas.Attach(entityFromServico)
ctx.Empresas.ApplyOriginalValues(entityFromDb)
ctx.Empresas.First(Function(c) c.empresaId = entityFromServico.empresaId)
ctx.Empresas.ApplyCurrentValues(entityFromServico)

Atenção, ambos os métodos só se preocupam com propriedades escalares e propriedades complexas da entidade de entrada.

Se uma propriedade escalar em uma entidade associada muda, ou se uma nova linha for adicionada, removida ou alterada em uma coleção associada, ela não será detectada.

O método DeleteObject

O método DeleteObject marca uma entidade como Deleted.

A única ressalva que você tem que ter em mente é que a entidade passada para este método deve ser anexada ao contexto. O objeto deve vir a partir da consulta ou ter sido anexada ao contexto usando o método Attach.

Se o objeto não for encontrado no contexto será lançada uma exceção do tipo InvalidOperationException com a seguinte mensagem: "The object cannot be deleted because it was not found in the ObjectStateManager.”

A seguir vemos exemplos de como usar este método exposto pela classe ObjectSet<T>:

1- C#

C# - cenário conectado  C# - cenário desconectado

var c = ctx.Empresas.OfType<Cliente>() .Where(w => w.empresaId == 1);
ctx.Empresas.DeleteObject(c);
  var c = new Cliente { ... };
  ctx.Empresas.Attach(c);
  ctx.Empresas.DeleteObject(c);

2- VB .NET

VB .NET- cenário conectado  VB .NET- cenário desconectado
Dim c = ctx.Empresas.OfType(Of Customer)(). Where(Function(w) w.empresaId = 1)
ctx.Empresas.DeleteObject(c)
  Dim c as New Cliente { ... } 
 ctx.Empresas.Attach(c)
  ctx.DeleteObject(c)

Quando DeleteObject é invocado, a entidade não é excluída do contexto, ela é marcada como excluída.

Quando a persistência é acionada, a entidade é removida do contexto, e os comandos DELETE são emitidos para excluí-la do banco de dados.

O método AcceptAllChanges

O método AcceptAllChanges toma todas as entidades que estão nos estados Added e Modified e as marca como Unchanged em seguida altera o estado das entidades que possuem o estado Deleted para Detached e eventualmente atualiza as entradas do ObjectStateManager.

O método AcceptAllChanges é exposto somente pelo ObjectContext e não existe na classe ObjectSet<T>.

C#    VB
ctx.AcceptAllChanges();     ctx.AcceptAllChanges()

Os métodos que vimos até agora são muito restritivos. Eles mudam o estado de uma entidade para um valor específico. Os próximos métodos que veremos são diferentes porque eles dão a você a liberdade para mudar para qualquer estado.

Os métodos ChangeState e ChangeObjectState

Os métodos ChangeState e ChangeObjectState são os métodos mais flexíveis para mudança de estado. Eles permitem alterar o estado de uma entidade para qualquer outro estado possível (exceto Detached).

O método ChangeState é exposto pela classe ObjectStateEntry, enquanto ChangeObjectState é exposto pela classe ObjectStateManager.

O método ChangeState só precisa do novo estado, porque a instância ObjectStateEntry já se refere a uma entidade. O método ChangeObjectState aceita a entidade e o novo estado como argumentos.

Ambos os métodos são mostrados nos exemplos a seguir.

1- C#

C# - Mudando o estado de um entidade
var osm = ctx.ObjectStateManager;
osm.ChangeObjectState(entity, EntityState.Unchanged);
osm.GetObjectStateEntry(entity).ChangeState(EntityState.Unchanged);

2- VB .NET

VB .NET- Mudando o estado de um entidade
Dim osm = ctx.ObjectStateManager
osm.ChangeObjectState(entity, EntityState.Unchanged)
osm.GetObjectStateEntry(entity).ChangeState(EntityState.Unchanged)

Estes métodos nem sempre alteram fisicamente o estado da entidade, por vezes eles recorrem ao uso de métodos anteriores. Por exemplo, movendo uma entidade para o estado Unchanged significa chamar o método AcceptChanges da classe ObjectStateEntry. Em contraste, alterando o estado de Unchanged para Added significa mudar o estado.

Às vezes você não precisa que as entidades sejam persistidas ou rastreadas para a modificação pelo contexto. Nesse caso, você pode remover as entidades do contexto desanexando-as.

O método Detached

O método Detach remove a entidade a partir da lista de entidades controladas pelo contexto. Seja qual for o estado da entidade, ela torna-se independente. Invocar este método é bastante simples, ele aceita somente a entidade que deve ser desanexada.

C#    VB
ctx.Empresas.Detach(c); ctx.Empresas.Detach(c)

A condição prévia para desanexar(Detach) uma entidade com sucesso é que a entidade já deve estar anexada ao contexto. Se isso não for o caso, você receberá uma aviso de erro do tipo InvalidOperationException com a mensagem: "O objeto não pode ser desanexado porque não esta anexado ao objeto StateManager".

Até o momento dissemos que o contexto é capaz de rastrear modificações para os objetos que são referenciados. Isso é verdade, mas apenas parcialmente. A verdade é que o contexto delega o gerenciamento de controle de alterações para outro componente interno chamado de ObjectStateManager.

Na continuação deste artigo eu vou abordar os conceitos relacionados com o ObjectStateManager.

Aguarde...

"Não ameis o mundo, nem o que há no mundo. Se alguém ama o mundo, o amor do Pai não está nele." 1 João 2:15

Referências:


José Carlos Macoratti