Estou recebendo esse erro quando eu GetById () em uma entidade e, em seguida, defino a coleção de entidades filho para minha nova lista que vem da exibição do MVC.
A operação falhou: O relacionamento não pôde ser alterado porque uma ou mais das propriedades de chave estrangeira são não anuláveis. Quando uma alteração é feita em um relacionamento, a propriedade de chave estrangeira relacionada é definida como um valor nulo. Se a chave estrangeira não suportar valores nulos, um novo relacionamento deverá ser definido, a propriedade da chave estrangeira deverá receber outro valor não nulo ou o objeto não relacionado deverá ser excluído.
Não entendo bem essa linha:
O relacionamento não pôde ser alterado porque uma ou mais das propriedades da chave estrangeira são não anuláveis.
Por que eu mudaria o relacionamento entre duas entidades? Ele deve permanecer o mesmo durante toda a vida útil de todo o aplicativo.
O código em que a exceção ocorre é a simples atribuição de classes filho modificadas em uma coleção à classe pai existente. Esperamos que isso atenda à remoção de classes filho, adição de novas e modificações. Eu teria pensado que o Entity Framework lida com isso.
As linhas de código podem ser destiladas para:
var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();
Respostas:
Você deve excluir itens filhos antigos
thisParent.ChildItems
um por um manualmente. O Entity Framework não faz isso por você. Finalmente, não é possível decidir o que você deseja fazer com os itens filhos antigos - se você deseja descartá-los ou se deseja mantê-los e atribuí-los a outras entidades-pai. Você deve informar ao Entity Framework sua decisão. Mas você deve tomar uma dessas duas decisões, pois as entidades filhas não podem viver sozinhas sem uma referência a nenhum pai no banco de dados (devido à restrição de chave estrangeira). Isso é basicamente o que a exceção diz.Editar
O que eu faria se itens filho pudessem ser adicionados, atualizados e excluídos:
Nota: Isso não foi testado. Supõe-se que a coleção de itens filho seja do tipo
ICollection
. (Normalmente, eu tenhoIList
e, em seguida, o código parece um pouco diferente.) Também removi todas as abstrações do repositório para simplificar.Não sei se essa é uma boa solução, mas acredito que algum tipo de trabalho duro nesse sentido deve ser feito para cuidar de todos os tipos de alterações na coleção de navegação. Eu também ficaria feliz em ver uma maneira mais fácil de fazer isso.
fonte
A razão pela qual você está enfrentando isso é devido à diferença entre composição e agregação .
Na composição, o objeto filho é criado quando o pai é criado e é destruído quando seu pai é destruído . Portanto, sua vida útil é controlada pelo pai. por exemplo, uma postagem de blog e seus comentários. Se uma postagem for excluída, seus comentários deverão ser excluídos. Não faz sentido ter comentários para uma postagem que não existe. O mesmo para pedidos e itens de pedidos.
Na agregação, o objeto filho pode existir independentemente de seu pai . Se o pai for destruído, o objeto filho ainda poderá existir, pois poderá ser adicionado a um pai diferente posteriormente. por exemplo: o relacionamento entre uma lista de reprodução e as músicas nessa lista de reprodução. Se a lista de reprodução for excluída, as músicas não deverão ser excluídas. Eles podem ser adicionados a uma lista de reprodução diferente.
A maneira como o Entity Framework diferencia os relacionamentos de agregação e composição é a seguinte:
Para composição: espera que o objeto filho tenha uma chave primária composta (ParentID, ChildID). Isso ocorre por design, já que os IDs das crianças devem estar dentro do escopo de seus pais.
Para agregação: espera que a propriedade da chave estrangeira no objeto filho seja anulável.
Portanto, o motivo pelo qual você está tendo esse problema é o motivo pelo qual você definiu sua chave primária na tabela filho. Deve ser composto, mas não é. Portanto, o Entity Framework vê essa associação como agregação, o que significa que, quando você remove ou limpa os objetos filho, ele não exclui os registros filhos. Ele simplesmente remove a associação e define a coluna de chave estrangeira correspondente como NULL (para que esses registros filhos possam ser associados posteriormente a um pai diferente). Como sua coluna não permite NULL, você obtém a exceção mencionada.
Soluções:
1- Se você tiver um motivo forte para não querer usar uma chave composta, precisará excluir os objetos filhos explicitamente. E isso pode ser feito de maneira mais simples do que as soluções sugeridas anteriormente:
2- Caso contrário, ao definir a chave primária adequada na tabela filho, seu código parecerá mais significativo:
fonte
Este é um problema muito grande. O que realmente acontece no seu código é o seguinte:
Parent
do banco de dados e obtém uma entidade anexadaAgora a solução realmente depende do que você quer fazer e como gostaria de fazê-lo?
Se você estiver usando o ASP.NET MVC, poderá tentar usar UpdateModel ou o TryUpdateModel .
Se você deseja apenas atualizar manualmente os filhos existentes, basta fazer algo como:
Anexar não é realmente necessário (definir o estado para
Modified
também anexará a entidade), mas eu gosto disso porque torna o processo mais óbvio.Se você deseja modificar os existentes, excluir os existentes e inserir novos filhos, deve fazer algo como:
fonte
.Clone()
. Você tem em mente que aChildItem
possui outras propriedades de navegação secundárias? Mas, nesse caso, não queremos que todo o subgráfico esteja anexado ao contexto, já que esperamos que todos os subfilhos sejam novos objetos se o próprio filho for novo? (Bem, pode ser diferente de modelo para modelo, mas vamos supor que o caso que os sub-criança são "dependentes" da criança como as da criança são dependentes do pai.)http://stackoverflow.com/questions/20233994/do-i-need-to-create-a-dbset-for-every-table-so-that-i-can-persist-child-entitie
Achei esta resposta muito mais útil para o mesmo erro. Parece que a EF não gosta quando você remove, prefere Excluir.
Você pode excluir uma coleção de registros anexados a um registro como este.
No exemplo, todos os registros de detalhes anexados a um pedido têm seu estado definido como Excluir. (Em preparação para adicionar novamente detalhes atualizados, como parte de uma atualização de pedido)
fonte
Não faço ideia por que as outras duas respostas são tão populares!
Acredito que você estava certo ao supor que a estrutura ORM deveria lidar com isso - afinal, é isso que promete entregar. Caso contrário, seu modelo de domínio será corrompido por preocupações de persistência. O NHibernate gerencia isso felizmente se você definir as configurações em cascata corretamente. No Entity Framework, também é possível, eles esperam que você siga melhores padrões ao configurar seu modelo de banco de dados, especialmente quando eles precisam inferir o que a cascata deve ser feita:
Você precisa definir o relacionamento pai-filho corretamente usando um " relacionamento de identificação ".
Se você fizer isso, o Entity Framework saberá que o objeto filho é identificado pelo pai e, portanto, deve ser uma situação "em cascata-excluir-órfão".
Além do acima, você pode precisar (da experiência do NHibernate)
em vez de substituir completamente a lista.
ATUALIZAR
O comentário de @ Slauma me lembrou que as entidades desanexadas são outra parte do problema geral. Para resolver isso, é possível usar o fichário de modelo personalizado que constrói seus modelos, tentando carregá-lo a partir do contexto. Esta postagem do blog mostra um exemplo do que quero dizer.
fonte
parent.ChildItems.Remove
vez de_dbContext.ChildItems.Remove
. Ainda existe (EF <= 6) nenhum suporte interno da EF para evitar códigos longos, como o das outras respostas.return context.Items.Find(id) ?? new Item()
Se você estiver usando o AutoMapper com Entity Framework na mesma classe, poderá encontrar esse problema. Por exemplo, se sua turma é
Isso tentará copiar as duas propriedades. Nesse caso, o ClassBId não é anulável. Como o AutoMapper copiará,
destination.ClassB = input.ClassB;
isso causará um problema.Defina seu AutoMapper para ignorar a
ClassB
propriedade.fonte
Eu apenas tive o mesmo erro. Eu tenho duas tabelas com um relacionamento pai-filho, mas configurei uma cascata "ao excluir" na coluna de chave estrangeira na definição da tabela filha. Portanto, quando eu excluo manualmente a linha pai (via SQL) no banco de dados, ela exclui automaticamente as linhas filho.
No entanto, isso não funcionou no EF, o erro descrito neste segmento apareceu. O motivo disso foi que, no meu modelo de dados da entidade (arquivo edmx), as propriedades da associação entre o pai e a tabela filho não estavam corretas. A
End1 OnDelete
opção foi configurada para sernone
("End1" no meu modelo é o final que possui uma multiplicidade de 1).Mudei manualmente a
End1 OnDelete
opção paraCascade
e do que funcionou. Não sei por que a EF não consegue captar isso quando atualizo o modelo do banco de dados (eu tenho um primeiro modelo de banco de dados).Para ser completo, é assim que meu código para excluir se parece:
Se eu não tivesse definido uma exclusão em cascata, teria que excluir manualmente as linhas filhas antes de excluir a linha pai.
fonte
Isso acontece porque a Entidade Filha está marcada como Modificada em vez de Excluída.
E a modificação que EF faz na Entidade Filha quando
parent.Remove(child)
é executada é simplesmente definir a referência ao painull
.Você pode verificar o EntityState da criança digitando o seguinte código na Janela Imediata do Visual Studio quando a exceção ocorrer, após a execução
SaveChanges()
:onde X deve ser substituído pela entidade excluída.
Se você não tiver acesso à
ObjectContext
execução_context.ChildEntity.Remove(child)
, poderá resolver esse problema, tornando a chave estrangeira parte da chave primária na tabela filho.Dessa forma, se você executar
parent.Remove(child)
, a EF marcará corretamente a Entidade como Excluída.fonte
Esse tipo de solução fez o truque para mim:
É importante dizer que isso exclui todos os registros e os insere novamente. Mas para o meu caso (menos de 10) está tudo bem.
Espero que ajude.
fonte
Encontrei esse problema hoje e queria compartilhar minha solução. No meu caso, a solução foi excluir os itens filhos antes de obter o pai do banco de dados.
Anteriormente, eu estava fazendo isso como no código abaixo. Em seguida, receberei o mesmo erro listado nesta pergunta.
O que funcionou para mim é obter os itens filhos primeiro, usando o parentId (chave estrangeira) e depois excluir esses itens. Depois, posso obter o pai do banco de dados e, nesse ponto, ele não deve ter mais itens filhos e posso adicionar novos itens filhos.
fonte
Você deve limpar manualmente a coleção ChildItems e anexar novos itens nela:
Depois disso, você pode chamar o método de extensão DeleteOrphans, que será tratado com entidades órfãs (ele deve ser chamado entre os métodos DetectChanges e SaveChanges).
fonte
context.DetectChanges();
.Eu tentei essas soluções e muitas outras, mas nenhuma delas funcionou. Como esta é a primeira resposta no google, adicionarei minha solução aqui.
O método que funcionou bem para mim foi tirar os relacionamentos de cena durante as confirmações, portanto não havia nada para a EF estragar. Eu fiz isso re-encontrando o objeto pai no DBContext e excluindo isso. Como as propriedades de navegação do objeto re-encontrado são todas nulas, os relacionamentos das crianças são ignorados durante a confirmação.
Observe que isso pressupõe que as chaves estrangeiras estejam configuradas com ON DELETE CASCADE; portanto, quando a linha pai for removida, os filhos serão limpos pelo banco de dados.
fonte
Usei a solução de Mosh , mas não me era óbvio como implementar a chave de composição corretamente no código primeiro.
Então, aqui está a solução:
fonte
Eu tinha o mesmo problema, mas sabia que funcionava bem em outros casos, então reduzi o problema para isso:
Tudo o que eu precisava fazer era tornar o ParentId parte da PK composta para indicar que os filhos não podem existir sem um pai. Usei o modelo DB-first, adicionei a PK e marquei a coluna parentId como EntityKey (então, tive que atualizá-la no DB e EF - não tenho certeza se a EF seria suficiente).
Depois que você pensa sobre isso, é uma distinção muito elegante que a EF usa para decidir se os filhos "fazem sentido" sem um pai (neste caso, Clear () não os excluirá e emitirá uma exceção, a menos que você defina o ParentId como algo diferente / especial ) ou, como na pergunta original, esperamos que os itens sejam excluídos assim que forem removidos do pai.
fonte
Esse problema ocorre porque tentamos excluir os dados da tabela pai ainda estão presentes na tabela pai. Resolvemos o problema com a ajuda da exclusão em cascata.
No modelo, crie o método na classe dbcontext.
Depois disso, em nossa chamada à API
Excluir em cascata opção de a tabela pai e a tabela filho relacionada ao pai com este código simples. Faça com que tente desta maneira simples.
Remover intervalo usado para excluir a lista de registros no banco de dados
fonte
Também resolvi meu problema com a resposta de Mosh e achei que a resposta de PeterB era um pouco, já que ela usava um enum como chave estrangeira. Lembre-se de que você precisará adicionar uma nova migração após adicionar esse código.
Também posso recomendar esta postagem no blog para outras soluções:
http://www.kianryan.co.uk/2013/03/orphaned-child/
Código:
fonte
Usando a solução do Slauma, criei algumas funções genéricas para ajudar a atualizar objetos filho e coleções de objetos filho.
Todos os meus objetos persistentes implementam essa interface
Com isso, implementei essas duas funções no meu Repositório
Para usá-lo, faço o seguinte:
Espero que isto ajude
EXTRA: Você também pode criar uma classe DbContextExtentions (ou sua própria interface de contexto) separada:
e use-o como:
fonte
Eu estava enfrentando o mesmo problema ao excluir meu registro do que ocorreu algum problema, pois essa solução é que quando você exclui seu registro do que está perdendo alguma coisa antes de excluir o cabeçalho / registro mestre, deve escrever no código para exclua seus detalhes antes do cabeçalho / mestre Espero que o problema seja resolvido.
fonte
Encontrei esse problema antes de várias horas e tentei de tudo, mas no meu caso, a solução era diferente da listada acima.
Se você usar a entidade já recuperada do banco de dados e tentar modificá-lo, o erro ocorrerá, mas se você obter uma cópia nova da entidade do banco de dados, não haverá problemas. Não use isso:
Usa isto:
fonte