Код EF 4.1 Первая ошибка в Find / Update.Любое решение?Стоит ли сообщать об этом? - PullRequest
3 голосов
/ 21 октября 2011

Я нашел довольно неприятную ошибку в 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» должен возвращать ноль.

Теперь две вещи: как мне временно обойти это, и где я могу сообщить об этой ошибке, потому что я не смог найти официальное место для отчетаошибка.

Спасибо.

РЕДАКТИРОВАТЬ: сообщил об ошибке здесь: https://connect.microsoft.com/VisualStudio/feedback/details/696352/ef-code-first-4-1-find-update-bug

1 Ответ

3 голосов
/ 21 октября 2011

Но метод find "автоматически" обрезает мой ключ и в любом случае находит сохраненную сущность.

Я не верю, что обрезка происходит.Find использует внутренний запрос SingleOrDefault, другими словами: когда вы звоните ...

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

... он использует этот запрос LINQ:

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

Проблеманаходится в базе данных, и результат зависит от порядка сортировки, языка, настроек прописных / строчных букв, акцентов и т. д. для ключевого поля string в базе данных (например, nvarchar(10)).

Например, если вы используете стандартный порядок сортировки Latin1_General в SQL Server, ключи "ABCDE" и "ABCDE" (с завершающим пробелом) идентичны, вы не можете создать две строки, которыеиметь эти значения в качестве первичных ключей.Даже "ABCDE" и "abcde" идентичны (если вы не настроили различать заглавные и строчные буквы в SQL Server).

В то же времяэто означает, что также запросы для столбцов string будут возвращать все совпадающие строки - в соответствии с порядком сортировки этого столбца в базе данных.Запрос для "ABCDE" с завершающим пробелом просто вернет запись с "ABCDE" без завершающего пробела.

До этого момента это "нормально"Поведение для всех запросов LINQ to Entities, которые содержат строки.

Теперь, как вы обнаружили, кажется, что ObjectContext не знает о настроенном порядке сортировки в базе данных и использует обычное сравнение строк .NET, гдестрока с и строка без завершающего пробела различаются.

Я не знаю, можно ли указать контексту использовать такое же сравнение строк, что и в базе данных.У меня есть некоторые сомнения, что это возможно, потому что мир .NET и мир реляционных баз данных слишком различны.Некоторые порядки сортировки могут быть специальными и доступны только в базе данных, а не в .NET вообще, и наоборот, возможно.Кроме того, существуют и другие базы данных, кроме SQL Server, которые должны поддерживаться Entity Framework, и эти базы данных могут иметь собственную систему порядка сортировки.

Для вашего конкретного случая - и, возможно, всегда, когда у вас есть ключи string -возможное решение вашей проблемы - установить свойство ключа объекта для обновления на ключ объекта, возвращенного из базы данных:

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 ключевые столбцы / свойства.

...