2011-01-28 52 views
10

在很多情况下,我想在服务器端进行一些过滤(有时是投影),然后切换到客户端,以获取LINQ提供程序本身不支持的操作。LINQ to Entities/LINQ to SQL:在查询理解中从服务器(可查询)切换到客户端(可枚举)?

简易方法(这基本上是我现在做的)是只把它分解成多个查询,类似于:

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; 

回答

19

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

你应该绝对挑你了解的选项之一:

  • 使用“临时”变量(如果你能静态类型变量作为IEnumerable<T>那么你不需要调用AsEnumerable - 不会,如果你有一个匿名类型作为当然的元素类型的呼叫工作)
  • 使用括号AsEnumerable
  • 使用“流利”或“点号”语法,使AsEnumerable电话适合。

但是,您可以一点魔法,使用查询表达式转换的方式。您只需要使查询表达式中具有表示形式的标准查询操作符具有不同的翻译。这里最简单的选择可能是“Where”。只要写你自己的扩展方法服用IQueryable<T>Func<T, SomeType>其中SomeTypebool,和你离开。这里有一个例子,首先是黑客本身,然后是它的一个示例使用...

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; 
    } 
} 
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(); 
+0

使用`ToList`当然有效,但会带来不必要的低效率。第二个“ToList”可能是多余的。 – Timwi 2011-01-28 03:05:01

+0

事实上,对于方法链接版本,我可以调用.AsEnumerable()来执行切换(这是我在问题的第一个版本中所做的)。 ToList也将有类似的效果,但效率较低,因为它会要求创建/填充列表,只有把它用。凡扔掉权之后(这就是为什么我会用AsEnumerable代替) – 2011-01-28 03:10:42

+0

同意,可以更换为ToList更高的效率 – 2011-01-28 03:12:15

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做不会造成任何问题;我测试了它。

+0

好一点,我应该作出更清晰 - 当然,我可以“内联”与括号的fromServer变量(如你在这里所做的),但是这就是我希望避免(如果有可能的一部分,它可能不是)。在'理论'情况下,'//魔术发生在这里切换到客户端'上面/下面的部分理想情况下应该保持原样(尽管如果范围变量需要在切换后更改名称, 没关系)。 – 2011-01-28 03:06:25

-1

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

想想查询理解中的范围问题。

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??