LINQ to Entities / LINQ to SQL:在查询理解过程中从服务器(可查询)切换到客户端(可枚举)?

时间:2011-01-28 02:48:03

标签: c# linq linq-to-sql linq-to-entities linq-to-objects

在许多情况下,我想在服务器端进行一些过滤(有时是投影),然后切换到客户端进行LINQ提供程序本身不支持的操作。

天真的方法(基本上就是我现在所做的)就是把它分解成多个查询,类似于:

var fromServer = from t in context.Table
                 where t.Col1 = 123
                 where t.Col2 = "blah"
                 select t;

var clientSide = from t in fromServer.AsEnumerable()
                 where t.Col3.Split('/').Last() == "whatever"
                 select t.Col4;

然而,很多时候这是更多的代码/麻烦,而不是真正的价值。我真的很想在中间做一个“切换到客户端”。我已经尝试了各种使用查询延续的方法,但是在第一个查询结束时执行'select t into foo'后,foo仍然是一个单独的项目,而不是集合,所以我不能AsEnumerable()它

我的目标是能够写出更像:

var results = from t in context.Table
              where t.Col1 = 123
              where t.Col2 = "blah"
              // Magic happens here to switch to the client side
              where t.Col3.Split('/').Last() == "whatever"
              select t.Col4;

4 个答案:

答案 0 :(得分:20)

好的,首先你绝对不应该使用这里的代码。它是由受过训练的特技仓鼠编写的,他们在处理这种性质的代码时已经接受过训练而不会呕吐。

绝对选择你知道的一个选项:

  • 使用“临时”变量(如果您可以将该变量静态键入IEnumerable<T>,那么您不需要调用AsEnumerable - 如果您有匿名的话,这将无效键入作为课程的元素类型)
  • 使用括号来调用AsEnumerable
  • 使用“流利”或“点符号”语法使AsEnumerable调用适合。

但是,可以使用查询表达式的翻译方式做一些魔术。您只需要使一个标准查询运算符与查询表达式中的表示具有不同的转换。这里最简单的选择可能就是“Where”。只需编写您自己的扩展方法,即IQueryable<T>Func<T, SomeType> SomeType不是bool,您就会离开。这是一个例子,首先是hack本身,然后是它的示例使用......

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

public static class QueryHacks
{
    public static readonly HackToken TransferToClient = HackToken.Instance;

    public static IEnumerable<T> Where<T>(
        this IQueryable<T> source,
        Func<T, HackToken> ignored)
    {
        // Just like AsEnumerable... we're just changing the compile-time
        // type, effectively.
        return source;
    }

    // This class only really exists to make sure we don't *accidentally* use
    // the hack above.
    public class HackToken
    {
        internal static readonly HackToken Instance = new HackToken();
        private HackToken() {}
    }
}

public class Test
{
    static void Main()
    {
        // Pretend this is really a db context or whatever
        IQueryable<string> source = new string[0].AsQueryable();

        var query = from x in source
                    where x.StartsWith("Foo") // Queryable.Where
                    where QueryHacks.TransferToClient
                    where x.GetHashCode() == 5 // Enumerable.Where
                    select x.Length;
    }
}

答案 1 :(得分:8)

当然,如果您使用的是常规方法语法,那就没问题了:

var results = context.Table
              .Where(t => t.Col1 == 123)
              .Where(t => t.Col2 == "blah")
              .AsEnumerable()
              .Where(t => t.Col3.Split('/').Last() == "whatever")
              .Select(t => t.Col4);

如果你坚持使用查询语法,你就不会使用一些括号,但除此之外,你当然可以这样做:

var results = from t in (
                  from t in context.Table
                  where t.Col1 == 123
                  where t.Col2 == "blah"
                  select t
              ).AsEnumerable()
              where t.Col3.Split('/').Last() == "whatever"
              select t.Col4;

重用变量名t不会导致任何问题;我测试了它。

答案 2 :(得分:0)

服务器/客户端是什么意思?

我猜你的意思是你从服务器获得一些集合,然后执行LINQ-to-entity中没有的额外过滤。试试这个:

var items =
    context.Table.Where(t => t.Col1 = 123 && t.Col2 = "blah").ToList()
    .Where(t => t.Col3.Split('/').Last() == "whatever")
    .Select(t => t.Col4).ToList();

答案 3 :(得分:-1)

您想使用更抽象的语法来更好地控制服务器与本地执行吗?对不起 - 这不可行。

考虑查询理解中的范围问题。

from c in context.Customers
from o in c.Orders
from d in o.Details
asLocal
where //c, o and d are all in scope, so they all had to be hydrated locally??