【问题标题】:Linq fill data for nested List<T>嵌套 List<T> 的 Linq 填充数据
【发布时间】:2016-07-08 01:54:50
【问题描述】:

我有这个模型

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; }
}

这个List的数据是CPMCId的5k条记录,里面每个子list有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中填充数据需要很多时间。你们可以为这个解决方案提供更好的实施吗 提前致谢

【问题讨论】:

  • 您没有使用EntityFramework,而只是使用linq?鉴于结构和大量数据,我预计它会很慢,因为您将所有内容都放在内存中(每个子列表的 5k CPMCId 和 50k 记录)
  • 是的,我正在使用简单的 linq 查询。我该怎么做才能让它更快
  • 什么是 tmpl 和 tpmcl,为什么它们不是由 hashmap 支持的 IDictionary(例如 Dictionary)?
  • @moreON 那是 2 个子类分开然后他们不是

标签: c# performance linq optimization


【解决方案1】:

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

通过使用 LINQ Group Join 运算符,它可以减少到更快的 O(K+M+N),该运算符在内部使用非常有效的基于哈希的查找:

var cpl =
    (from cpmcId in CPMCIdList
     join tpm in tpml on cpmcId equals tpm.CPMCId into tpmg
     join tpmc in tpmcl on cpmcId equals tpmc.CPMCId into tpmcg
     select new CPMC
     {
         CPMCId = cpmcId,
         tpm = tpmg.ToList(),
         tpmc = tpmcg.ToList()
     }).ToList();

【讨论】:

  • 这看起来不错,感谢组加入密钥。我接受这个答案
【解决方案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; }
}

显然,您有一个 A's、B's 和 Cs 的列表

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

您正在寻找创建A 的列表

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

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

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

所以我们自己去做。可以制作这种方法的通用版本,但这并不完全简单。我们要做的就是按照CPMCId对A和B进行排序,然后在As的列表中取出所有具有对应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 将是 A 和 @987654335 中最低的 Current @s

也很有可能你现在正处于一个你自己挖的坑里。您很有可能可以更高效、更优雅地在代码中进一步“上游”执行此操作。

最后一点,当B 的列表或C 的列表不包含任何元素时,此 sn-p 不起作用。在这种情况下,一个简单的 GroupBy 就足够了。

【讨论】:

  • 请注意!这个 sn-p 仅在我的脑海中进行了编译、测试和类型检查。
  • 不,这太复杂了,我使用@Ivan Stoev 的答案并通过简单的工具获得更好的性能。谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-06
  • 1970-01-01
  • 2020-06-07
  • 2013-10-05
  • 2013-01-08
相关资源
最近更新 更多