解决 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 }

 

但是这样操作以后,如果我快速切换,然后在未读取完成后,退出程序,就会报如下的错误

image

 

查了一下,大概原因就是上一个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#中,SqlDataAdapterSqlDataReader的区别

特性SqlDataReaderSqlDataAdapter
连接模式 连接式(长期保持连接) 断开式(仅操作时连接)
数据访问 只读、只进 可读写、随机访问
内存占用 低(流式处理) 高(数据缓存到内存)
数据更新 不支持直接更新 支持批量更新
灵活性 低(只能读取) 高(支持复杂操作)
性能 高(适合大量数据快速读取) 中等(有额外开销)
适用数据量 大(无需一次性加载全部) 中小(全部加载到内存)

 

分别适应的场景:

  • 当需要快速读取大量数据且不需要修改时,优先使用 SqlDataReader
    • SqlDataReader 采用流式读取方式,数据逐条从数据库传输到应用程序,不需要将所有数据一次性加载到内存中。对于大量数据(如几万、几十万行),这种方式能显著降低内存占用。
    • 由于 SqlDataReader 是只读、只进的数据流,且保持数据库连接(连接模式),减少了数据传输的中间环节,读取速度通常比 SqlDataAdapter 更快。
    • 批量读取往往伴随批量处理(如导入、分析、转换),SqlDataReader 可以边读边处理,无需等待所有数据加载完成,适合流水线式操作。
  • 当需要离线处理数据或需要修改并更新回数据库时,使用 SqlDataAdapter

 

总结:

  • SqlDataReader 更适合性能敏感的场景
  • SqlDataAdapter 更适合灵活性要求高的场景

 

posted @ 2025-09-12 15:50  zhaotianff  阅读(32)  评论(0)    收藏  举报