【问题标题】:C# EF 6 CurrentValues.SetValues cannot change Object's Key InformationC# EF 6 CurrentValues.SetValues 不能更改对象的关键信息
【发布时间】:2020-12-06 18:06:58
【问题描述】:

我看到了关于这个相同错误的其他问题,但我无法用我的代码中的这些建议来纠正错误;我认为这是一个不同的问题,而不是重复的。

我有一个应用程序,它制定了一系列规则,用户可以在 GUI 中设置这些规则的属性。在连接的数据库中有一个规则表,主键在 Rule.Id 上。当用户保存对规则的更改时,现有规则将获取“IsActive=0”以隐藏它,然后创建一个新的数据库记录,并将 GUI 中的属性写入数据库。用户看起来好像他们已经编辑了规则,但数据库实际上看到了一个反映新属性的新规则(这允许保留历史记录),通过另一个参考字段连接到旧规则。

在应用程序的 C# 代码中,每个规则的视图模型都包含一个 EF 规则对象属性。当用户单击“保存”时,我使用视图中设置的参数为他们要保存的每个 ruleViewModel 构建ruleViewModel.Rule,并使用与 GUI 匹配的属性。 MainViewModel 包含名为dbo 的DbContext 对象,因此我使用ruleViewModel.Rule 写入我保存到实体框架的mainViewModel.dbo.Entry。以下是为每个可保存的规则视图模型执行的三个基本步骤:

// get the rule from the GUI and use it to make sure we are updating the right rule in EF (which is connected to the mainViewModel)
var dboItem = ruleViewModel.MainViewModel.dbo.Rules.Single(r => r.Id == ruleViewModel.Rule.Id);

// set the values in the EF item to be those we got from the GUI
ruleViewModel.MainViewModel.dbo.Entry(dboItem).CurrentValues.SetValues(ruleViewModel.Rule);

// Save the differences
ruleViewModel.MainViewModel.dbo.SaveChanges();

如果用户只保存一个规则,一切正常,但如果他们随后尝试保存另一个规则,或者一次保存多个规则,则会收到以下错误,由 ..SetValues(..) 返回行:

Message = "The property 'Id' is part of the object's key information and cannot be modified. "

我从有关此主题的其他问题中看到,EF 有一个功能可以阻止您将同一对象两次写入具有不同 ID 的数据库,因此此错误经常发生在循环中。我尝试使用一些建议,例如添加

viewModel.MainViewModel.dbo.Rules.Add(dboItem);

viewModel.MainViewModel.dbo.Entry(dboItem).Property(x => x.Id).IsModified = false;

SaveChanges() 命令之前,但这并没有解决问题(更不用说更改代码的功能了)。我看到其他一些建议说应该在循环中创建条目,但在这种情况下,条目都是数据库中现有的规则 - 在我看来(可能是错误的)我无法在保存循环中创建它们,因为它们是构建循环的对象 - 对于我找到的每个实体,我想保存更改。

我真的很困惑该怎么做,并且越来越纠结于试图修复错误。现在已经好几天了,我的理智和自尊开始减弱!任何让我朝着正确的方向工作以阻止错误出现并允许我设置数据库值的指针都会非常受欢迎,因为我觉得我已经走到了完全的死胡同!第一次循环,一切正常。

【问题讨论】:

  • 您最好在代码中显示实际发生的情况。不可能从描述中完全将其拼凑起来。特别是。上下文的生命周期以及键和历史记录会发生什么。

标签: c# entity-framework mvvm entity-framework-6


【解决方案1】:

除了 DbContext 和包含实体的视图模型的位置有问题之外,这看起来可以按预期工作。我从 MVVM 标签中假设这是一个 Windows 应用程序而不是一个 Web 应用程序。唯一的问题是,这假定您的 ruleViewModel 中的 Rule 实体与 DbContext 分离。如果 DbContext 仍在跟踪该实体引用,那么再次从 DbContext 获取实体会将相同的引用传回给您。

这可能值得在调试会话中测试一次。如果添加以下内容:

var dboItem = ruleViewModel.MainViewModel.dbo.Rules.Single(r => r.Id == ruleViewModel.Rule.Id);
bool isReferenceSame = Object.ReferenceEquals(dboItem, ruleViewModel.Rule);

您的 isReferenceSame 值是 True 还是 False?如果为 True,则主视图模型中的 DbContext 仍在跟踪 Rule 实体,并且不需要整个 get dboItem 和 SetValues。如果为 False,则分离 ruleViewModel。

如果实体已附加并被跟踪,则当您在 DbContext 上调用 SaveChanges 时,对视图模型实体的编辑将保持不变。 (无需加载和 SetValues)这应该适用于单个或多个实体编辑。

如果实体是分离的,那么通常跨 DbContext 实例更新实体的方法看起来更像:

var context = mainViewModel.dbo; 

foreach( var ruleViewModel in updatedRuleViewModels)
{
    // This should associate the Entity in the ruleViewModel with the DbContext and set it's tracking state to Modified.
    context.Entry(ruleViewModel.Rule).State = EntityState.Modified;
}
context.SaveChanges();

这种方法存在几个潜在问题,您应该考虑尽可能避免这些问题。 DbContext 应该保持相对较短的寿命,因此在 ViewModel 中看到对 DbContext 的引用有点危险。总的来说,我不建议将实体引用放在视图模型中或将它们传递到它们创建的 DbContext 范围之外。EF 当然支持它,但它需要更多的关注和关注来评估实体是否被跟踪,并且在 Web 应用程序等情况下,会打开域以进行无效篡改。 (相信通过覆盖数据状态附加或复制任何更改的实体)

【讨论】:

  • 是的,isReferenceSame 在每次循环时都会返回 true。从你所说的看来,由于我对 EF 的了解有限(这是我第一次使用它),我可能需要重组很多应用程序以避免这个问题。我这么说是因为我想解决这个问题的最好方法是标准化结构。你同意吗?我怀疑这将是很多工作!你知道我应该怎么做的好地方吗?
  • 如果引用相同,那么听起来您正在处理一个长期存在的 DbContext。如果删除重新加载 dboItem,删除复制/设置状态,然后只在 MainViewModel DbContext 上调用 SaveChanges(),会发生什么? (dbo) 如果这是一个长期存在的上下文并且这些实体引用被跟踪,那么 SaveChanges 将保留它们。长寿命上下文有几个缺点需要考虑,例如内存使用量不断增长,由于跟踪实体数量的增加导致操作时间更长,以及陈旧数据和丢弃污染保存的“坏”更改的额外努力。
  • 我找到了一门课程,我将学习如何避免长期存在的 Dbcontext - 你是对的,它会引起问题,你的回答让我大开眼界!我认为这些问题比这里可以解决的要大。
  • 我会在我解决方法后回答这个问题,但你已经向我展示了问题,所以谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-12
  • 2023-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多