我正在使用 Entity Framework。在我的模型中,我设置了一个可空的外键,它似乎在数据库中正确反映出来了。这是我的 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; }
}
如您所见,SubtypeID
和导航属性都设置为可空,表中的列也正确设置为可空。插入新的时Asset
,它接受SubtypeID
为 NULL,但是,在更新已存在的Asset
以使 为 NULL 时SubtypeID
,我收到以下错误:
SqlException:UPDATE 语句与 FOREIGN KEY 约束“FK_Assets_Subtypes_SubtypeID”冲突。冲突发生在数据库“assetinventory”、表“dbo.Subtypes”、列“TypeID”中。
以下是引发该异常的方法的一部分:
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();
}
方法调用时引发异常SaveChangesAsync
。
我尝试了在这里找到的很多东西,询问了 ChatGPT,什么都试过了。不明白为什么会发生这种情况。我接下来可以尝试什么?
使用来自传入值的导航属性更新实体时要小心,尤其是像反序列化副本这样的内容。例如:
通常,我建议对使用导航属性的 FK 使用影子属性,而不是公开两列。当您的实体同时公开导航和更新两个值时,导航属性优先。检查导航属性的值以查看该子类型的 ID 是否实际填充,并且不是 #null。通常,如果这是类似于更新的资产和相关子类型的 API,则子类型不会自动识别为“现有”记录,因为它未附加到 DbContext。默认情况下,EF 会尝试插入一行或尝试将一行与默认 ID(即 0 或 -1)关联
您可以选择仅通过设置 FK 来更新关系,只要您不依赖序列化/使用 SelectedAsset 及其 Subtype,因为设置 FK 而不更改导航属性将触发数据库的更新,但在保存 SelectedAsset.Subtype 后不会在
SaveChanges
调用后自动重新加载,这意味着如果您将 SelectedAsset 序列化为视图,它将是旧的导航属性。通常,当公开导航属性并使用 FK 值更新引用(子类型)时,您会从中获取子类型,
DbContext
或者Attach()
在检查尚未跟踪后获取副本DbContext
。例如,如果传入的 updatedAsset 具有 SubTypeId:获取要关联的子类型:
如果子类型尚未被跟踪,则获取子类型将从数据库中提取。这也可以检查 SubtypeID 是否有效。
附加子类型:
附加并不总是像调用那样简单,
Attach()
因为在某些情况下,可能DbContext
已经跟踪了相同 ID 的子类型的实例,这Attach()
会导致引发异常。这取决于跟踪的实例,因此在运行时看起来像是间歇性错误。(大多数情况下有效,但对于某些特定用户/场景则失败)上述检查对照.Local
跟踪缓存,如果找到则使用该引用,如果找不到则附加并使用该引用。您可能需要仔细检查
SubtypeID
数据库中的列是否允许NULL
。如果不允许,请修改约束以允许它。如下所示:即使您已经运行了迁移,也要确保数据库架构完全是最新的:
可能值得重新审视更新是如何完成的。由于它适用于
Add()
但不能Update()
,因此在跟踪实体状态方面可能会有所不同。如果需要,请使用context.Attach()
在更新期间跟踪实体:确保使用 Fluent API 正确配置了关系:
这应该可以解决问题并允许您更新
SubtypeID
而不NULL
违反外键约束。放弃了 EF,将其改为原始 SQL,并且效果很好。