我在EF 4.1 Code First中发现了一个非常讨厌的错误。假设我们有这段代码可以从上下文中检索实体,然后使用新值对其进行更新:
public T Update<T>(T toUpdate) where T : class
{
System.Data.Objects.ObjectContext objectContext = ((System.Data.Entity.Infrastructure.IObjectContextAdapter)_context).ObjectContext;
System.Data.Objects.ObjectSet<T> set = objectContext.CreateObjectSet<T>();
IEnumerable<string> keyNames = set.EntitySet.ElementType
.KeyMembers
.Select(k => k.Name);
var type = typeof(T);
var values = keyNames.Select(c => type.GetProperty(c).GetValue(toUpdate, null)).ToArray();
var current = _context.Set<T>().Find(values);
if (current != null)
{
_context.Entry(current).CurrentValues.SetValues(toUpdate);
}
return current;
}
现在假定我的实体具有一个字符串的单个键属性。
工作方案:存储的实体具有键“ABCDE”,而我的toUpdate实体具有相同的键“ABCDE”:一切正常。
错误情况:存储的实体具有键“ABCDE”,而我的toUpdate实体具有键“ABCDE”(请注意最后一个字母后的空格)。
这两个键确实是不同的。但是find方法“自动地”修剪了我的密钥并找到了存储的实体。如果它没有破坏SetValues方法,那将是很好的:由于存储的密钥和新密钥不同,因此我(正确地)得到以下内容:
属性“Id”是对象键信息的一部分,不能
被修改。
因为与众不同,它会尝试更新它,并且由于它是一个关键属性,因此无法更新,因此整个过程都会失败并抛出异常。
我认为,“查找”方法不应自动修剪键值(或在内部做的使两个不同的字符串显示相同的操作)。在第二种情况下,“查找”方法应返回null。
现在有两件事:我如何临时解决此问题,以及在哪里可以报告此错误,因为我找不到官方的位置来报告此错误。
谢谢。
编辑:在这里报告该错误:https://connect.microsoft.com/VisualStudio/feedback/details/696352/ef-code-first-4-1-find-update-bug
最佳答案
但是查找方法“自动”修剪我的密钥并找到存储的
实体。
我不相信修剪会发生。 Find
在内部使用SingleOrDefault
查询,换句话说:当您调用...
set.Find("ABCDE "); // including the trailing blank
...它使用以下LINQ查询:
set.SingleOrDefault(key => key == "ABCDE "); // including the trailing blank
问题出在数据库中,结果取决于排序顺序,语言,数据库中
string
键字段(例如nvarchar(10)
)的大写/小写字母,重音等设置。例如,如果您在SQL Server中使用标准的
Latin1_General
排序顺序,则键“ABCDE” 和“ABCDE” (后跟空白)是相同的,则不能创建两行,这些行具有这些值作为主键。甚至“ABCDE” 和“abcde” 也相同(如果未在SQL Server中设置为区分大写和小写字母)。同时,这意味着对
string
列的查询也将返回所有匹配的行-匹配数据库中该列的排序顺序。带有尾随空白的“ABCDE” 的查询将只返回带有尾随空白的“ABCDE” 的记录。到目前为止,这是所有涉及字符串的LINQ to Entities查询的“正常”行为。
现在,正如您所发现的,似乎ObjectContext不知道数据库中配置的排序顺序,而是使用普通的.NET字符串比较,其中带空格的字符串和不带空格的字符串是不同的。
我不知道是否有可能告诉上下文使用与数据库相同的字符串比较。我怀疑这是否可行,因为.NET世界和关系数据库世界太不同了。某些排序顺序可能很特殊,并且仅在数据库中可用,而在.NET中则根本不可用,也许反之亦然。此外,除实体实体外,SQL Server还提供了其他数据库,它们必须由实体框架支持,并且这些数据库可能具有自己的排序系统。
对于您的特定情况-也许总是当您有
string
键时-一个可能的问题解决方法是将实体的key属性设置为更新为从数据库返回的对象的key:toUpdate.Id = current.Id;
_context.Entry(current).CurrentValues.SetValues(toUpdate);
或更一般而言,在您的代码上下文中:
//...
var current = _context.Set<T>().Find(values);
if (current != null)
{
foreach (var keyName in keyNames)
{
var currentValue = type.GetProperty(keyName).GetValue(current, null);
type.GetProperty(keyName).SetValue(toUpdate, currentValue, null);
}
_context.Entry(current).CurrentValues.SetValues(toUpdate);
}
不能将
toUpdate
附加到上下文以使此工作正常。这是一个错误吗?我不知道。至少这是.NET与关系数据库世界之间不匹配的结果,并且是一开始就避免
string
键列/属性的良好原因。关于.net - 查找/更新中的EF 4.1代码优先错误。任何解决方法?应该报告吗?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/7848644/