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

a 夏天 提交于 2020-01-01 04:36:06

问题


In many cases, I want to do some filtering (and sometimes projection) on the server side and then switch to client-side for operations that the LINQ provider doesn't natively support.

The naive approach (which is basically what I do now) is to just break it up into multiple queries, similar to:

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;

However, there are many times where this is more code/trouble than it's really worth. I'd really like to do a 'switch to client side' in the middle. I've tried various methods of using a query continuation, but after doing a 'select t into foo' at the end of the first query, foo is still an individual item, not the collection, so I can't AsEnumerable() it.

My goal is to be able write something more like:

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;

回答1:


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



回答2:


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.




回答3:


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



回答4:


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


来源:https://stackoverflow.com/questions/4824367/linq-to-entities-linq-to-sql-switching-from-server-queryable-to-client-enu

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