Подтвердить что ты не робот

Как издеваться над ограничениями реализации EntityFramework IQueryable

В настоящее время я пишу модульные тесты для реализации моего репозитория в приложении MVC4. Чтобы издеваться над контекстом данных, я начал с принятия некоторых идей из этого сообщения, но теперь я обнаружил некоторые ограничения, которые заставляют меня сомневаться в том, возможно ли это правильно mock IQueryable.

В частности, я видел некоторые ситуации, когда тесты проходят, но код не работает, и я не смог найти способ издеваться над поведением, которое вызывает этот сбой.

Например, следующий фрагмент используется для выбора объектов Post, которые входят в предопределенный список категорий:

var posts = repository.GetEntities<Post>(); // Returns IQueryable<Post>
var categories = GetCategoriesInGroup("Post"); // Returns a fixed list of type Category
var filtered = posts.Where(p => categories.Any(c => c.Name == p.Category)).ToList();

В моей тестовой среде я попытался издеваться над posts с помощью упомянутой выше реализации поддельной DbSet, а также создав экземпляры List of Post и преобразовывая ее в IQueryable с помощью AsQueryable() способ расширения. Оба этих подхода работают в условиях тестирования, но код фактически не работает в производстве, за исключением следующего:

System.NotSupportedException : Unable to create a constant value of type 'Category'. Only primitive types or enumeration types are supported in this context.

Хотя проблемы LINQ, подобные этому, достаточно легки для исправления, реальная задача заключается в их поиске, учитывая, что они не раскрываются в тестовой среде.

Я нереалистичен в ожидании того, что я могу высмеять поведение реализации Entity Framework IQueryable?

Спасибо за ваши идеи,

Тим.

4b9b3361

Ответ 1

Я думаю, что очень сложно, если возможно, издеваться над поведением Entity Framework. Прежде всего, потому что это потребует глубокого знания всех особенностей и крайних случаев, когда linq-to-entites отличается от linq-to-objects. Как вы говорите: реальная задача - найти их. Позвольте мне указать на три основные области, не претендуя на то, чтобы быть даже почти исчерпывающим:

Случаи, когда Linq-to-Objects преуспевают, а Linq-to-Entities терпят неудачу:

  • .Выберите (x = > x.Property1.ToString(). LINQ to Entities не распознает метод "System.String ToString()"... Это относится ко почти всем методам в собственных классах .Net и, конечно же, к собственным методам. Только несколько методов .Net будут переведены в SQL. См. Метод CLR для канонического отображения функций. С точки зрения EF 6.1 поддерживается ToString. Но только безпараметрическая перегрузка.
  • Пропустить() без предшествующего OrderBy.
  • За исключением и Intersect: может создавать чудовищные запросы, которые бросают Некоторая часть вашего оператора SQL слишком глубоко вложена. Перепишите запрос или разделите его на более мелкие запросы.
  • Выберите (x = > x.Date1 - x.Date2): Аргументы DbArithmeticExpression должны иметь числовой общий тип.
  • (ваш случай) .Where(p = > p.Category == category): В этом контексте поддерживаются только примитивные типы или типы перечислений.
  • Nodes.Where(n = > n.ParentNodes.First(). Id == 1): Метод "Первый" может использоваться только как конечная операция запроса.
  • context.Nodes.Last(): LINQ to Entities не распознает метод "... Last..." . Это относится ко многим другим методам расширения IQueryable. См. Поддерживаемые и неподдерживаемые методы LINQ.
  • (см. комментарий Slauma ниже): . Выбрать (x = > new A {Property1 = (x.BoolProperty? new B {BProp1 = x.Prop1, BProp2 = x.Prop2}: новый B {BProp1 = x.Prop1})}): Тип "B" появляется в двух структурно несовместимых инициализациях внутри одного запроса LINQ to Entities... из here.
  • context.Entities.Cast <IEntity>(): Невозможно применить тип 'Entity' к типу 'IEntity'. LINQ to Entities поддерживает только листинг примитивных или перечисляемых типов EDM.
  • .Выберите (p = > p.Category?.Name). Использование пустого распространения в выражении throws CS8072 Дерево выражений lambda может не содержать нулевой оператор распространения. Этот может быть исправлена ​​в один прекрасный день.
  • Этот вопрос: Почему эта комбинация Select, Where и GroupBy вызывает исключение? заставило меня осознать факт что существуют даже целые построения запросов, которые не поддерживаются EF, в то время как L2O не будет иметь с ними никаких проблем.

Случаи, когда объекты Linq-to-Objects терпят неудачу, и Linq-to-Entities преуспевает:

  • .Выберите (p = > p.Category.Name): когда p.Category имеет значение null L2E возвращает значение null, но L2O выбрасывает Ссылка на объект не установленный в экземпляр объекта. Это невозможно устранить, используя нулевое распространение (см. выше).
  • Nodes.Max(n = > n.ParentId.Value) с некоторыми нулевыми значениями для n.ParentId. L2E возвращает максимальное значение, L2O throws Объект Nullable должен иметь значение.
  • Использование EntityFunctions ( DbFunctions с EF 6) или SqlFunctions.

Случаи, когда оба успеха/неудачи, но ведут себя по-другому:

  • Nodes.Include( "ParentNodes" ): L2O не имеет реализации include. Он будет запускаться и возвращать узлы (если Nodes является IQueryable), но без родительских узлов.
  • Nodes.Select(n = > n.ParentNodes.Max(p = > p.Id)) с некоторыми пустными коллекциями ParentNodes: оба не работают, но с различные исключения.
  • Nodes.Where(n = > n.Name.Contains( "par" )): L2O чувствителен к регистру, L2E зависит от сортировки базы данных (часто не чувствительной к регистру).
  • node.ParentNode = parentNode: с двунаправленным отношением, в L2E это также добавит node в коллекцию узлов родительского (связь fixup). Не в L2O. (См. Единичное тестирование двухсторонних отношений EF).
  • Работа для обхода нулевого распространения: .Select(p = > p.Category == null? string.Empty: p.Category.Name): результат будет таким же, но сгенерированный SQL-запрос также содержит нулевую проверку и может быть сложнее оптимизировать.
  • Nodes.AsNoTracking(). Выберите (n = > n.ParentNode). Этот очень сложный!. С AsNoTracking EF создает новый объект ParentNode для каждого Node, поэтому могут быть дубликаты. Без AsNoTracking EF повторно использует существующие ParentNodes, потому что теперь задействованы диспетчер состояний объекта и сущности. AsNoTracking() можно вызвать в L2O, но он ничего не делает, поэтому никогда не будет никакой разницы с ним или без него.

А как насчет насмешливой ленивой/нетерпеливой загрузки и влияния жизненного цикла контекста на ленивые ошибки загрузки? Или эффект некоторых конструкций запроса на производительность (например, конструкции, которые запускают N + 1 SQL-запросы). Или исключения из-за дублирования или отсутствия ключей сущностей? Или исправление отношений?

Мое мнение: никто не собирается подделывать это. Наиболее тревожная область - это то, где L2O преуспевает, а L2E терпит неудачу. Теперь, какова ценность тестов зеленого блока? Ранее было сказано, что EF можно надежно тестировать только в тестах интеграции (например, здесь), и я склонен согласиться.

Однако это не означает, что мы должны забыть об модульных тестах в проектах с EF в качестве уровня данных. Существуют способы сделать это, но, Я думаю, не без интеграционных тестов.