使用LINQ查询巨大的CSV和Excel

我试图find方法来访问巨大的Excel或CSV文件,并执行聚合操作,如总和,计数和特定的SQL像操作select,分组等。我意识到的事实,即LINQ可以帮助我做到这一点。 我更喜欢使用C#。 我的问题是:

1)在执行任何查询时,将excel或CSV中的数据加载到内存中? 所有文件大约12GB。 我问的原因是我不希望应用程序挂起。

2)我可以创build一个表单应用程序,其中我有一个TextArea其中列出了CSV / Excel的所有列。 用户可以select从TextArea中select任何列。 我打算有SELECT,GROUP BY,SUM,AVERAGE等许多SQL选项。 用户可以select其中之一,并在内部使用LINQ构build查询并获得结果。 结果可以存储在一个文本文件中。

我不确定这是否可行。 请在此build议我。 我是使用LINQ的新手。 如果这不可能通过LINQ,请您build议其他方法来有效地做到这一点。

提前致谢。

内存不是你的问题 – 性能是。

我完全同意关于把这个加载到一个合适的数据库的索引的意见,索引行等,使它更有效率。 如此一来,通过直接阅读CSV来执行您描述的各种查询是可能和简单的(如果非常不明智的话)。

我认为在这个任务中testingLINQtoCSV会很有趣。

没有任何准备好的巨大的csv文件手,我只是用下面的标题生成一个:

 "row,Guid,Grouping1,Grouping2,Metric1,Metric2" 

其中row是行号, Guid只是一个随机guid Grouping1是0到49之间的随机int, Grouping2是0到50,000之间的随机int, Metric1Metric2分别是随机整数和双精度。 基本上只是一些随机数据。

然后我生成了一个99999999行的7Gb +文件…(我瞄准了100,000,000,但是在某个地方打了我的零索引盲点!)

所以好消息是LINQtoCSV会高兴地parsing这个坏男孩,而不必将文件加载到内存中。 只需在csv中创build一行代表的类:

  class Entry { [CsvColumn(FieldIndex = 1, Name="row")] public long Id { get; set; } [CsvColumn(FieldIndex = 2)] public string Guid { get; set; } [CsvColumn(FieldIndex = 3)] public int Grouping1 { get; set; } [CsvColumn(FieldIndex = 4)] public int Grouping2 { get; set; } [CsvColumn(FieldIndex = 5)] public int Metric1 { get; set; } [CsvColumn(FieldIndex = 6)] public double Metric2 { get; set; } } 

然后将条目读入IEnumerable

  IEnumerable<Entry> ReadEntries() { CsvFileDescription inputFileDescription = new CsvFileDescription { SeparatorChar = ',', FirstLineHasColumnNames = true }; CsvContext ctx = new CsvContext(); return ctx.Read<Entry>("test.csv", inputFileDescription); } 

并离开你去。

你现在已经有了一个function齐全的IEnumerable,可以用它来查询所有的GroupBy(),Select(),Sum(),Count()等等和Linq的所有其他类似SQL的乐趣。

只是一个小问题….

它不是快!

使用这个简单的LINQtoCSV设置,我并行运行了3个简单的linq查询

  1. 统计所有条目
  2. 统计Group1 == 1的所有条目
  3. Sum Metric2其中Group2 == 1

使用这个代码:

  static IEnumerable<Entry> entries; static void Main(string[] args) { entries = ReadEntries(); var tasks = new List<Task> {timeEntryCount,timeEntryCountWhereG1_equals_1,timeMetric2SumWhereG2_equals_1}; tasks.ForEach(t => t.Start()); Task.WaitAll(tasks.ToArray()); Console.WriteLine("Entry count took " + timeEntryCount.Result); Console.WriteLine("Entry count where G1==1 took " + timeEntryCountWhereG1_equals_1.Result); Console.WriteLine("Metric1 sum where G2==1 took " + timeMetric2SumWhereG2_equals_1.Result); Console.ReadLine(); } static Task<TimeSpan> timeMetric2SumWhereG2_equals_1 = new Task<TimeSpan>(() => { DateTime start = DateTime.Now; double sum = entries .Where(e => e.Grouping2 == 1) .Sum(e=>e.Metric2); Console.WriteLine("sum: " + sum); DateTime end = DateTime.Now; return end - start; },TaskCreationOptions.LongRunning); static Task<TimeSpan> timeEntryCountWhereG1_equals_1 = new Task<TimeSpan>(() => { DateTime start = DateTime.Now; long count = entries .Where(e=>e.Grouping1==1) .Count(); DateTime end = DateTime.Now; Console.WriteLine("countG1: " + count); return end - start; }, TaskCreationOptions.LongRunning); static Task<TimeSpan> timeEntryCount = new Task<TimeSpan>(() => { DateTime start = DateTime.Now; long count = entries.Count(); Console.WriteLine("CountAll: " + count); DateTime end = DateTime.Now; return end - start; }, TaskCreationOptions.LongRunning); 

现在可以肯定的是,这种狡猾的平行主义不是testing这个最好的方法,但我只是从臀部开始拍摄。

然而,结果是真的非常难看,我的合理快SSD配备笔记本电脑:

 countG1: 2003023 CountAll: 99999999 sum: 1236810295.16543 Entry count took 00:25:26.3767852 Entry count where G1==1 took 00:24:41.9855814 Metric1 sum where G2==1 took 00:25:30.9080960 

这大概是25分钟来查询文件。 如果这不促使你把这个数据库转换成索引数据库,