查找/更新中的 EF 4.1 Code First 错误。有什么解决办法吗?是否应该报告?

发布于 2024-12-11 08:07:10 字数 1803 浏览 0 评论 0原文

我在 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”是对象关键信息的一部分,不能 进行修改。

因为,不同的是,它尝试更新它,并且由于它是一个关键属性,所以无法更新,所以整个事情都会失败并抛出。

我认为“查找”方法不应该自动修剪键值(或者它在内部所做的任何事情以使两个不同的字符串看起来相同)。在第二种情况下,“Find”方法应返回 null。

现在有两件事:我如何暂时解决这个问题,以及我可以在哪里报告这个错误,因为我找不到报告该错误的官方位置。

谢谢。

编辑:在这里报告错误:https://connect.microsoft.com/VisualStudio/feedback/details/696352/ef-code-first-4-1-find-update-bug

I've found a pretty nasty bug in EF 4.1 Code First. Assume we have this piece of code to retrieve an entity from the context and then update it with new values:

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;
        }

Now assume that my entity has a single key property which is a string.

Working scenario: stored entity has key "ABCDE" and my toUpdate entity has same key "ABCDE": everything works fine.

Bug scenario: stored entity has key "ABCDE" and my toUpdate entity has key "ABCDE " (notice the space after the last letter).

The two keys are indeed different. But the find method "automagically" trims my key and finds the stored entity anyway. This would be good, if it didn't break the SetValues method: since the stored key and the new key are different, I get (rightly) the following:

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

Because, being different, it tries to update it, and since it's a key property it cannot be updated, so the whole thing fails and throws.

What I think is that the "Find" method shouldn't automagically trim the key values (or whatever it is doing internally to make the two different strings appear the same). In the second scenario, the "Find" method should return null.

Now two things: how do I workaround this temporarily, and where can I report this bug, because I couldn't find an official place to report the bug.

Thanks.

EDIT: Reported the bug here: https://connect.microsoft.com/VisualStudio/feedback/details/696352/ef-code-first-4-1-find-update-bug

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

像你 2024-12-18 08:07:10

但是 find 方法“自动”修剪我的密钥并找到存储的
无论如何都是实体。

我不相信会发生修剪。 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 属性设置为更新为从数据库返回的对象的键:

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 键列/属性的充分理由。

But the find method "automagically" trims my key and finds the stored
entity anyway.

I dont' believe that trimming happens. Find uses a SingleOrDefault query internally, in other words: When you call...

set.Find("ABCDE "); // including the trailing blank

...it uses this LINQ query:

set.SingleOrDefault(key => key == "ABCDE "); // including the trailing blank

The problem is in the database and the result depends on the sort order, language, settings for capital/small letters, accents, etc. for your string key field in the database (nvarchar(10) for example).

For example if you use a standard Latin1_General sort order in SQL Server the key "ABCDE" and "ABCDE " (with trailing blank) are identical, you cannot create two rows which have these values as primary keys. Even "ABCDE" and "abcde" are identical (if you don't setup to distinguish capital and small letters in SQL Server).

At the same time this means that also queries for string columns will return all matching rows - matching with respect to the sort order of that column in the database. The query for "ABCDE " with trailing blank will just return a record with "ABCDE" without the trailing blank.

Up to this point this is "normal" behaviour for all LINQ to Entities queries which involve strings.

Now, it seems - as you found - that the ObjectContext doesn't know about the configured sort order in the database and uses the normal .NET string comparison where a string with and a string without trailing blank are different.

I don't know if it's possible to tell the context to use the same comparison of strings as the database. I have some doubt that this is possible because the .NET world and the relational database world are too different. Some sort orders might be special and only available in the database and not in .NET at all and vice versa perhaps. In addition there are also other databases than SQL Server which must be supported by Entity Framework and those databases might have their own sort order system.

For your specific case - and perhaps always when you have string keys - a possible fix of your problem would be to set the key property of the entity to update to the key of the object returned from the database:

toUpdate.Id = current.Id;
_context.Entry(current).CurrentValues.SetValues(toUpdate);

Or more generally in the context of your code:

//...
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 must not be attached to the context to get this working.

Is this a bug? I don't know. At least it is a consequence of a mismatch between .NET and relational database world and a good reason to avoid string key columns/properties in the first place.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文