繁体   English   中英

搜索可枚举的最有效方法

[英]Most efficient way to search enumerable

我正在编写一个小程序,它接收一个.csv文件作为输入,大约有45k行。 我试图将此文件的内容与数据库中的表的内容进行比较(SQL Server通过动态CRM使用Xrm.Sdk,如果它有所不同)。

在我当前的程序中(大约需要25分钟进行比较 - 文件和数据库在这里完全相同,只有45k行没有差异),我在数据库中的所有现有记录都在DataCollection<Entity>中继承了Collection<T>IEnumerable<T>

在我下面的代码中,我使用Where方法进行过滤,然后根据匹配计数进行逻辑。 Where似乎是这里的瓶颈。 有没有比这更有效的方法? 我绝不是LINQ专家。

foreach (var record in inputDataLines)
{
    var fields = record.Split(',');

    var fund = fields[0];
    var bps = Convert.ToDecimal(fields[1]);
    var withdrawalPct = Convert.ToDecimal(fields[2]);
    var percentile = Convert.ToInt32(fields[3]);
    var age = Convert.ToInt32(fields[4]);
    var bombOutTerm = Convert.ToDecimal(fields[5]);

    var matchingRows = existingRecords.Entities.Where(r => r["field_1"].ToString() == fund
                                      && Convert.ToDecimal(r["field_2"]) == bps
                                      && Convert.ToDecimal(r["field_3"]) == withdrawalPct
                                      && Convert.ToDecimal(r["field_4"]) == percentile
                                      && Convert.ToDecimal(r["field_5"]) == age);

    entitiesFound.AddRange(matchingRows);

    if (matchingRows.Count() == 0)
    {
        rowsToAdd.Add(record);
    }
    else if (matchingRows.Count() == 1)
    {
        if (Convert.ToDecimal(matchingRows.First()["field_6"]) != bombOutTerm)
        {
            rowsToUpdate.Add(record);
            entitiesToUpdate.Add(matchingRows.First());
        }
    }
    else
    {
        entitiesToDelete.AddRange(matchingRows);
        rowsToAdd.Add(record);
    }
}

编辑:在执行此代码之前,我可以确认所有existingRecords都在内存中。 上述循环中没有IO或DB访问权限。

Himbrombeere是对的,您应首先执行查询并将结果放入集合中,然后再使用AnyCountAddRange或任何方法再次执行查询。 在您的代码中,查询可能在每次循环迭代中执行5次。

请注意文档中的延迟执行一词。 如果以这种方式实现方法,则意味着此方法可用于构造LINQ查询(因此您可以使用其他方法链接它,最后您有查询)。 但是,只有不使用延迟执行的方法(如CountAnyToList (或简单的foreach ))才会实际执行它。 如果您不希望每次都执行整个查询并且您必须多次访问此查询,则最好将结果存储在集合中(带有ToList .fe)。

但是,您可以使用一种效率更高的不同方法, Lookup<TKey, TValue>类似于字典,可以与匿名类型一起使用作为键:

var lookup = existingRecords.Entities.ToLookup(r => new 
{
    fund = r["field_1"].ToString(),
    bps = Convert.ToDecimal(r["field_2"]),
    withdrawalPct =  Convert.ToDecimal(r["field_3"]),
    percentile = Convert.ToDecimal(r["field_4"]),
    age = Convert.ToDecimal(r["field_5"])
});

现在,您可以非常有效地在循环中访问此查找。

foreach (var record in inputDataLines)
{
    var fields = record.Split(',');
    var fund = fields[0];
    var bps = Convert.ToDecimal(fields[1]);
    var withdrawalPct = Convert.ToDecimal(fields[2]);
    var percentile = Convert.ToInt32(fields[3]);
    var age = Convert.ToInt32(fields[4]);
    var bombOutTerm = Convert.ToDecimal(fields[5]);

    var matchingRows = lookup[new {fund, bps, withdrawalPct, percentile, age}].ToList();

    entitiesFound.AddRange(matchingRows);

    if (matchingRows.Count() == 0)
    {
        rowsToAdd.Add(record);
    }
    else if (matchingRows.Count() == 1)
    {
        if (Convert.ToDecimal(matchingRows.First()["field_6"]) != bombOutTerm)
        {
            rowsToUpdate.Add(record);
            entitiesToUpdate.Add(matchingRows.First());
        }
    }
    else
    {
        entitiesToDelete.AddRange(matchingRows);
        rowsToAdd.Add(record);
    }
}

请注意,即使密钥不存在(返回空列表),这也会起作用。

Convert.ToDecimal(r["field_5"]) == age);之后添加一个ToList Convert.ToDecimal(r["field_5"]) == age); -line强制立即执行查询。

var matchingRows = existingRecords.Entities.Where(r => r["field_1"].ToString() == fund
                                  && Convert.ToDecimal(r["field_2"]) == bps
                                  && Convert.ToDecimal(r["field_3"]) == withdrawalPct
                                  && Convert.ToDecimal(r["field_4"]) == percentile
                                  && Convert.ToDecimal(r["field_5"]) == age)
                    .ToList();

Where没有实际执行您的查询,它只是准备它。 实际执行后来以延迟的方式发生。 在你调用Count时发生的情况,它本身将迭代整个项目集合。 但是如果第一个条件失败,则在调用Count时检查第二个条件导致完整集合的第二次迭代。 在这种情况下,你打电话时,实际执行该查询thrird时间matchingRows.First()

当强制立即执行时,您只执行一次查询,因此也只迭代整个集合一次,这将减少您的总时间。

另一个选项,基本上与其他答案基本相同,是首先准备你的数据,这样你就不会重复调用r["field_2"] (查找起来相对较慢)。

这是(1)清理您的数据,(2)查询/加入您的数据,(3)处理您的数据方法。

做这个:

(1)

var inputs =
    inputDataLines
        .Select(record =>
        {
            var fields = record.Split(',');
            return new
            {
                fund = fields[0],
                bps = Convert.ToDecimal(fields[1]),
                withdrawalPct = Convert.ToDecimal(fields[2]),
                percentile = Convert.ToInt32(fields[3]),
                age = Convert.ToInt32(fields[4]),
                bombOutTerm = Convert.ToDecimal(fields[5]),
                record
            };
        })
        .ToArray();

var entities =
    existingRecords
        .Entities
        .Select(entity => new
        {
            fund = entity["field_1"].ToString(),
            bps = Convert.ToDecimal(entity["field_2"]),
            withdrawalPct = Convert.ToDecimal(entity["field_3"]),
            percentile = Convert.ToInt32(entity["field_4"]),
            age = Convert.ToInt32(entity["field_5"]),
            bombOutTerm = Convert.ToDecimal(entity["field_6"]),
            entity
        })
        .ToArray()
        .GroupBy(x => new
        {
            x.fund,
            x.bps,
            x.withdrawalPct,
            x.percentile,
            x.age
        }, x => new
        {
            x.bombOutTerm,
            x.entity,
        });

(2)

var query =
    from i in inputs
    join e in entities on new { i.fund, i.bps, i.withdrawalPct, i.percentile, i.age } equals e.Key
    select new { input = i, matchingRows = e };

(3)

foreach (var x in query)
{
    entitiesFound.AddRange(x.matchingRows.Select(y => y.entity));

    if (x.matchingRows.Count() == 0)
    {
        rowsToAdd.Add(x.input.record);
    }
    else if (x.matchingRows.Count() == 1)
    {
        if (x.matchingRows.First().bombOutTerm != x.input.bombOutTerm)
        {
            rowsToUpdate.Add(x.input.record);
            entitiesToUpdate.Add(x.matchingRows.First().entity);
        }
    }
    else
    {
        entitiesToDelete.AddRange(x.matchingRows.Select(y => y.entity));
        rowsToAdd.Add(x.input.record);
    }
}

我怀疑这将是最快的方法之一。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM