2013-11-23 47 views
4

我一直在尝试很长时间来找到一个“干净”模式来处理.SelectMany与匿名类型,当你不总是想要返回结果。我最常见的使用案例如下所示:SelectMany匿名类型和跳过迭代

  1. 我们有一个我想要进行报告的客户列表。
  2. 每位客户的数据都驻留在一个单独的数据库中,因此我做了一个并行处理.SelectMany
  3. 在每个lambda表达式中,我收集客户对最终报告的结果。
  4. 如果一个特定的客户应该被跳过,我需要返回一个空列表。
  5. 为了快速报告我经常鞭打这些,所以我更喜欢匿名类型。

例如,逻辑可能看起来是这样的:

//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是实施用.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来存储结果,并且您必须定义类型。 .SelectMany.Where通常不切实际,因为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 
     continue? return null? return empty list? 
    }) 
    .ToList(); 

我放在else线什么跳过一个返回值?该解决方案中没有我可以拿出工作或理想:

  1. continue不能编译,因为它不是一个积极的foreach循环
  2. return null导致NRE
  3. return空单需要我初始化再次列出匿名类型。

有没有办法完成上述的干净,简单,整洁,并满足我所有(挑剔)的要求?

回答

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

这将导致大量的动态调用,并反过来反射。在性能方面,这不是最好的选择。 – Athari

+1

好点。但他也表示他只是需要这个来产生一些快速报告。 –

+0

我最喜欢这个解决方案,因为它很短,不需要任何自定义的扩展方法。我尝试了'Enumerable.Empty()',但它不能确定类型,没有''。谢谢!! – mellamokb

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

+0

这个解决方案与'foreach'和'return'空列表基本相同 - 您必须动态构建一个匿名类型的列表。例如,我知道诸如[this one]这样的解决方案(http://stackoverflow.com/questions/612689)。它有效,但现在我有两个属性列表,如果我决定在即时报告中添加/删除属性,我必须更新这两个属性列表。 – mellamokb

1

不知道什么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 }); 
+0

我意识到我的例子并不正义。实际上,'someCondition'和'myData'背后的逻辑是20-30行代码,并且正在迅速发展,所以我最喜欢'.SelectMany'。 – mellamokb

1

您可以创建自己的SelectMany LINQ方法的variarion其中支持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; 
      } 
     } 
    } 
} 

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

+0

这是一个不错的方法。我考虑的另一件事是'.Select(...)。其中(x => x!= null).SelectMany(x => x)'。不是很漂亮,但不需要我在任何地方指定匿名类型。 – mellamokb

+1

@mellamokb ReSharper建议我实际上。 :)我也有许多'IEnumerable'扩展方法,所以我会写'.Select(...)。WhereNotNull()。Flatten()'我自己,这对我来说看起来非常棒。 – Athari

0

接受的答案返回dynamic。最简洁的方法是将过滤逻辑转换为Where,这使得linq上下文看起来更好。既然你明确地解决了这个问题,而且我不是一个在linq调用中写成多行的代表的粉丝,我会尝试this,但是可以争辩说它更冒险。

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呼叫的第二时间。