C# 与.Net Framework中的Groupby不一致

C# 与.Net Framework中的Groupby不一致,c#,entity-framework,linq,group-by,C#,Entity Framework,Linq,Group By,我试图理解GroupBy在操作可枚举对象时如何在不具体化的情况下正常工作 我有一个实体,看起来是这样的: public class ConfigurationValue { public int Id {get; set;} public string KeyField {get; set;} public string Value {get; set;} public Guid OwnerId { get; set; } } var groupQuery =

我试图理解
GroupBy
在操作可枚举对象时如何在不具体化的情况下正常工作

我有一个实体,看起来是这样的:

public class ConfigurationValue
{ 
    public int Id {get; set;}
    public string KeyField {get; set;}
    public string Value {get; set;}
    public Guid OwnerId { get; set; }
}
var groupQuery = context.ConfigurationValues.GroupBy(x => x.Namespace);
Func<ConfigurationValue, bool> orderFunc = y => y.Type == "Int";

//This should call Enumerable because there is no direct conversion to Queryable
var shouldCallInMemory = groupQuery.Select(x => x.FirstOrDefault(y => y.Type == "Int"))
                                                 .ToArray();

var usingExplicitFunc = groupQuery.Select(x => x.FirstOrDefault(orderFunc)).ToArray();
我的问题是这样的:

public class ConfigurationValue
{ 
    public int Id {get; set;}
    public string KeyField {get; set;}
    public string Value {get; set;}
    public Guid OwnerId { get; set; }
}
var groupQuery = context.ConfigurationValues.GroupBy(x => x.Namespace);
Func<ConfigurationValue, bool> orderFunc = y => y.Type == "Int";

//This should call Enumerable because there is no direct conversion to Queryable
var shouldCallInMemory = groupQuery.Select(x => x.FirstOrDefault(y => y.Type == "Int"))
                                                 .ToArray();

var usingExplicitFunc = groupQuery.Select(x => x.FirstOrDefault(orderFunc)).ToArray();
使用显式Func时,我希望也会这样做,但它会生成一个错误:

查询运算符“FirstOrDefault”使用了不支持的重载

这是非常混乱和不一致的,因为它会误导正确的行为,并且要求您在使用显式lambda时使用变通方法

编辑

IGrouping
只实现
IEnumerable
,但是当传递lambda时,它仍然被正确地翻译,这是如何工作的编译器有处理
GroupBy
的特殊情况吗

编辑

我已在上与Microsoft讨论了一个问题

似乎正在发生的事情是在选择的时候,您仍然在创建一个表达式
。选择(x=>x.FirstOrDefault(y=>y.Type==“Int”)。ToArray()
Lambda
y=>y.Type==“Int”
仍将被编译为func,但它没有实际使用,因为select
x=>x.FirstOrDefault(y=>y.Type==“Int”)
实际上正在生成一个表达式

我推测在编译期间生成的表达式可能不使用反射,并且有一些特殊的行为,因为它们实际上是Lambda的

我希望C#guru能够澄清编译器是如何正确处理这种情况的?

让我们只关注
shouldCallInMemory
案例。因此,您的代码是:

var groupQuery = context.ConfigurationValues.GroupBy(x => x.Namespace);
//This should call Enumerable because there is no direct conversion to Queryable
var shouldCallInMemory = groupQuery.Select(x => x.FirstOrDefault(y => y.Type == "Int"))
                                                 .ToArray();
现在,您给出了基于
i分组的推理,但这只是内部lambda表达式使用的类型。外部lambda表达式仍然转换为表达式树,包括内部lambda表达式本身

最简单的方法是用一个独立的示例来说明这一点,该示例实际上不查询任何内容:

using System;
using System.Linq;
using System.Linq.Expressions;
public class Test
{
    static void Main()
    {
        // Value doesn't matter... we're interested in the type inference
        IQueryable<IGrouping<string, string>> grouping = null;

        FakeSelect(grouping, x => x.FirstOrDefault(y => y == "Int"));
    }

    static void FakeSelect<TSource, TResult>(
        IQueryable<TSource> source,
        Expression<Func<TSource, TResult>> selector)
    {
        Console.WriteLine(selector);
    }
}

因此,表达式树包含对
Enumerable.FirstOrDefault
的方法调用,该调用带有一个表示谓词的参数。第二个参数的类型为
Func
,但我们从未实际计算表达式树。关键是表达式树保留了参数是lambda表达式的事实,并且lambda表达式中的代码。。。这意味着EF(或任何东西)能够将其转换为SQL。

是的,它似乎在转换func,我将reclarify@AndrewWilliamson只有在使用
AsQueryable
进行强制转换时,我才能使用显式表达式,但如果使用显式
Func
它会爆炸。这一点的实施令人困惑。我已经弄明白了如何过滤我只是不知道为什么会发生这种行为我认为
.Select(x=>x.FirstOrDefault(y=>y.Type==“Int”)
部分是在查看自身的表达式,而不是要求
I分组
生成sql。因此,
.Select
完成了所有的翻译工作,而不是分组。当你传递一个
Func
时,它就不能再翻译它了,这很有意义,但是,这很奇怪,因为在
i分组
上使用表达式没有重载。如果你想使用一个显式表达式,你必须使用
.AsQueryable
强制转换,但是如果你想使用一个Func,你可以传递它,但它会在以后中断编译,
I分组
应该实现IQueryable,这应该是链接时的默认值。实际上,我还是有点困惑,因为如果表达式反映到该部分,它仍然必须编译为表达式,否则它将反映到函数中,也许Lambdas通常有特殊的处理?这种解释是有道理的,但是第二种情况会产生错误,建议采用什么方法来传递显式的
Func
?如果你想使用显式表达式,你必须在
选择
中调用
AsQueryable
,但是如果你想使用显式Func,这会产生一个编译器错误,这是微软设计中的一个bug/缺陷吗,GroupBy不应该实现IQueryable吗?@johnny5:你不能传入显式
Func
,因为表达式树并没有在其中表示什么的信息。关键是表达式树需要具有将其转换为SQL所需的所有信息。如果您想将其分为几个阶段,您可能需要使用
表达式
,并在此基础上创建
LambdaExpression
,然后手动构建外部部分。我不确定我是否会将其称为设计中的一个bug-很难说是真的。如果您要查看iGroup的重载,它表明您正在对枚举进行操作,而将任何其他linq方法从IQueryable链接到IQueryable,那么您仍然会对IQueryable进行操作。通过使用可枚举重载,我不应该期望在过滤之前实现物化吗?表达式正确生成的唯一原因不是因为它们同时使用DuckTyping来生成Select表达式吗?@johnny5:但是
GroupBy
不返回
IGrouping
,而是返回
IQueryable
。您永远不会执行
可枚举
方法,您只是在传递给
Select
的表达式树中表示对该方法的调用。我不确定使用duck类型生成表达式是什么意思,但这确实是因为lambda表达式中转换为表达式树的所有内容都包含在表达式树中,包括任何进一步转换为委托的lambda表达式的详细信息。抱歉,我用错了词,
i分组
在某种意义上仍然是SQL查询的一部分。我们可以看到这一点,因为我们的内部Lambda是在SQL端执行的。如果是这种情况,基本上,
i分组
继续对查询进行操作,并且应该实现
IQ
x => x.FirstOrDefault(y => (y == "Int"))