Atualizar relacionamentos ao salvar alterações de objects EF4 POCO

Entidade Framework 4, objects POCO e ASP.Net MVC2. Eu tenho um relacionamento muitos para muitos, digamos entre entidades BlogPost e Tag. Isso significa que na minha class POCO BlogPost gerada por T4 eu tenho:

public virtual ICollection Tags { // getter and setter with the magic FixupCollection } private ICollection _tags; 

Eu peço um BlogPost e os Tags relacionados de uma instância do ObjectContext e o envio para outra camada (View no aplicativo MVC). Mais tarde, recebo o BlogPost atualizado com propriedades alteradas e relacionamentos alterados. Por exemplo, ele tinha as tags “A” “B” e “C”, e as novas tags são “C” e “D”. No meu exemplo particular, não há novas tags e as propriedades das Tags nunca mudam, então a única coisa que deve ser salva são as relações alteradas. Agora eu preciso salvar isso em outro ObjectContext. (Update: Agora eu tentei fazer na mesma instância de contexto e também falhei.)

O problema: não consigo salvar as relações corretamente. Eu tentei tudo que encontrei:

  • Controller.UpdateModel e Controller.TryUpdateModel não funcionam.
  • Obtendo o antigo BlogPost do contexto, em seguida, modificar a coleção não funciona. (com methods diferentes do próximo ponto)
  • Isso provavelmente funcionaria, mas espero que isso seja apenas uma solução alternativa, não a solução :(.
  • Tentei Anexar / Adicionar / ChangeObjectState funções para BlogPost e / ou Tags em todas as combinações possíveis. Falhou.
  • Isso parece com o que eu preciso, mas não funciona (eu tentei consertar isso, mas não para meu problema).
  • Tentei ChangeState / Add / Attach / … os objects de relacionamento do contexto. Falhou.

“Não funciona” significa, na maioria dos casos, que trabalhei na “solução” fornecida até que ela não produza erros e salve pelo menos as propriedades do BlogPost. O que acontece com os relacionamentos varia: geralmente, os tags são adicionados novamente à tabela Tag com novos PKs e o BlogPost salvo faz referência a esses e não aos originais. É claro que os Tags retornados têm PKs, e antes dos methods save / update eu verifico os PKs e eles são iguais aos do database, então provavelmente o EF acha que eles são novos objects e os PKs são os temporários.

Um problema que conheço e que pode tornar impossível encontrar uma solução simples e automatizada: Quando uma coleção de objects POCO é alterada, isso deve acontecer pela propriedade de coleção virtual mencionada acima, porque, em seguida, o truque FixupCollection atualizará as referências inversas na outra extremidade do relacionamento muitos-para-muitos. No entanto, quando uma exibição “retorna” um object BlogPost atualizado, isso não aconteceu. Isso significa que talvez não haja uma solução simples para o meu problema, mas isso me deixaria muito triste e odiaria o triunfo do EF4-POCO-MVC :(. Também isso significaria que a EF não pode fazer isso no ambiente MVC, o que ocorrer Tipos de objects EF4 são usados ​​:(. Eu acho que o rastreamento de mudanças baseado em captura instantânea deve descobrir que o BlogPost alterado tem relação com Tags com PKs existentes.

Btw: Eu acho que o mesmo problema acontece com relações um-para-muitos (google e meu colega dizem isso). Vou tentar em casa, mas mesmo que isso funcione, isso não me ajuda em meus relacionamentos de muitos-para-muitos em meu aplicativo :(.

Vamos tentar assim:

  • Anexar BlogPost ao contexto. Depois de append o object ao contexto do estado do object, todos os objects relacionados e todas as relações são definidos como Inalterados.
  • Use context.ObjectStateManager.ChangeObjectState para definir seu BlogPost como Modificado
  • Iterar pela coleção de tags
  • Use context.ObjectStateManager.ChangeRelationshipState para definir o estado da relação entre o Tag atual e o BlogPost.
  • SaveChanges

Editar:

Eu acho que um dos meus comentários deu a você falsas esperanças de que a EF fará a fusão para você. Eu joguei muito com esse problema e minha conclusão diz que a EF não fará isso por você. Eu acho que você também encontrou minha pergunta no MSDN . Na realidade, há muitas dessas questões na Internet. O problema é que não está claramente indicado como lidar com esse cenário. Então vamos dar uma olhada no problema:

Fundo do problema

A EF precisa rastrear mudanças em entidades para que a persistência saiba quais registros devem ser atualizados, inseridos ou excluídos. O problema é que é responsabilidade do ObjectContext rastrear as alterações. O ObjectContext é capaz de rastrear alterações apenas para entidades conectadas. Entidades criadas fora do ObjectContext não são rastreadas.

Descrição do Problema

Com base na descrição acima, podemos afirmar claramente que o EF é mais adequado para cenários conectados onde a entidade é sempre anexada ao contexto – típico para o aplicativo WinForm. Os aplicativos da Web exigem um cenário desconectado em que o contexto é fechado após o processamento da solicitação e o conteúdo da entidade é transmitido como resposta HTTP ao cliente. A próxima solicitação HTTP fornece conteúdo modificado da entidade que precisa ser recriado, anexado ao novo contexto e persistido. A recreação normalmente acontece fora do escopo do contexto (arquitetura em camadas com persistência ignorada).

Solução

Então, como lidar com esse cenário desconectado? Ao usar as classs POCO, temos 3 maneiras de lidar com o controle de alterações:

  • Instantâneo – requer o mesmo contexto = inútil para cenário desconectado
  • Proxies de rastreamento dynamic – requer o mesmo contexto = inútil para o cenário desconectado
  • Sincronização manual.

Sincronização manual em entidade única é tarefa fácil. Você só precisa append entidade e chamar AddObject para inserir, DeleteObject para excluir ou definir o estado no ObjectStateManager para Modificado para atualização. A dor real vem quando você tem que lidar com o gráfico de objects em vez de uma única entidade. Essa dor é ainda pior quando você tem que lidar com associações independentes (aquelas que não usam a propriedade Chave Estrangeira) e muitas para muitas relações. Nesse caso, você precisa sincronizar manualmente cada entidade no gráfico de objects, mas também cada relação no gráfico de objects.

A synchronization manual é proposta como solução pela documentação do MSDN: Anexar e desappend objects diz:

Objetos são anexados ao contexto do object em um estado Inalterado. Se você precisar alterar o estado de um object ou o relacionamento porque sabe que seu object foi modificado no estado desanexado, use um dos seguintes methods.

Os methods mencionados são ChangeObjectState e ChangeRelationshipState of ObjectStateManager = rastreamento manual de alterações. Proposta semelhante está em outro artigo de documentação do MSDN: Definindo e Gerenciando Relacionamentos diz:

Se você estiver trabalhando com objects desconectados, deverá gerenciar manualmente a synchronization.

Além disso, existe uma postagem no blog relacionada à EF v1 que critica exatamente esse comportamento da EF.

Razão para solução

A EF tem muitas operações e configurações “úteis” como Atualizar , Carregar , ApplyCurrentValues , ApplyOriginalValues , MergeOption etc. Mas, pela minha investigação, todos esses resources funcionam apenas para uma única entidade e afetam apenas preperies escalares (= não propriedades e relações de navegação). Eu prefiro não testar esses methods com tipos complexos nesteds na entidade.

Outra solução proposta

Em vez da funcionalidade real de Mesclagem, a equipe da EF fornece algo chamado Entidades de Rastreamento Automático (STE), que não solucionam o problema. Primeiro de tudo, o STE funciona apenas se a mesma instância for usada para processamento inteiro. No aplicativo da Web, esse não é o caso, a menos que você armazene a instância no estado de exibição ou na session. Devido a isso estou muito infeliz de usar o EF e vou verificar os resources do NHibernate. A primeira observação diz que o NHibernate talvez tenha essa funcionalidade .

Conclusão

Eu acabarei com essas suposições com um único link para outra questão relacionada no fórum do MSDN. Verifique a resposta de Zeeshan Hirani. Ele é autor de Receitas do Entity Framework 4.0 . Se ele disser que a mesclagem automática de charts de object não é suportada, acredito nele.

Mas ainda existe a possibilidade de eu estar completamente errado e alguma funcionalidade de mesclagem automática existir na EF.

Editar 2:

Como você pode ver isso já foi adicionado ao MS Connect como sugestão em 2007. MS fechou como algo a ser feito na próxima versão, mas na verdade nada foi feito para melhorar essa lacuna, exceto STE.

Eu tenho uma solução para o problema que foi descrito acima por Ladislav. Eu criei um método de extensão para o DbContext que executará automaticamente o add / update / delete baseado em um diff do gráfico fornecido e do gráfico persistido.

No momento, usando o Entity Framework, você precisará executar as atualizações dos contatos manualmente, verificar se cada contato é novo e adicionar, verificar se está atualizado e editar, verificar se foi removido e, em seguida, excluí-lo do database. Depois de fazer isso para alguns agregados diferentes em um sistema grande, você começa a perceber que deve haver uma maneira melhor e mais genérica.

Por favor, dê uma olhada e veja se ele pode ajudar http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a- entidades de grafo-de-destacado /

Você pode ir direto para o código aqui https://github.com/refactorthis/GraphDiff

Eu sei que é tarde para o OP, mas como esse é um problema muito comum, eu postei isso, caso ele sirva a outra pessoa. Eu estive brincando com esse problema e acho que tenho uma solução bastante simples, o que eu faço é:

  1. Salve o object principal (Blogs por exemplo) definindo seu estado como Modificado.
  2. Consulte o database para o object atualizado, incluindo as collections que preciso atualizar.
  3. Consulta e converte .ToList () as entidades que eu quero que minha coleção inclua.
  4. Atualize a (s) coleção (s) do object principal para a lista que obtive na etapa 3.
  5. SaveChanges ();

No exemplo a seguir, “dataobj” e “_categories” são os parâmetros recebidos pelo meu controlador “dataobj” é meu object principal e “_categories” é um IEnumerable contendo os IDs das categorias que o usuário selecionou na exibição.

  db.Entry(dataobj).State = EntityState.Modified; db.SaveChanges(); dataobj = db.ServiceTypes.Include(x => x.Categories).Single(x => x.Id == dataobj.Id); var it = _categories != null ? db.Categories.Where(x => _categories.Contains(x.Id)).ToList() : null; dataobj.Categories = it; db.SaveChanges(); 

Funciona mesmo para múltiplas relações

A equipe do Entity Framework está ciente de que este é um problema de usabilidade e planeja abordá-lo após o EF6.

Da equipe do Entity Framework:

Este é um problema de usabilidade que estamos cientes e é algo em que estivemos pensando e planejamos fazer mais trabalhos no pós-EF6. Eu criei este item de trabalho para acompanhar o problema: http://entityframework.codeplex.com/workitem/864 O item de trabalho também contém um link para o item de voz do usuário para isso – eu o incentivo a votar nele se você tiver não fiz isso já.

Se isso afetar você, vote no recurso em

http://entityframework.codeplex.com/workitem/864

Todas as respostas foram ótimas para explicar o problema, mas nenhuma delas realmente resolveu o problema para mim.

Descobri que, se eu não usasse o relacionamento na entidade pai, apenas adicionei e removi as entidades filho, tudo funcionou perfeitamente bem.

Desculpe pelo VB, mas é com isso que o projeto em que estou trabalhando está escrito.

A entidade pai “Report” tem um relacionamento de muitos para “ReportRole” e tem a propriedade “ReportRoles”. As novas funções são passadas por uma string separada por vírgula de uma chamada Ajax.

A primeira linha irá remover todas as entidades filhas, e se eu usei “report.ReportRoles.Remove (f)” em vez do “db.ReportRoles.Remove (f)” eu iria receber o erro.

 report.ReportRoles.ToList.ForEach(Function(f) db.ReportRoles.Remove(f)) Dim newRoles = If(String.IsNullOrEmpty(model.RolesString), New String() {}, model.RolesString.Split(",")) newRoles.ToList.ForEach(Function(f) db.ReportRoles.Add(New ReportRole With {.ReportId = report.Id, .AspNetRoleId = f}))