我如何确定LINQ查询将是LINQ到SQL还是LINQ到对象?

6
通常情况下,LINQ to SQL和LINQ to Objects之间的区别并不是什么大问题,但是如何确定正在发生哪种情况呢?
在编写代码时了解这一点会很有用,但有时只能在运行时确保。

3
好的,不是特别针对于 LINQ to SQL 和 LINQ to Objects……但通常来说,如果你有一个 IEnumerable<T>,操作将使用 LINQ to Objects,但如果你有一个 IQueryable<T>,操作将使用“其他东西”。(当然,AsQueryable 会将 IEnumerable<T> 包装成 IQueryable<T>,所以并不是100%......)你实际上在寻找什么样的东西? - Jon Skeet
1
如果是 IQueryable,请谨慎处理。您可以始终使用 AsEnumerable() 选择内存中的处理方式,但必须注意其影响(将所有数据加载到内存中)。 - haim770
如果您有一个IQueryable<T>,操作将使用“其他东西”。那么这个“其他东西”是什么?你怎么能确定呢?难道我们只是期望计算机处理所有这些事情而不需要完全理解吗? - Protector one
1
@Protectorone:这只是一个抽象概念...想法是通常你不需要知道。例如,如果它是LINQ to SQL还是EF,你会用这个知识做什么呢?(再次强调,在问题中更清晰地表达你想要确定哪些信息以及你想要对这些信息做什么将会很有帮助。) - Jon Skeet
1
那么这似乎是需要更多了解LINQ工作原理的问题,而不是你最后一句话所暗示的代码需要处理的问题。你可能想阅读一下我的Edulinq博客系列的一些部分:https://codeblog.jonskeet.uk/category/edulinq/ 但我现在不确定对你的帖子有什么好的回答... - Jon Skeet
显示剩余3条评论
4个回答

3
这并不是微小的优化,而是需要区分Linq-To-SQL和Linq-To-Objects。后者要求在开始过滤之前将所有数据加载到内存中。当然,这可能是一个重大问题。
大多数LINQ方法都使用延迟执行,这意味着它只是构建了查询,但尚未执行(例如Select或Where)。少数其他方法执行查询并将结果实现为内存集合(例如ToList或ToArray)。如果使用AsEnumerable,还将使用Linq-To-Objects,并且在其后部分不会生成SQL,这意味着必须将数据加载到内存中(仍然使用延迟执行)。
因此,请考虑以下两个查询。第一个在数据库中选择和筛选:
var queryLondonCustomers = from cust in db.customers
                           where cust.City == "London"
                           select cust;

而第二种方法则通过Linq-To-Objects选择所有内容并进行筛选:

var queryLondonCustomers = from cust in db.customers.AsEnumerable()
                           where cust.City == "London"
                           select cust;

后者有一个优点:你可以使用任何 .NET 方法,因为它不需要被翻译成 SQL(例如!String.IsNullOrWhiteSpace(cust.City))。
如果你只是得到一个 IEnumerable<T>,你不能确定它实际上是一个查询还是已经是内存中的一个对象。即使尝试将其转换为 IQueryable<T>,也不能确定它实际上是什么,因为有 AsQueryable-method 存在。也许你可以尝试将其转换为集合类型。如果转换成功,你可以确定它已经被实现了,但否则它并不告诉你它是使用的 Linq-To-Sql 还是 Linq-To-Objects
bool isMaterialized = queryLondonCustomers as ICollection<Customer> != null;

相关内容: EF ICollection Vs List Vs IEnumerable Vs IQueryable


EF ICollection、List、IEnumerable和IQueryable之间的区别

1
也许值得补充一下,AsEnumerable() 不会立即执行查询。延迟执行仍然保留,因此您可以进一步添加 Select()Where(),但它们最终将被 Linq to Objects 拦截。 - haim770
@Protectorone,不是一样的。AsEnumerable()不会实现查询(请参见http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,825)。 - haim770
你不能确定它实际上是一个查询还是已经是内存对象了。我们不能吗?真的吗?我们应该对此感到满意吗?这让我感到无知或懒惰... - Protector one

3

我首先想到的解决方案是检查查询提供程序

如果查询已经被实例化,也就是数据已经加载到内存中,那么会使用EnumerableQuery(T)。否则,会使用一个特殊的查询提供程序,例如对于entityframework,会使用System.Data.Entity.Internal.Linq.DbQueryProvider

var materialized = query
                  .AsQueryable()
                  .Provider
                  .GetType()
                  .GetGenericTypeDefinition() == typeof(EnumerableQuery<>);

然而,上述情况是理想的案例,因为有人可以实现一个自定义的查询提供程序,就像 EnumerableQuery 一样运行。

2

在net core 6中使用EF core

要查看提供程序是否为EF提供程序,请使用以下代码:

if (queryable.Provider is Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider)
{
    // Queryable is backed by EF and is not an in-memory/client-side queryable.
}

通过对提供程序进行 System.Linq.EnumerableQuery 的测试(EnumerableQuery<T> 的基本类型 - 因此您不必测试泛型),可以获得相反的结果。

如果您有像 EF.Functions.Like(...) 这样只能在数据库中执行的方法,并且您希望在客户端执行时分支到其他内容,则此功能非常有用。


这种分支结构在数据库中非常有用,因为 "==" 运算符将 "é" 和 "e" 视为相同的字母,但是在内存数据中不是这样。 - Master DJon
这种分支在数据库中非常有用,因为"=="会将"é"和"e"视为相同的字母,但在内存数据中却不是这样。 - undefined

2

出于不同的原因,我有相同的问题。

仅根据您的标题和初始描述(这就是为什么谷歌搜索将我带到这里的原因),可以判断在预编译时,鉴于实现IQueryable的实例,没有办法知道接口背后的实现。

在运行时,您需要检查实例的Provider属性,就像@Danny Chen提到的那样。

public enum LinqProvider
{
    Linq2SQL, Linq2Objects
}

public static class LinqProviderExtensions
{
    public static LinqProvider LinqProvider(this IQueryable query)
    {

        if (query.Provider.GetType().IsGenericType && query.Provider.GetType().GetGenericTypeDefinition() == typeof(EnumerableQuery<>))
            return LinqProvider.Linq2Objects;
        if (typeof(ICollection<>).MakeGenericType(query.ElementType).IsAssignableFrom(query.GetType()))
            return LinqProvider.Linq2Objects;

        return LinqProvider.Linq2SQL;
    }
}

在我们的情况下,我们动态添加了额外的过滤器,但是在不同的提供程序上,大小写敏感性/空引用处理的处理方式不同,因此在运行时,我们必须根据提供程序的类型调整添加的过滤器,并最终添加了这个扩展方法:


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