解决 C# 读取数据库时 当另一个read操作挂起时不能调用read方法的问题
最近有小伙们遇到一个数据库读取的问题,
她连续读取了多张表,并且表里面存储了二进制数据,所以导致界面卡死。
她这里的代码如下:
private void xxx(MySqlConnection conn, string tableName) { string sql = $"SELECT * FROM `{tableName}`"; MySqlCommand cmd = new MySqlCommand(sql, conn); var reader = cmd.ExecuteReader(); while (reader.Read()) { //读取逻辑 } }
一开始我想的是使用SqlDataApter,因为我一直以为SqlDataApter是批量读取,速度会更快。
然后在此基础上,我再加上了异步。
大概逻辑如下
1 using (MySqlCommand cmd = new MySqlCommand(sql, conn)) 2 { 3 using (MySqlDataAdapter mda = new MySqlDataAdapter(cmd)) 4 { 5 DataSet ds = new DataSet(); 6 mda.Fill(ds); 7 var dt = ds.Tables[0]; 8 9 foreach (DataRow row in dt.Rows) 10 { 11 T t = MapDataRow<T>(row, isLowerCase); 12 list.Add(t); 13 } 14 } 15 }
但是这样操作以后,如果我快速切换,然后在未读取完成后,退出程序,就会报如下的错误

查了一下,大概原因就是上一个SqlDataApter未读取完成。
所以在进行大量数据读取时,尽量使用SqlDataReader方式进行读取,
同步方式示例如下:
//创建连接(省略) string sql = $"SELECT * FROM `{tableName}`"; MySqlCommand cmd = new MySqlCommand(sql, conn); var reader = cmd.ExecuteReader(); while (reader.Read()) { Id = reader.GetInt32("id"), //其它字段读取逻辑省略 }
异步方式示例如下:
//创建CancellationTokenSource(省略) using (MySqlCommand cmd = new MySqlCommand(sql, conn)) { using (var reader = await cmd.ExecuteReaderAsync(cts.Token)) { while (await reader.ReadAsync(cts.Token)) { if (cts.Token.IsCancellationRequested) break; Id = reader.GetInt32("Id"), //其它字段读取逻辑省略 } } }
可以通过反射,将逐行读取的逻辑进行简写
1 private T MapReader<T>(DbDataReader reader, bool isLowerCase = false) where T : class, new() 2 { 3 T t = new T(); 4 var propertyList = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); 5 6 foreach (var property in propertyList) 7 { 8 try 9 { 10 string columnName; 11 12 // 特殊字段映射 13 // 这里只增加大小写的处理 14 columnName = isLowerCase ? property.Name.ToLower() : property.Name; 15 16 int ordinal; 17 try 18 { 19 ordinal = reader.GetOrdinal(columnName); 20 } 21 catch (IndexOutOfRangeException) 22 { 23 continue; // 如果列不存在,跳过 24 } 25 26 if (reader.IsDBNull(ordinal)) 27 { 28 property.SetValue(t, null); 29 } 30 else 31 { 32 var targetType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType; 33 var value = reader.GetValue(ordinal); 34 property.SetValue(t, Convert.ChangeType(value, targetType)); 35 } 36 } 37 catch 38 { 39 // 可以选择 log 或忽略 40 } 41 } 42 43 return t; 44 }
前面while里的调用逻辑就可以使用MapReader进行简写
1 using (MySqlCommand cmd = new MySqlCommand(sql, conn)) 2 { 3 4 using (var reader = await cmd.ExecuteReaderAsync(cts.Token)) 5 { 6 while (await reader.ReadAsync(cts.Token)) 7 { 8 if (cts.Token.IsCancellationRequested) 9 break; 10 11 //假设是Student类 12 var student = MapReader<Student>(reader); 13 } 14 } 15 16 }
最后再总结一下,在C#中,SqlDataAdapter和SqlDataReader的区别
| 特性 | SqlDataReader | SqlDataAdapter |
|---|---|---|
| 连接模式 | 连接式(长期保持连接) | 断开式(仅操作时连接) |
| 数据访问 | 只读、只进 | 可读写、随机访问 |
| 内存占用 | 低(流式处理) | 高(数据缓存到内存) |
| 数据更新 | 不支持直接更新 | 支持批量更新 |
| 灵活性 | 低(只能读取) | 高(支持复杂操作) |
| 性能 | 高(适合大量数据快速读取) | 中等(有额外开销) |
| 适用数据量 | 大(无需一次性加载全部) | 中小(全部加载到内存) |
分别适应的场景:
总结:

浙公网安备 33010602011771号