SelectMany Anonymous Type和Skip Iterations

时间:2013-11-23 05:31:04

标签: c# anonymous-types linq

我一直在尝试找一个“干净”的模式来处理带有匿名类型的.SelectMany,而不总是想要返回结果。我最常见的用例如下:

  1. 我们有一份我想要报告的客户列表。
  2. 每个客户的数据都驻留在一个单独的数据库中,因此我会进行并行.SelectMany
  3. 在每个lambda表达式中,我收集客户对最终报告的结果。
  4. 如果要跳过某个特定客户,我需要返回一个空列表。
  5. 我经常鞭打它们以便快速报告,所以我更喜欢匿名类型。
  6. 例如,逻辑可能如下所示:

    //c is a customer
    var context = GetContextForCustomer(c);
    // look up some data, myData using the context connection
    if (someCondition)
      return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
    else
      return null;
    

    这可以作为foreach声明实现:

    var results = new List<WhatType?>();
    foreach (var c in customers) {
      var context = GetContextForCustomer(c);
      if (someCondition)
        results.AddRange(myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 }));
    }
    

    或者可以使用预先过滤了.SelectMany的{​​{1}}来实施:

    .Where

    这两种方法都存在问题。 customers .Where(c => someCondition) .AsParallel() .SelectMany(c => { var context = GetContextForCustomer(c); return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 }); }) .ToList(); 解决方案需要初始化foreach来存储结果,您必须定义类型。带有List的{​​{1}}通常不切实际,因为.SelectMany的逻辑相当复杂,并且取决于某些数据查找。所以我的理想解决方案看起来像这样:

    .Where

    我在someCondition行中放置什么来跳过返回值?没有任何解决方案我可以提出工作或理想:

    1. customers .AsParallel() .SelectMany(c => { var context = GetContextForCustomer(c); if (someCondition) return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 }); else continue? return null? return empty list? }) .ToList(); 无法编译,因为它不是活动的else循环
    2. continue会导致foreach
    3. return null空列表要求我再次初始化匿名类型列表。
    4. 有没有办法完成上述干净,简单,整洁,满足我所有(挑剔)的要求?

5 个答案:

答案 0 :(得分:2)

不知道someConditionmyData的样子......

为什么你不仅仅SelectWhere上下文:

customers
.Select(c => GetContextForCustomer(c))
.Where(ctx => someCondition)
.SelectMany(ctx => 
    myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });

编辑:我刚才意识到你需要进一步携带customercontext,所以你可以这样做:

customers
.Select(c => new { Customer = c, Context = GetContextForCustomer(c) })
.Where(x => someCondition(x.Context))
.SelectMany(x => 
    myData.Select(d => new { CustomerID = x.Customer, X1 = d.x1, X2 = d.x2 });

答案 1 :(得分:1)

您可以尝试以下操作:

customers
  .AsParallel()
  .SelectMany(c => {
     var context = GetContextForCustomer(c);
     if (someCondition)
       return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
     else
       return Enumerable.Empty<int>().Select(x => new { CustomerID = 0, X1 = "defValue", X2 = "defValue" });
  })
  .ToList();

具有相同属性集(相同名称和类型)的所有匿名类型由编译器组合成一个匿名类。这就是为什么SelectEnumerable.Empty上的T将返回相同的{{1}}。

答案 2 :(得分:1)

您可以返回空Enumerable<dynamic>。这是一个例子(虽然没有你的客户和someCondition,因为我不知道它们是什么,而是你的例子的一般形式):

new int[] { 1, 2, 3, 4 }
    .AsParallel()
    .SelectMany(i => {
        if (i % 2 == 0)
            return Enumerable.Repeat(new { i, squared = i * i }, i);
        else
            return Enumerable.Empty<dynamic>();
        })
    .ToList();

因此,对于您的对象和someCondition,它看起来像

customers
    .AsParallel()
    .SelectMany(c => {
        var context = GetContextForCustomer(c);
        if (someCondition)
            return myData.Select(x => new { CustomerID = c, X1 = x.x1, X2 = x.x2 });
        else
            return Enumerable.Empty<dynamic>();
       })
    .ToList();

答案 3 :(得分:1)

您可以创建自己的SelectMany LINQ方法变体,该方法支持null s:

public static class EnumerableExtensions
{
    public static IEnumerable<TResult> NullableSelectMany<TSource, TResult> (
        this IEnumerable<TSource> source,
        Func<TSource, IEnumerable<TResult>> selector)
    {
        if (source == null) 
            throw new ArgumentNullException("source");
        if (selector == null) 
            throw new ArgumentNullException("selector");
        foreach (TSource item in source) {
            IEnumerable<TResult> results = selector(item);
            if (results != null) {
                foreach (TResult result in results)
                    yield return result;
            }
        }
    }
}

现在,您可以在null lambda中返回selector

答案 4 :(得分:0)

接受的答案返回dynamic。最干净的是将过滤逻辑移动到Where,这使得整个事情在linq上下文中看起来更好。既然你明确地在问题中排除了这一点,并且我不是在linq调用中多行写的代表的粉丝,那么我会尝试this,但是可以认为它更为hacky。

var results = new 
{ 
    customerID = default(int), //notice the casing of property names
    x1 = default(U), //whatever types they are
    x2 = default(V) 
}.GetEmptyListOfThisType();

foreach (var customerID in customers) {
  var context = GetContextForCustomer(customerID);
  if (someCondition)
    results.AddRange(myData.Select(x => new { customerID, x.x1, x.x2 }));
}

public static List<T> GetEmptyListOfThisType<T>(this T item)
{
    return new List<T>();
}

注意适当使用与其他变量名称一致的属性名称,因此您不必在Select调用中第二次写入属性名称。