Linq命令出错 - 已经有一个与此命令关联的打开DataReader,必须先关闭它

时间:2015-01-21 07:44:51

标签: c# multithreading linq

以下linq声明:

    List<Column> columns = table.Columns.Cast<Column>().SelectMany(t => table.Columns.Cast<Column>().Where(col => col.DataType.Equals(DataType.NVarChar(4000))).Select(col => col)).ToList(); 

需要大约5秒才能执行,对于500多个表,这是非常长的时间。所以我决定采用并行循环。因此,该过程不会等待完成并继续下一次迭代的过程。但是得到错误 - 已经有一个与此命令关联的开放DataReader必须先关闭

以下是我的代码 -

    BackgroundWorker worker = new BackgroundWorker();
                        worker.WorkerReportsProgress = true;
                        worker.WorkerSupportsCancellation = true;
                        worker.DoWork += (_, args) =>
                            {
                                int count = 1;
                                foreach (Table table in DestinationTables)
                                {
                                    Task.Factory.StartNew(() =>
                                        {
                                            Thread thread = new Thread(new ThreadStart(() =>
                                                {
                                                    List<Column> columns = table.Columns.Cast<Column>().SelectMany(t => table.Columns.Cast<Column>().Where(col => col.DataType.Equals(DataType.NVarChar(4000))).Select(col => col)).ToList();
                                                    if (columns.Count > 0)
                                                        encryptedcolumns.Add(table.ToString(), columns);
                                                    Application.Current.Dispatcher.InvokeAsync(() =>
                                                        {
                                                            worker.ReportProgress(count++);
                                                        });


                                                }));
                                            thread.Start();
                                        }).Wait();

                                }

                            };
                        worker.ProgressChanged += (_, args) =>
                            {
                                ProgressBarCurrentValue = args.ProgressPercentage;
                                NotificationText[1] = "Configuration In Progress. " + Math.Round(Convert.ToDouble(progressBarCurrentValue * 100/progressBarMaximumValue) , 2) + "% Completed";
                            };
                        worker.RunWorkerCompleted += (_, args) =>
                            {
                                NotificationText[1] = "Execution Started. Please wait";
                                NotifyPropertyChanged("NotificationText");
                                ExecuteWorker.RunWorkerAsync();
                                NotifyPropertyChanged("ExecuteWorker");
                            };
                        worker.RunWorkerAsync();

如果我添加并行foreach并删除所有三个线程(backgroundworker,taskfactory和最里面的线程),那么系统会冻结,我不会在每次迭代时都进行更新。

2 个答案:

答案 0 :(得分:1)

我认为你不能在单个ADO.NET连接上进行并行查询,至少不能与常见的提供者进行并行查询。即使使用Entity Framework(从版本6开始提供异步功能),您也需要创建多个上下文(每个都有自己的连接)来进行并行查询。

This link适用于Entity Framework,但我相信任何ADO.Net DataReader都是如此。引用:

  

但是...... EF不支持通过相同的DbContext对象处理多个请求。如果同一个DbContext实例上的第二个异步请求在第一个请求完成之前启动(并且这就是整个点),那么您将收到一条错误消息,指出您的请求正在处理打开的DataReader。

也就是说,如果查询不需要彼此的信息,那么没有什么可以阻止你打开不同的连接并且并行查询,而不是相同的。

考虑到在最常见的情况下,对于像这样的简单查询,大部分时间都用于实际传输结果数据(而不是查询本身),在这种情况下,我不会&#39 ;并认为并行处理会有所帮助(除非有某种网络限制限制每个客户端端口的数据传输速率,或类似的东西)

答案 1 :(得分:0)

感谢您的回答。在我的情况下,这有效 -

string sqlcommand = "SELECT s.name + '.' + ts.name AS TableName, 
c.name AS column_name, 
t.name + '(' + Convert(varchar ,c.max_length/2) + ')' AS Datatypename,     
c.PRECISION, c.scale FROM sys.columns AS c
INNER JOIN sys.types AS t ON c.user_type_id=t.user_type_id 
INNER JOIN sys.tables ts ON ts.OBJECT_ID = c.OBJECT_ID 
INNER JOIN sys.schemas s ON s.schema_id = ts.schema_id 
ORDER BY s.name + '.' + ts.name, c.column_id";

DataSet dtset = DestinationDB.ExecuteWithResults(sqlcommand);

在这里,我能够在一秒内获得数据集中所有表的列类型(与一小时相比:D),我可以用它来比较列类型