LINQ to Entities / LINQ to SQL: switching from server (queryable) to client (enumerable) in the middle of a query comprehension?

谁都会走 提交于 2019-12-03 13:02:46

Okay, firstly you absolutely should not use the code here. It was written by trained stunt-hamsters who have been trained not to throw up when dealing with this code of this nature.

You should absolutely pick one of the options you know about:

  • Use a "temporary" variable (if you can statically type that variable as IEnumerable<T> then you don't need the call to AsEnumerable - that won't work if you've got an anonymous type as the element type of course)
  • Use brackets for a call to AsEnumerable
  • Use the "fluent" or "dot notation" syntax to make the AsEnumerable call fit in.

However, you can do a bit of magic, using the way that query expressions are translated. You just need to make one of the standard query operators with a representation in query expressions have a different translation. The simplest option here is probably "Where". Just write your own extension method taking an IQueryable<T> and a Func<T, SomeType> where SomeType isn't bool, and you're away. Here's an example, first of the hack itself and then a sample use of it...

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;
    }
}

Of course, if you were using the normal method syntax, this would be no problem:

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);

If you insist on using the query syntax, you won’t get around using some parentheses, but otherwise, you can certainly still do the same:

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;

Reusing the variable name t does not cause any problems; I tested it.

Kris Ivanov

What do you mean by server/client side?

I guess you mean you get some collection from the server and then execute additional filtering that's is not available in the LINQ-to-entity. Just try this:

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();

You want to use the more abstract syntax to gain finer control over server vs local execution? Sorry - that's not do-able.

Think about the problem of scope within the query comprehension.

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??
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!