List<T>和IQueryable<T>执行的区别

3

我正试图编写一个通用的数据库更新方法,可以利用IQueryable来缩小处理前返回的实体数量。 因此,在代码的一部分中,我尝试了以下内容(b.ToType()返回P):

IQueryable<B> bs = bcontext.Set<B>();
IQueryable<P> ps = pcontext.Set<P>();
List<P> inserts = ps.Except(bs.Select(b => b.ToType())).Take(500).ToList();

当我像这样写时,会出现System.ArgumentNullException: '值不能为空。'

然而,在执行Except之前枚举DBSets可以解决问题,就像这样:

List<B> bs = bcontext.Set<B>().ToList();
List<P> ps = pcontext.Set<P>().ToList();
List<P> inserts = ps.Except(bs.Select(b => b.ToType())).Take(500).ToList();

两种方式都可以编译通过,但第一种方式会抛出异常而第二种则不会。是否存在某些限制,使得在IQueryable表达式树中无法使用的内容,在List中却可以使用?


你使用哪个LINQ提供程序?EF6?EF Core?LINQ-to-SQL?还是其他什么?另外,如果你写了 IQueryable<P> ps2 = bs.Select(b => b.ToType());,会发生什么?如果在 ps2 上调用 .ToList 会发生什么?当然有限制——不是在表达式树中(虽然也有这样的限制)——而是更多地涉及表达式树的哪些部分可以转换为SQL(或者底层数据访问语言)。例如,针对SQL Server的EF6将无法处理对.ToString的调用,因为没有简单的方法将其转换为SQL。但如果是这种情况... - Zev Spitz
我希望错误信息更明确一些:LINQ to Entities 不识别 'System.String ToString()' 方法,因此该方法无法转换为存储表达式。 这就是我怀疑 bs.Select 实际上由于某种原因调用了 Enumerable.Select,并且 bs 中的一个结果是 null 的原因。 - Zev Spitz
我正在使用EF Core。List<P> p1 = ps2.ToList()可以正常工作,如果在此之后执行ps.Except(p1)也可以正常工作。 - Kristen Hammack
2个回答

2
通常情况下,IQueryable 用于避免查询的执行,直到它被缩小到我们获取实际所需数据的确切点为止。
与 List 相比,当我们使用 .ToList() 时,查询将被执行,并且我们有所有结果存储在内存中,从那里你可以查询或过滤结果。
根据客户端或网络的性能,可以选择正确的选项。使用 .ToList 将会在内存中返回结果,从那里你可以执行操作。
参考答案请查看: Differences between IQueryable, List, IEnumerator?

2
以下是实现 IQueryable<T>.Except 的代码,可以在 这里 进行查看:
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 的主要区别在于,Queryable 类型在内部使用 Expression> 进行操作,因为它是远程执行的,例如在您的情况下使用提供程序执行时,而 List 则使用 Func 进行内存处理。当涉及到类似 EF 这样的远程处理时,它会将相关的 SQL 查询转换为处理语句,而在您的情况下,bs.Select(b => b.ToType()) 在远程处理过程中会转换为 null。
以下是 IEnumerable.Except 的实现,可在此处查看:here
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<T>”,调用“Except(null)”也会导致相同的异常。
正如您所看到的,“IQueryable<T>.Except”的定义,重要的是要理解“Expression和Func”的处理方式的差异,表达式更多关注做什么,而Func则是关于如何检查this
对于一个简单的“var intList = new List<int>{1,2,3}”,这就是可查询表达式的样子(如附图所示)。
实质上,需要检查您的提供程序内部将Queryable Expression转换为什么,这导致了空值,因此在处理过程中出现异常。

enter image description here


表达式更多关注做什么,而 Func 关注如何做。这不完全准确;更准确的说法是 Func 指向实际执行某些操作的方法,就像 Action action = Console.WriteLine; 一样,而 Expression 是描述代码部分以获得某个结果的数据结构。你不能编写 Expression<Action> expr = Console.WriteLine;,因为 Console.WriteLine 是一个真正的方法;最多你可以编写 Expression<Action> expr = () => Console.WriteLine();,然后编译器会将其转换为包含“调用...”的数据结构。 - Zev Spitz
正如您链接的答案所述,lambda表达式语法可用于两者,并且编译器根据它是被分配给Func还是Expression而不同地解释lambda表达式。如果没有参数,则可以使用Console.WriteLine方法。 - Zev Spitz
实际上,我们可以通过详细的过程来创建表达式树,详细说明各种属性和参数,这是使用Lambda语法不总是可能的,Lambda语法更多地是用于快速机制以进行内存处理。它也可以与紧密集成的MS提供程序一起使用,但不能与第三方系统一起使用。 - Mrinal Kamboj
当讨论Func<T> (及相关类型)和Expression<Func<T>> (及相关类型)之间的区别时,事实上大多数提供者并不使用表达式树中所有可能的操作,并不反映这两个类型家族之间的差异。此外,独立于IQueryable使用Lambda表达式是完全可能的——ASP.NET MVC允许传递返回匿名类型的Lambda表达式以传递名称/属性对;我已经将其用作获取MethodInfo的更简单方法,而不是直接使用反射。 - Zev Spitz
此外,表达式树可以在IQueryable<T>之外使用,主要是为了在运行时创建Func<T>,但反过来则不成立,因为IQueryable<T>处理远程系统,它总是需要表达式来解释他们需要实现的“什么”,而“如何”甚至不是一个考虑因素。 - Mrinal Kamboj
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接