Difference in execution between List<T> and IQueryable<T>
我正在尝试编写一个通用的数据库更新方法,它可以利用iQueryable在处理之前减少返回实体的数量。因此,对于代码的一部分,我尝试了这个方法(b.ToType()返回P:
1 2 3 4 5 6
| IQueryable bs = bcontext.Set();
IQueryable<p>
ps = pcontext.Set<p>
();
List<p>
inserts = ps.Except(bs.Select(b => b.ToType())).Take(500).ToList(); |
当我这样写的时候,我得到了System.ArgumentNullException: 'Value cannot be null.'。
但是,当我在这样做Except之前继续枚举dbset时,它是有效的:
1 2 3 4 5 6
| List bs = bcontext.Set().ToList();
List<p>
ps = pcontext.Set<p>
().ToList();
List<p>
inserts = ps.Except(bs.Select(b => b.ToType())).Take(500).ToList(); |
两种方法都编译得很好,但是我第一种方法得到异常,而不是第二种方法。对于不存在于列表中的IQueryable表达式树,是否存在一些限制?
- 您使用的是哪个LINQ提供商?EF6?EF核心?LINQtoSQL?还有别的吗?另外,如果你写IQueryable
ps2 = bs.Select(b => b.ToType());
会发生什么,如果你在ps2上称.ToList会发生什么?当然也有一些限制——在表达式树中没有这么多限制(尽管有这样的限制),但更多的是表达式树的哪些部分可以转换为SQL(或者基础数据访问语言是什么)。例如,针对SQL Server的EF6在调用.ToString时会阻塞,因为没有简单的方法可以将其转换为SQL。但如果是这样的话…
- …我希望错误更为明确:linq to entities无法识别方法"system.string toString()"方法,并且此方法无法转换为存储表达式。这就是为什么我怀疑bs.Select实际上是出于某种原因调用Enumerable.Select,bs的结果之一是null。
- 我用的是英孚核心。江户十一〔8〕行,以后我再行江户十一〔9〕行,就行了。
下面是IQueryable.Except的实现,请查看:
1 2 3 4 5 6 7 8 9 10 11 12
| public static IQueryable <TSource > Except <TSource >(this IQueryable <TSource > source1, IEnumerable <TSource > source2 ) {
if (source1 == null)
throw Error .ArgumentNull("source1");
if (source2 == null)
throw Error .ArgumentNull("source2");
return source1 .Provider.CreateQuery<TSource >(
Expression .Call(
null,
GetMethodInfo (Queryable .Except, source1, source2 ),
new Expression [] { source1 .Expression, GetSourceExpression (source2 ) }
));
} |
IQueryable和List的工作之间的主要区别是,可查询类型内部与Expression>一起工作,因为它是远程执行的,在您使用提供程序的情况下,当List与Func一起工作时,因为它是内存处理。当涉及到远程处理时,比如ef转换成相关的SQL查询进行处理,在您的情况下,在远程处理期间,以下转换为null:bs.Select(b => b.ToType())。
以下是IEnumerable.Except的实施情况,请查看:
1 2 3 4 5 6 7
| public static IEnumerable<TSource> Except<TSource>(this IEnumerable<TSource> first,
IEnumerable<TSource> second)
{
if (first == null) throw Error.ArgumentNull("first");
if (second == null) throw Error.ArgumentNull("second");
return ExceptIterator<TSource>(first, second, null);
} |
Except本身就是一个集合操作,即使对于List调用Except(null)也会导致相同的异常。
正如您所看到的IQueryable.Except的定义,理解Expression and Func处理过程中的差异非常重要,表达式更多地是关于要做什么,func是关于如何检查这一点。
对于一个简单的var intList = new List{1,2,3},这就是可查询表达式的样子(如所附图片所示)。
本质仍然是检查提供者在内部将可查询表达式转换为什么,这将导致空值,从而在处理时出现异常。
- 表达式更多的是关于要做什么,而func是关于如何做这件事的,这并不十分准确;更准确的说法是,Func指向的是一种实际执行某些操作的方法,就像Action action = Console.WriteLine;那样,而Expression是一种数据结构,它描述了代码的某些部分,以获得某些结果,因此您不能编写Expression expr = Console.WriteLine;,因为Console.WriteLine是一个真正的方法;最多你可以编写Expression expr = () => Console.WriteLine();,编译器会把它转换成一个数据结构,其中包含"调用……
- …无参数的Console.WriteLine方法。作为链接到状态的答案,lambda表达式语法可用于两者,编译器根据lambda表达式是分配给Func还是分配给Expression来不同地解释lambda表达式。
- 像这样,最终表达式可以编译成func,func是实际的包含委托的方法,比如您的方法,在内存处理方面效果很好,但是在与mongo db、redis cache等远程提供者集成时,这样的实现Expression expr = () => Console.WriteLine();没有意义,因为它们不直接编译,所以需要通知关于你想要实现什么,而不是如何实现,他们有自己的内置API来实现它,直到他们确切地知道什么部分,这是使用iQuery的表达式树的本质和用例
- 事实上,我们可以通过详细的过程来创建表达式树,详细解释各种属性和参数,但使用lambda语法并不总是可能的,它是为内存处理编译的快速机制。也可以与紧密集成的MS提供商合作,但不能与第三方系统合作。
- 我对你所说的做出了回应:表达更多的是关于做什么,而func是关于如何做。同样,这是不准确的。Func是一个实际执行(通过指向一个方法)的对象;Expression是一个描述如何执行或如何执行的数据结构。
- 当讨论EDOCX1(和相关类型)和EDOCX1(和相关类型)之间的差异时,大多数提供者没有利用表达式树中所有可能的操作这一事实并不是这两个类型族之间差异的反映。另外,使用独立于IQueryable的lambda表达式也是很有可能的——ASP.NET MVC允许传递lambda表达式,该表达式返回匿名类型以传递名称/属性对;我将其用作获取MethodInfo的简单方法,而不是使用直上反射。
- 仅仅因为您可以在MS系统中以类似于func的方式使用表达式树,并不意味着它告诉您如何使用,它仍然是什么。如何总是关于消费/远程系统,表达式树从来没有打算如何,因为它已经有一个func,为什么创建一个副本。如果这有助于更好地理解,那就太好了,否则你可以继续你的观点。
- 表达式树也可以在IQueryable之外使用,后者主要是在运行时创建Func,反之亦然,因为IQueryable处理远程系统,所以它总是需要表达式来解释它们需要实现什么,这里怎么甚至不考虑呢
- 事实上,作为链接的作者,我在文章中提到了像Func myFunc = () => 10;这样简单的东西,它只返回一个值10,但它与Expression> myExpression = () => 10;不同,因为这创建了一个详细的数据结构来解释需要实现什么,在哪里依赖于消费系统,取决于如何解析细节
通常,iQueryable用于避免执行查询,直到它缩小到我们将获得实际所需数据的精确点为止。
与list相比,当我们执行.tolist()时,查询将被执行,并且我们在内存中有所有的结果,从中您可以查询或筛选出结果。
根据客户端的性能或网络,可以选择正确的选项。doing.tolist将在内存中提供结果,从中可以执行操作。
对于参考资料,我会将您重定向到此答案:iQuery、List和IEnumerator之间的区别?