是否可以在 Linq 中按方法在组中使用方法?

Is it possible to use method in the group by method in Linq?

我正在尝试按我的自定义方法进行分组。比如group id是什么东西,那么我想从GetClientGroup的方法里return1或者0,那我就想按值分组。但是我收到这样的错误。

错误

could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

await (from o in _cdsContext.Order
   where o.ClienteleId == clienteleId && o.DeliveryDate >= new DateTime(2020, 06, 29).Date
   && o.DeliveryDate != null
   group o by new
   {
       o.ClienteleId,
       o.DeliveryDate,
       ClientGroup= o.OrderTypeId == 22 ? 259 : GetClientGroup(clienteleId, (int)o.GroupId), 
   }
 into g
 select new { ClienteleId = g.Key.ClienteleId}).ToListAsync()
                       


我想你在 运行 时得到这个错误,而不是在编译时。我说得对吗?

IEnumerable 和 IQueryable

您应该知道 IEnumerable<...>IQueryable<...> 之间的区别。

实现IEnumerable<...>IQueryable<...>的对象表示可能给你一个可枚举的序列。拿到序列就可以求第一个元素,拿到这个就可以求下一个元素,只要有元素就可以了。

这种对元素的迭代通常是使用 foreach (var element in sequence) {...} 完成的。这转化为以下内容:

IEnumerable<MyType> sequence = ...                          // the potential to get iterator
IEnumerator<MyType> enumerator = sequence.GetEnumerator();  // get the iterator

while (enumerator.MoveNext())                               // iterate
{                                                           // as long as there are items
     MyType item = enumerator.Current;                      // fetch the item
     ProcessItem(item);                                     // and process it.
}

不 return IEnumerable<...>IQueryable<...> 的 LINQ 方法,如 ToListToDictionaryCountAnyFirstOrDefault等内部都使用foreachGetEnumerator

实现 IEnumerable<...> 的对象将由您的本地进程处理。该对象包含所有能够迭代的内容,包括对本地方法的调用。

另一方面,实现 IQueryable<...> 的对象,如您的 _cdsContext.Order 意味着要由另一个进程处理,通常是数据库管理系统。

此对象包含一个 Expression 和一个 ProviderExpression 是您要查询的数据的通用形式。 Provider 知道谁必须执行查询,以及使用什么语言(通常 SQL)

连接 LINQ 语句不会执行查询,它们只会更改表达式。当(深入内部)调用 GetEnumerator() 时,Expression 被发送到 Provider,后者会将其翻译成 SQL 并在 DBMS 上执行查询。获取的数据表示为您进程的迭代器,该迭代器将重复调用 MoveNext()Current.

回到你的问题

您的 GroupBy 包含对本地方法的调用。 GroupBy 不会执行查询,它只会更改表达式。最后你做了一个ToList。 Tolist 将执行 GetEnumerator()。表达式被发送到提供商,提供商将尝试将其翻译成 SQL.

唉,您的提供商不知道您的本地方法 GetClientGroup,因此无法将其转换为 SQL。事实上,除了你所有的本地方法之外,还有几个 LINQ 方法不能被翻译成 SQL。参见 Supported and Unsupported LINQ methods (LINQ to entities)

您的编译器不知道提供者可以翻译哪些方法,因此编译器不会报错。仅在 运行 次,当您执行 ToList 时,会检测到问题。

如何解决问题

问题出在 Queryable.GroupBy

的参数 KeySelect 中
Expression<Func<TSource,TKey>> keySelector

唉,你忘了写 GetClientGroup 做什么。似乎它需要订单的 ClienteleId 和 GroupId,并且 returns 是一个类似于 ClientGroup 的整数。

最简单的方法是用该方法中的代码替换对 GetClientGroup 的调用。 不要调用任何其他方法

DateTime deliveryLimitDate = new DateTime(2020, 06, 29).Date;
var result = dbContext.Orders
    .Where (order => order.ClienteleId == clienteleId
                  && order.DeliveryDate != null
                  && order.DeliveryDate >= deliveryLimitDate)
    .GroupBy(order => new                                 // Parameter KeySelector
    {
         ClienteleId = order.ClienteleId,
         DeliveryDate = order.DeliveryDate,
         ClientGroup= order.OrderTypeId == 22 ? 259 :
             
                  // formula in GetClientGroup(...)
                  // for example
                  (int)order.GroupId << 16 + order.ClienteleId

         // parameter ResultSelector
         group => new { ClienteleId = group.Key.ClienteleId});

我使用了带有参数 ResultSelector 的 GroupBy 重载,而不是单独的 Select。您的结果是一系列只有一个 属性 ClienteleId 的对象。考虑 return 只有一个 ClienteleId 序列:

         // parameter ResultSelector
         group => group.Key.ClienteleId});

唉,我不知道你的GetClientGroup,不能给你参数KeySelector