关于实体框架:.ToList(),. AsEnumerable(),AsQueryable()之间的区别是什么?

What's the difference(s) between .ToList(), .AsEnumerable(), AsQueryable()?

我知道linq与实体和linq与对象的一些区别,前者实现IQueryable,后者实现IEnumerable,我的问题范围在ef 5之内。

我的问题是这三种方法的技术差异是什么?我看到在很多情况下,它们都能工作。我还看到使用它们的组合,如.ToList().AsQueryable()

  • 这些方法到底是什么意思?

  • 是否存在性能问题或导致使用一个而另一个的问题?

  • 例如,为什么要使用.ToList().AsQueryable()而不是.AsQueryable()


  • 关于这个有很多话要说。让我把重点放在AsEnumerableAsQueryable上,一路上提到ToList()。好的。这些方法有什么作用?

    AsEnumerableAsQueryable分别铸造或转换为IEnumerableIQueryable。我说演员或转换有一个原因:好的。

    • 当源对象已经实现目标接口时,将返回源对象本身,但将其强制转换到目标接口。换句话说:类型没有更改,但编译时类型是。好的。

    • 当源对象不实现目标接口时,源对象将转换为实现目标接口的对象。因此类型和编译时类型都会更改。好的。

    让我用一些例子来说明这一点。我有一个报告编译时类型和对象实际类型的小方法(由jon skeet提供):好的。

    1
    2
    3
    4
    5
    void ReportTypeProperties<T>(T obj)
    {
        Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
        Console.WriteLine("Actual type: {0}", obj.GetType().Name);
    }

    让我们尝试一个任意的linq-to-sql Table,它实现了IQueryable:好的。

    1
    2
    3
    ReportTypeProperties(context.Observations);
    ReportTypeProperties(context.Observations.AsEnumerable());
    ReportTypeProperties(context.Observations.AsQueryable());

    结果:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    Compile-time type: Table`1
    Actual type: Table`1

    Compile-time type: IEnumerable`1
    Actual type: Table`1

    Compile-time type: IQueryable`1
    Actual type: Table`1

    您可以看到,表类本身总是返回的,但是它的表示形式会改变。好的。

    现在一个实现IEnumerable而不是IQueryable的对象:好的。

    1
    2
    3
    4
    var ints = new[] { 1, 2 };
    ReportTypeProperties(ints);
    ReportTypeProperties(ints.AsEnumerable());
    ReportTypeProperties(ints.AsQueryable());

    结果:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    Compile-time type: Int32[]
    Actual type: Int32[]

    Compile-time type: IEnumerable`1
    Actual type: Int32[]

    Compile-time type: IQueryable`1
    Actual type: EnumerableQuery`1

    就在那里。AsQueryable()已将数组转换为EnumerableQuery,它"表示作为IQueryable数据源的IEnumerable集合。"(msdn)。好的。有什么用?

    AsEnumerable经常用于从任何IQueryable实现切换到linq到objects(l2o),这主要是因为前者不支持l2o所具有的功能。有关详细信息,请参见AsEnumerable()对Linq实体的影响是什么?.好的。

    例如,在实体框架查询中,我们只能使用有限数量的方法。因此,例如,如果我们需要在查询中使用我们自己的方法之一,我们通常会编写好的。

    1
    2
    var query = context.Observations.Select(o => o.Id)
                       .AsEnumerable().Select(x => MySuperSmartMethod(x))

    ToList——它把IEnumerable转换成List——也经常用于这个目的。使用AsEnumerableToList的优点是AsEnumerable不执行查询。AsEnumerable保留了延迟的执行,并且不构建通常无用的中间列表。好的。

    另一方面,当需要强制执行LINQ查询时,ToList可以是一种实现这一点的方法。好的。

    AsQueryable可用于使可枚举集合接受LINQ语句中的表达式。有关详细信息,请参见此处:是否确实需要对集合使用asqueryable()?.好的。药物滥用注意事项!

    AsEnumerable的作用就像一种药物。这是一个快速的解决方案,但要付出代价,而且不能解决根本问题。好的。

    在许多堆栈溢出答案中,我看到人们应用AsEnumerable来解决LINQ表达式中不支持的方法的任何问题。但价格并不总是明朗的。例如,如果您这样做:好的。

    1
    2
    3
    context.MyLongWideTable // A table with many records and columns
           .Where(x => x.Type =="type")
           .Select(x => new { x.Name, x.CreateDate })

    …所有内容都被巧妙地转换成一个SQL语句,用于过滤(Where和项目(Select)。也就是说,分别减少了SQL结果集的长度和宽度。好的。

    现在假设用户只想看到CreateDate的日期部分。在实体框架中,您将很快发现…好的。

    1
    .Select(x => new { x.Name, x.CreateDate.Date })

    …不受支持(在写入时)。啊,幸运的是,有一个AsEnumerable修复:好的。

    1
    2
    3
    context.MyLongWideTable.AsEnumerable()
           .Where(x => x.Type =="type")
           .Select(x => new { x.Name, x.CreateDate.Date })

    当然,它可能会运行。但它将整个表拉入内存,然后应用过滤器和投影。好吧,大多数人都很聪明,可以先做Where:好的。

    1
    2
    3
    context.MyLongWideTable
           .Where(x => x.Type =="type").AsEnumerable()
           .Select(x => new { x.Name, x.CreateDate.Date })

    但是,仍然首先获取所有列,并在内存中完成投影。好的。

    真正的解决办法是:好的。

    1
    2
    3
    context.MyLongWideTable
           .Where(x => x.Type =="type")
           .Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })

    (但那只需要多一点知识…)好的。这些方法不能做什么?

    现在是一个重要的警告。当你这样做的时候好的。

    1
    2
    context.Observations.AsEnumerable()
                        .AsQueryable()

    您最终将得到表示为IQueryable的源对象。(因为这两种方法都只转换和不转换)。好的。

    但是当你这样做的时候好的。

    1
    2
    context.Observations.AsEnumerable().Select(x => x)
                        .AsQueryable()

    结果会是什么?好的。

    Select产生WhereSelectEnumerableIterator。这是一个实现IEnumerable而不是IQueryable的内部.NET类。因此,转换到另一种类型已经发生,随后的AsQueryable将无法再返回原始源。好的。

    这意味着使用AsQueryable不是将具有特定特性的查询提供程序神奇地注入可枚举的查询提供程序的方法。假设你这样做好的。

    1
    2
    3
    4
    var query = context.Observations.Select(o => o.Id)
                       .AsEnumerable().Select(x => x.ToString())
                       .AsQueryable()
                       .Where(...)

    Where条件永远不会转换为SQL。后面跟着LINQ语句的AsEnumerable()最终切断了与实体框架查询提供程序的连接。好的。

    我故意展示这个例子,因为我在这里看到过一些问题,比如人们试图通过调用AsQueryable向集合中"注入"Include功能。它编译并运行,但它什么也不做,因为底层对象不再具有Include实现。好的。具体实施

    到目前为止,这只是关于Queryable.AsQueryableEnumerable.AsEnumerable扩展方法。当然,任何人都可以用相同的名称(和函数)编写实例方法或扩展方法。好的。

    事实上,特定AsEnumerable扩展方法的一个常见示例是DataTableExtensions.AsEnumerableDataTable不实现IQueryableIEnumerable,所以常规的扩展方法不适用。好的。好啊。


    托尔斯特()

    • 立即执行查询

    Asvestable()

    • 懒惰(稍后执行查询)
    • 参数:Func
    • 将每个记录加载到应用程序内存中,然后处理/过滤它们。(例如,在WHERE/TAKE/SKIP中,它将从表1中选择*进入内存,然后选择前x个元素)(在本例中,它所做的是:linq to sql+linq to object)

    ASQualable()

    • 懒惰(稍后执行查询)
    • 参数:Expression>
    • 将表达式转换为T-SQL(使用特定的提供程序),远程查询并将结果加载到应用程序内存中。
    • 这就是为什么dbset(在实体框架中)也继承iqueryable以获得有效的查询。
    • 不要加载每个记录,例如,如果take(5),它将在后台生成select top 5*sql。这意味着这种类型对SQL数据库更友好,这就是为什么这种类型通常具有更高的性能,并且在处理数据库时建议使用这种类型。
    • 因此,AsQueryable()通常比AsEnumerable()工作得快得多,因为它首先生成T-SQL,其中包括您的linq中的所有where条件。


    tolist()将成为内存中的所有内容,然后您将处理它。所以,tolist()。其中(应用一些过滤器)在本地执行。asqueryable()将远程执行所有操作,即将其上的筛选器发送到数据库进行应用。在执行之前,Queryable不会做任何事情。但会立即执行。

    另外,看看这个答案为什么使用asqueryable()而不是list()?.

    编辑:另外,在您的情况下,一旦您执行tolist(),那么每个后续操作都是本地的,包括asqueryable()。一旦开始在本地执行,就无法切换到远程。希望这能更清楚一点。