我在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/

10-13 04:27