C# 列表<;T>;FirstOrDefault()性能不好-在这种情况下是否可以使用字典?

C# 列表<;T>;FirstOrDefault()性能不好-在这种情况下是否可以使用字典?,c#,linq,performance,linq-to-sql,list,C#,Linq,Performance,Linq To Sql,List,我有一组在特定时间段内有效的“代码”Z 因为我在一个大循环(百万+)中需要它们很多次,每次我必须查找相应的代码时,我都将它们缓存在一个列表中。找到正确的代码后,我(使用SqlBulkCopy)插入了一百万行 我使用以下代码查找id(l_z是一个列表) var z_fk=(从l_z中的z开始) 其中z.CODE==查找代码&& z、 VALIDFROM=lookupDate 选择z.id).SingleOrDefault(); 在其他情况下,我使用了性能优异的字典,但在这些情况下,我只需要根据代

我有一组在特定时间段内有效的“代码”Z

因为我在一个大循环(百万+)中需要它们很多次,每次我必须查找相应的代码时,我都将它们缓存在一个列表中。找到正确的代码后,我(使用SqlBulkCopy)插入了一百万行

我使用以下代码查找id(
l_z
是一个
列表

var z_fk=(从l_z中的z开始)
其中z.CODE==查找代码&&
z、 VALIDFROM=lookupDate
选择z.id).SingleOrDefault();
在其他情况下,我使用了性能优异的字典,但在这些情况下,我只需要根据代码查找id

但是现在,随着对字段组合的搜索,我被卡住了


有什么想法吗?提前感谢。

创建一个字典,该字典存储每个查找代码的项目列表-
字典(假设查找代码是字符串,对象是类型代码)

然后,当需要基于
lookupDate
进行查询时,可以直接从
dict[lookupCode]
运行查询:

var z_fk = (from z in dict[lookupCode]
            where z.VALIDFROM <= lookupDate &&
                  z.VALIDUNTIL >= lookupDate 
            select z.id).SingleOrDefault();
var z_fk=(来自dict[lookupCode]中的z)
其中z.VALIDFROM=lookupDate
选择z.id).SingleOrDefault();

然后,只要确保每当您有一个新的代码对象时,它就会被添加到dict中的列表集合中,该集合对应于lookupCode(如果不存在,则创建它).

对我来说,这听起来像是一种情况,在这种情况下,所有这些都可以通过一条语句在数据库中发生。然后,您可以使用索引来保持查询速度,并避免将数据通过连接推送到数据库或从数据库中推出来。

一个简单的改进是使用

//in initialization somewhere
ILookup<string, T> l_z_lookup = l_z.ToLookup(z=>z.CODE);

//your repeated code:
var z_fk = (from z in lookup[lookupCode]
            where z.VALIDFROM <= lookupDate && z.VALIDUNTIL >= lookupDate 
            select z.id).SingleOrDefault();
//在初始化过程中
ILookup l_z_lookup=l_z.ToLookup(z=>z.CODE);
//您的重复代码:
var z_fk=(从查找[lookupCode]中的z开始)
其中z.VALIDFROM=lookupDate
选择z.id).SingleOrDefault();
您可以进一步使用更复杂、更智能的数据结构以排序方式存储日期,并使用二进制搜索来查找id,但这可能就足够了。此外,您还谈到了
SqlBulkCopy
——如果您正在处理一个数据库,也许您可以在数据库上执行查询,然后简单地创建适当的索引,包括code、VALIDUNTIL和VALIDFROM列


我通常更喜欢使用
查找
而不是包含
列表的
字典
,因为它的构造非常简单,并且有一个更干净的API(例如,当一个键不存在时)。

我们没有足够的信息来给出非常规范的建议-但是有一些一般的事情你应该考虑

时间值是什么类型的?您是在比较日期时间还是一些基本值(如时间)。考虑数据类型如何影响性能。选择最好的

您真的应该在内存中执行此操作,还是应该将所有这些行放入SQL并让它在SQL中查询?它真的很擅长这个

但让我们坚持你所问的——内存搜索

当搜索花费的时间太长时,只有一个解决方案——搜索更少的东西。要做到这一点,可以对数据进行分区,使您能够用尽可能少的操作轻松排除尽可能多的节点

在您的情况下,您有两个标准-代码和日期范围。这里有一些想法

您可以基于代码进行分区-即Dictionary>-如果您有许多均匀分布的代码,那么您的列表大小将分别为N/M(其中N=事件总数,M=事件数)。因此,100万个节点和10个代码现在需要搜索10万个项目,而不是100万个。但你可以更进一步。列表本身可以按开始时间排序,允许二进制搜索非常快地排除许多其他节点。(当然,这在建立数据收集的时间上有一个权衡)。这将提供非常快的速度

您可以基于日期进行分区,只需将所有数据存储在一个按开始日期排序的列表中,然后使用二进制搜索查找开始日期,然后前进查找代码。与字典相比,这种方法是否有好处?这取决于程序的其余部分。也许成为一名IList很重要。我不知道。你需要弄清楚

您可以通过将开始时间四舍五入到某个边界(取决于事件的长度、粒度和频率)来翻转字典模型分区。这基本上是将数据分成具有相似开始时间的组。例如,在12:00和12:01之间启动的所有事件可能都在一个存储桶中,等等。如果您有非常少的事件和大量的高频率(但不是病理性的)事件,这可能会提供非常好的查找性能


重点是什么?想想你的数据。考虑添加新数据和查询数据应该花费多大的代价。考虑数据类型如何影响这些特征。根据这些数据做出明智的决定。如果有疑问,请让SQL为您解决。

我同意,但在插入记录之前,您不能使用数据库(它们来自平面文件)。当然,我可以在之前将它们加载到暂存表中,但我更喜欢缓存代码并立即使用正确的外键插入。否则,我首先必须插入记录(总计6000万条),然后再次更新所有6000万条记录(日志文件等)。好的,基于此,也许你想要一个分类列表而不是一本字典。这会在列表中放置一个索引(不是双关语),这样可以更快地查找。如果需要在短时间内多次调用,将它们缓存在内存中是一个好的解决方案。O
//in initialization somewhere
ILookup<string, T> l_z_lookup = l_z.ToLookup(z=>z.CODE);

//your repeated code:
var z_fk = (from z in lookup[lookupCode]
            where z.VALIDFROM <= lookupDate && z.VALIDUNTIL >= lookupDate 
            select z.id).SingleOrDefault();