Linq填充嵌套List <t>的数据

时间:2016-07-08 01:54:51

标签: c# performance linq optimization

我有这个模型

public class CPMC
{
    public int CPMCId { get; set; }
    public List<TPM> tpm = new List<TPM>();
    public List<TPMC> tpmc = new List<TPMC>();
}
public class TPMC
{
    public int Id { get; set; }
    public Int64 Amount { get; set; }
    public int PId { get; set; }
    public Int64 PAmount { get; set; }
    public int CPMCId { get; set; }
}
public class TPM
{
    public int Type { get; set; }
    public int Id { get; set; }
    public Int64 Amount { get; set; }
    public int VAT { get; set; }
    public DateTime FromDate { get; set; }
    public DateTime ToDate { get; set; }
    public int CPMCId { get; set; }
}

此列表的数据是CPMCId的5k记录,内部每个子列表的50k记录,条件是

List<int> CPMCIdList = aPP.Select(x => Convert.ToInt32(x.CPMCId)).Distinct().ToList(); 
                List<CPMC> cpl = (from ap in CPMCIdList 
                                  select new CPMC
                                  {
                                      CPMCId = ap,
                                      tpm = tpml.Where(x=>x.CPMCId == ap).ToList(),
                                      tpmc = tpmcl.Where(x=>x.CPMCId == ap).ToList()
                                  }).ToList(); 

但是在List中填充数据需要花费很多时间。你们能为这个解决方案提供更好的工具吗? 提前致谢

2 个答案:

答案 0 :(得分:2)

首先,让我们将您的问题减少到最小的情况:

您有以下类型:

public class A
{
    public int Id { get; set; }
    public List<B> bs = new List<B>();
    public List<C> cs = new List<C>();
}

public class B
{
    public int CPMCId { get; set; }
}

public class C
{
    public int CPMCId { get; set; }
}

显然,您有一个ABC的列表

List<A> as;
List<B> bs;
List<C> cs;

您要创建A

列表

现在首先让我们来看看为什么你的解决方案很慢。

您正在做的是首先创建您想要的所有ID的列表,然后,对于每个ID,搜索所有匹配的记录。这意味着您正在为每个ID完全扫描子列表。这显然不是最佳的。

您正在寻找的操作在Outer Join中称为SQL。不幸的是,Linq没有开箱即用的等效操作。

所以我们自己就是这样。可以制作这种方法的通用版本,但这并不完全是直截了当的。我们要做的是按照CPMCId对A和B进行排序,然后在A s列表中获取具有相应ID的所有匹配记录:

IEnumerable<A> make_as(IEnumerator<B> ordered_bs, IEnumerator<C> ordered_cs, IEnumerator<int> ordered_ids) {
  //make sure the current element of bs and cs is at the first element, not before it.
  if(!ordered_bs.MoveNext() || !ordered_cs.MoveNext())
    throw new ArgumentException("empty bs or cs");

  while(ordered_ids.MoveNext()) {
    nextid = ordered_ids.Current;
    var a = new A(){
      id = nextId;
    };
    //process the B's
    while(ordered_bs.Current.CPMCId < nextid) //not in the list, skip it {
      ordered_bs.MoveNext();
    }
    while(ordered_bs.Current.CPMCId == nextid) //matching, add to the list {
      a.bs.add(ordered_cs.Current);
      if(!orderd_bs.MoveNext()) break; //move bs forward. If b's is empty, we're done here
    }
    //do the same for the C's
    while(ordered_cs.Current.CPMCId < nextid) {
      ordered_cs.MoveNext();
    }
    while(ordered_cs.Current.CPMCId == nextid) {
      a.cs.add(ordered_cs.Current);
      if(!ordered_cs.MoveNext()) break;
    }
    yield return a;
  }
}

var result = make_as(
  bs.orderBy(b => b.PCMCId).GetEnumerator(),
  cs.orderBy(c => c.PCMCId).GetEnumerator(),
  as.Select(a => a.id).OrderBy(id => id).Distinct().GetEnumerator()
).ToList()

一些注意事项:

我的印象是,这是已经完成一些处理的解决方案的一部分。如果您知道自己需要所有ID,则根本不需要A的原始列表,而nextId将是Current的最低A }和B s

现在你很有可能在自己挖洞中遇到了一些漏洞。很有可能你可以更有效地 - 更优雅地 - 在你的代码中进一步“上游”。

最后一点,当B列表或C列表中没有元素时,此代码段不起作用。在这种情况下,一个简单的GroupBy就足够了。

答案 1 :(得分:2)

由于两个内部循环线性搜索(LINQ Where运算符),您当前的实现具有O(K*N*M)时间复杂度,其中K=CPMCIdList.CountN=tpml.Count,{{1} }。

使用LINQ Group Join运算符可以将其简化为更快M=tpmcl.Count,这些运算符在内部使用非常有效的基于散列的查找:

O(K+M+N)