Estou usando o Entity Framework. No meu modelo, tornei uma chave estrangeira anulável, e parece que ela está refletida corretamente no banco de dados. Aqui está parte da minha classe de modelo Asset:
public class Asset
{
[Key]
public int AssetID { get; set; }
[Required]
[MaxLength(100)]
public string AssetName { get; set; }
[Required]
public int AssetTypeID { get; set; }
[ForeignKey("AssetTypeID")]
public AssetType AssetType { get; set; }
public int? SubtypeID { get; set; }
[ForeignKey("SubtypeID")]
public Subtype? Subtype { get; set; }
}
Como você pode ver, a SubtypeID
propriedade e a propriedade de navegação estão ambas definidas como anuláveis, e a coluna na tabela está corretamente definida como anulável também. Ao inserir um novo Asset
, ele aceita SubtypeID
como NULL, no entanto, ao atualizar um já existente Asset
para ter NULL para SubtypeID
, recebo o seguinte erro:
SqlException: A instrução UPDATE entrou em conflito com a restrição FOREIGN KEY "FK_Assets_Subtypes_SubtypeID". O conflito ocorreu no banco de dados "assetinventory", tabela "dbo.Subtypes", coluna 'TypeID'.
Aqui está parte do método que lança essa exceção:
var updatedAsset = viewModel.Asset;
using (var scope = App.ServiceProvider.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<AssetDbContext>();
SelectedAsset.AssetName = updatedAsset.AssetName;
SelectedAsset.AssetTypeID = updatedAsset.AssetTypeID;
SelectedAsset.AssetType = updatedAsset.AssetType;
if (updatedAsset.SubtypeID == -1)
{
SelectedAsset.SubtypeID = null;
SelectedAsset.Subtype = null;
}
else
{
SelectedAsset.SubtypeID = updatedAsset.SubtypeID;
SelectedAsset.Subtype = updatedAsset.Subtype;
}
SelectedAsset.Owner = updatedAsset.Owner;
SelectedAsset.Location = updatedAsset.Location;
SelectedAsset.PurchaseDate = updatedAsset.PurchaseDate;
SelectedAsset.Value = updatedAsset.Value;
SelectedAsset.Status = updatedAsset.Status;
SelectedAsset.Description = updatedAsset.Description;
context.Assets.Update(SelectedAsset);
var log = new AssetLog
{
AssetID = SelectedAsset.AssetID,
Action = "Updated",
Timestamp = DateTime.Now,
PerformedBy = AuthenticationService.Instance.CurrentUser.GetTenantProfiles().ElementAt(0).Oid
};
context.AssetLogs.Add(log);
await context.SaveChangesAsync();
}
A exceção é lançada na SaveChangesAsync
chamada do método.
Tentei um monte de coisas que encontrei aqui, perguntei ao ChatGPT, tudo. Não consegui entender por que isso pode estar acontecendo. O que posso tentar em seguida?
Tenha cuidado ao atualizar entidades usando propriedades de navegação originadas de valores que chegam, especialmente algo como uma cópia desserializada. Por exemplo:
Normalmente, recomendo usar uma propriedade shadow para FKs onde uma propriedade de navegação é usada em vez de expor ambas as colunas. Quando sua entidade expõe uma navegação e atualiza ambos os valores, é a propriedade de navegação que tem precedência. Verifique os valores da propriedade de navegação para ver se o ID desse Subtipo foi realmente preenchido e se não era #null. Normalmente, se isso for algo como uma API onde o updatedAsset e o Subtipo associado foram desserializados, o SubTipo não seria reconhecido automaticamente como um registro "existente" porque não está anexado ao DbContext. O EF por padrão tentaria inserir uma linha ou tentar associar uma linha a um ID padrão (ou seja, 0 ou -1)
Você pode optar por apenas atualizar o relacionamento definindo o FK, desde que não esteja contando com a serialização/uso do SelectedAsset e seu Subtipo, pois definir o FK e não alterar a propriedade de navegação acionará uma ATUALIZAÇÃO no banco de dados, mas depois de salvar o SelectedAsset.Subtype não será recarregado automaticamente após
SaveChanges
ser chamado, o que significa que se você serializar o SelectedAsset para uma visualização, ele será a antiga propriedade de navegação.Normalmente, ao expor uma propriedade de navegação e atualizar a referência (subtipo) usando um valor FK, você buscaria o SubType de
DbContext
ouAttach()
a cópia após verificar se oDbContext
não está rastreando. Então, por exemplo, se o passado em updatedAsset tiver um SubTypeId:Buscando o subtipo para associar:
Buscar o subtipo puxará do BD se o subtipo ainda não estiver rastreado. Isso também serve como uma verificação de que o SubtypeID é válido.
Anexando o subtipo:
Anexar nem sempre é tão simples quanto chamar,
Attach()
pois pode haver situações em que oDbContext
já poderia estar rastreando uma instância do Subtipo para o mesmo ID, o queAttach()
resultaria em uma exceção gerada. Isso é situacional com base em quais instâncias são rastreadas, então pode parecer um erro intermitente no tempo de execução. (Funciona na maioria das vezes, mas falha para algum usuário/cenário específico) As verificações acima verificam.Local
o cache de rastreamento e usam essa referência se forem encontradas, ou anexam e usam se não forem.você pode querer verificar novamente se a
SubtypeID
coluna no seu banco de dados permiteNULL
. Se não permitir, modifique a restrição para permitir. algo como isto:Mesmo que você tenha executado migrações, certifique-se de que o esquema do banco de dados esteja totalmente atualizado:
Pode valer a pena revisitar como a atualização é feita. Como funciona com
Add()
mas nãoUpdate()
, pode haver uma diferença no rastreamento do estado da entidade. Se necessário, usecontext.Attach()
para rastrear a entidade durante a atualização:Certifique-se de que o relacionamento esteja configurado corretamente com a API Fluent:
Isso deve resolver o problema e permitir que você atualize o
SubtypeID
toNULL
sem violar a restrição de chave estrangeira.Desisti do EF, mudei para SQL puro e ele funciona perfeitamente.