entity-framework - 如何模拟EntityFramework的IQueryable实现的局限性

标签 entity-framework mocking iqueryable

我目前正在为MVC4应用程序中的存储库实现编写单元测试。为了模拟数据上下文,我首先采用了this post的一些想法,但是现在我发现了一些局限性,这些疑问使我怀疑是否有可能正确模拟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实例的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实现的行为是否不切实际?

谢谢你的想法

蒂姆

最佳答案

我认为模拟 Entity Framework 的行为非常困难,甚至不可能。首先也是最重要的是,因为这需要对linq-entent与linq-to-objects不同的所有特性和边缘情况的深入了解。正如您所说:真正的挑战是找到它们。让我指出三个主要 Realm ,而不是声称自己几乎是详尽无遗的:

Linq到对象成功而Linq到实体失败的情况:

  • .Select(x => x.Property1.ToString()。 LINQ to Entities无法识别方法'System.String ToString()'方法...这几乎适用于本机.Net类中的几乎所有方法,当然也适用于自己的方法。只有少数.Net方法将转换为SQL。参见CLR Method to Canonical Function Mapping。从EF 6.1开始,该方式支持ToString。但是只有无参数过载。
  • 前面没有Skip()OrderBy
  • ExceptIntersect:可能会产生令人讨厌的查询,从而引发您的SQL语句的某些部分嵌套得太深。重写查询或将其分解为较小的查询。
  • Select(x => x.Date1 - x.Date2):DbArithmeticExpression参数必须具有数字通用类型。
  • (您的情况).Where(p => p.Category == category):在此上下文中仅支持原始类型或枚举类型。
  • Nodes.Where(n => n.ParentNodes.First().Id == 1):方法“First”只能用作最终查询操作。
  • context.Nodes.Last():LINQ to Entities无法识别方法'... Last ...'。这适用于许多其他IQueryable扩展方法。参见Supported and Unsupported LINQ Methods
  • (请参阅下面的Slauma注释):.Select(x => new A { Property1 = (x.BoolProperty ? new B { BProp1 = x.Prop1, BProp2 = x.Prop2 } : new B { BProp1 = x.Prop1 }) }):类型B在here的单个LINQ to Entities查询中出现在两个结构不兼容的初始化中。
  • context.Entities.Cast<IEntity>():无法将类型“Entity”强制转换为类型“IEntity”。 LINQ to Entities仅支持强制转换EDM基本类型或枚举类型。
  • .Select(p => p.Category?.Name)。在表达式中使用空传播会引发CS8072表达式树lambda可能不包含空传播运算符。这may get fixed one day
  • 这个问题:Why does this combination of Select, Where and GroupBy cause an exception?让我意识到,甚至EF都不支持整个查询结构,而L2O不会有任何麻烦。

  • Linq-to-Object失败并且Linq-to-Entities成功的情况:
  • .Select(p => p.Category.Name):当p.Category为null时,L2E返回null,但是L2O抛出未设置为对象实例的Object reference。这不能通过使用空传播来解决(请参见上文)。
  • Nodes.Max(n => n.ParentId.Value),其中n.ParentId具有一些空值。 L2E返回一个最大值,L2O引发Nullable对象必须具有一个值。
  • 使用EntityFunctions(从EF 6开始为DbFunctions)或SqlFunctions

  • 都成功/失败但行为不同的情况:
  • Nodes.Include("ParentNodes"):L2O没有包含的实现。它将运行并返回节点(如果NodesIQueryable),但没有父节点。
  • 带有一些空Nodes.Select(n => n.ParentNodes.Max(p => p.Id))集合的
  • ParentNodes:都失败,但有不同的异常(exception)。
  • Nodes.Where(n => n.Name.Contains("par")):L2O区分大小写,L2E取决于数据库排序规则(通常不区分大小写)。
  • node.ParentNode = parentNode:具有双向关系,在L2E中,还将节点添加到父节点的节点集合中(关系修正)。不在L2O中。 (请参阅Unit testing a two way EF relationship)。
  • 空传播失败的解决方法:.Select(p => p.Category == null ? string.Empty : p.Category.Name):结果相同,但是生成的SQL查询还包含空检查,可能难以优化。
  • Nodes.AsNoTracking().Select(n => n.ParentNode。这个非常棘手! 。使用AsNoTracking,EF会为每个ParentNode创建新的Node对象,因此可以有重复项。没有AsNoTracking,EF会重复使用现有的ParentNodes,因为现在涉及到实体状态管理器和实体密钥。可以在L2O中调用AsNoTracking(),但它不会执行任何操作,因此,无论有没有它,都不会有任何区别。

  • 那么关于模拟延迟/急切加载以及上下文生命周期对延迟加载异常的影响又如何呢?或某些查询构造对性能的影响(例如触发N + 1个SQL查询的构造)。还是由于重复或缺少实体密钥而导致异常?还是关系修正?

    我的意见:没有人会伪造那个。最令人担忧的区域是L2O成功和L2E失败的地方。现在,绿色单元测试的值(value)是什么?之前已经说过,EF只能在集成测试(例如here)中可靠地测试,我倾向于同意。

    但是,这并不意味着我们应该忘记以EF作为数据层的项目中的单元测试。有ways to do it,但我认为并非没有集成测试。

    关于entity-framework - 如何模拟EntityFramework的IQueryable实现的局限性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13332002/

    相关文章:

    c# - MVC、 View 和 System.Data.Entity

    java - 将模拟添加到集合适用于 .times,而不适用于 for 循环

    unit-testing - 什么是模拟?

    c# - 使用 IQueryable<T> 和自定义函数时优化对数据库的访问次数

    c# - 通过 Entity Framework 将整数数组传递给 T-SQL 存储过程

    c# - 如果 Entity Framework/DbContext 是 DAL/Repository,它在 3 层架构中的什么位置?

    c# - 我在 EntityFramework 5 中找不到 System.Data.Entity.Validation

    javascript - 用 casperjs 伪造 xmlhttprequests

    entity-framework - 从 EF 生成动态 IQueryable<T>

    c# - Linq 用表达式查询抽象,哪种方式更好?