☰
VB.NET连接数据库实战:ADO.NET核心对象与参数化查询详解
2026/10/15 5:16:19 网站建设 项目流程

开写之前先说明,这篇的内容完全围绕“VB.NET 连接并操作数据库”这个主题展开,我尽量按照一线踩坑的经验来写,代码直接能用,注释和思路也一并给到,你看完能上手就行。

如果你现在打开招聘软件,搜“VB.NET”,岗位数量确实不算多。但你去那些银行、制造业、老牌软件公司的内部系统里看看,VB.NET写出来的业务程序可能还在跑着核心数据流。这意味着“VB.NET连接数据库”这套技能,在很多团队里依然不可替代。我们不讲语法大全,就聊最实在的事情:怎么让VB.NET程序和数据库顺畅地打交道,怎么安全地读数据、写数据、改数据,以及遇到问题怎么排查。这篇文章覆盖从连接字符串、ADO.NET五个核心对象,到参数化查询、事务处理、连接池、性能优化、问题排查的完整链路,适合正在维护老系统的人、想用VB.NET快速写内部工具的人,也适合从VB6迁移过来、想搞清楚现状的开发者。

1. 为什么还得掌握VB.NET连数据库

1.1 这个技能在今天还有用吗

先说结论:非常有用,而且现实需求比想象中要多得多。

很多老企业级系统是用VB.NET搭起来的,尤其是WinForms这类桌面客户端。它们不漂亮、不现代,但业务逻辑扎实,数据模型经过多年打磨,换掉成本极高——不是说没有人想换,而是老板算过账,数据和业务流程都在里面,重写一次风险太大。于是这些系统的日常维护、二次开发、报表导出、数据订正,全都堆在少数几个懂VB.NET的人身上。我见过不少团队,核心系统的维护者就一两个人,工资还挺高,因为别人真的接不住。

另外,VB.NET加上WinForms写内部小工具特别快。数据库里导点数据、批量改字段、生成临时报表,用它半小时就能搞定。有人会问,Python不也能干吗?能,但在Windows环境、企业内网、要求双击就能跑的场景下,VB.NET编译出来的原生Windows程序有它的便利性——不用装解释器,不用配环境变量,双击走人。

还有一个容易被忽略的点:VB.NET的数据库操作基于ADO.NET,这套模型和C#完全一致。你在VB.NET里学会了SqlConnection、SqlCommand、DataTable,换成C#只是语法差异,底层思想一模一样。所以这门技能不局限于一种语言,它其实是通往.NET数据访问体系的敲门砖,学会了不亏。

1.2 动手前需要准备什么

环境方面,Visual Studio还是首选。装的时候注意勾选“.NET桌面开发”工作负载,WinForms和WPF模板都会带出来。如果你是维护老项目的人,还可能遇到VS2008、VS2010时代的老代码,那种项目打开的时候耐心一点,先改目标框架再慢慢转换,别一上来就大动干戈。

数据库方面,常见的选择有SQL Server、SQLite、MySQL、Access这几种,对应不同的驱动和数据访问类。SQL Server使用System.Data.SqlClient;MySQL一般装第三方驱动MySql.Data;SQLite用System.Data.SQLite;Access走System.Data.OleDb。我们的重点会放在SQL Server上,因为最经典、最接近老系统的真实场景,但大多数思路其他数据库也通用。

另外,准备一个测试库和测试表,用来跑后面的示例。你本地随便建一个数据库,建一张用户表就可以。表里的字段建议包含自增主键、字符串类型、数字类型、日期类型各一个,这样后面演示增删改查、参数化、绑定控件都比较方便。建表脚本可以直接复制到SQL Server Management Studio里执行:

CREATE DATABASE TestDb; GO USE TestDb; GO CREATE TABLE Users ( Id INT IDENTITY(1,1) PRIMARY KEY, Name NVARCHAR(50) NOT NULL, Age INT NOT NULL, CreateTime DATETIME NULL DEFAULT GETDATE() ); GO INSERT INTO Users (Name, Age) VALUES (N'张三', 25), (N'李四', 30);

字段类型为什么用NVARCHAR而不是VARCHAR?这和后面的中文乱码问题直接相关。SQL Server里NVARCHAR按Unicode存储,装中文不会乱码;VARCHAR是按编码页存储,一旦编码对不上,中文就会变成问号。这一点好多老开发都栽过,建表时用NVARCHAR最省心。

2. ADO.NET:VB.NET操作数据库的基石

2.1 五大核心对象的分工

ADO.NET是.NET平台下的数据访问模型,可以用“吃自助餐”来理解。你拿一个盘子来取食物,盘子是Connection,取食的动作是Command,取完放到自己碗里,碗就是DataTable或DataSet,而帮你说“帮我夹那块鸡腿”的助手,就是DataAdapter。这个类比虽然糙,但能快速帮新手建立整体概念。

ADO.NET有五个核心对象,各自分工如下:

对象职责类比
Connection建立与数据库的连接,维护会话水管本身
Command执行SQL语句或存储过程开关水龙头
DataReader只进、只读地流式读取查询结果直接对准水龙头喝水
DataAdapter作为连接与数据集之间的桥梁,填充数据把水接到桶里的中转设备
DataSet / DataTable内存中的数据库表,可离线操作装水的桶

这里最关键的认知是:DataReader是“流式读取”,读一条处理一条,不占太多内存,适合大量数据的顺序处理;DataTable是“缓存式读取”,一次性把结果都存在内存里,适合需要反复筛选、排序、绑定的场景。选错对象是很多性能问题的根源。

比如你只是遍历一遍100万行数据做统计,用DataReader没问题;但如果你要把数据绑定到DataGridView,还需要支持用户排序筛选,就别用DataReader一条条塞,直接用DataAdapter把结果灌进DataTable,一次性绑上,让控件自己去处理。如果非要用DataReader往DataTable里一行行填,也行,但代码啰嗦,效率也不高。

2.2 连接字符串:数据库的敲门砖

连接字符串是数据库操作的“门禁卡”,写错了连门都进不去。它是一个由分号分割的键值对集合,长得像这样:

Dim connStr As String = "Data Source=.;Initial Catalog=TestDb;Integrated Security=True;TrustServerCertificate=True"

拆开看每个键的含义:

  • Data Source:数据库服务器地址。一个点表示本机,也可以写localhost、机器名、IP加端口。
  • Initial Catalog:要访问的数据库名。
  • Integrated Security:是否使用Windows身份认证。True表示用当前Windows账号登录,不用写账号密码。
  • User ID / Password:SQL Server身份认证时使用,替代Integrated Security。
  • TrustServerCertificate:本地开发时加密证书不受信任会报错,加上True能省很多事。

如果你要连局域网内的SQL Server,地址可以写成192.168.1.100,1433,逗号后面跟端口号。很多新人在这里踩坑:服务器地址不写端口,结果怎么都连不上,后来才发现默认端口是1433,你换了端口就必须显式写出来。

不同数据库的连接字符串格式差异也不小,我常用到的几种列出来方便参考:

数据库连接字符串示例
SQL ServerData Source=.;Initial Catalog=TestDb;Integrated Security=True
MySQLServer=localhost;Database=TestDb;Uid=root;Pwd=123456;Charset=utf8mb4
SQLiteData Source=C:\test.db
AccessProvider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\test.accdb

连接字符串建议放在App.config的connectionStrings节点里,不要硬编码在代码中。后面要换服务器环境时,只改配置文件,不用重新编译发布,这真的是老项目维护里最实用的习惯之一。

3. 实操:从连接到查询增删改

3.1 连接数据库的最简套路

在VB.NET里连接数据库,最标准的做法就是“创建连接、打开连接、执行操作、释放资源”。释放资源一定要用Using语句,因为Using会自动帮你调用Dispose,即使中途抛异常也会执行清理。数据库连接是稀缺资源,不释放很快就把连接池耗尽,程序假死给你看。

这里给出一个最标准的连接代码模板:

Imports System.Data.SqlClient Dim connStr As String = "Data Source=.;Initial Catalog=TestDb;Integrated Security=True;TrustServerCertificate=True" Using conn As New SqlConnection(connStr) conn.Open() ' 在这里执行具体操作 End Using

Using conn As New SqlConnection(connStr)这段代码包含三件事:创建连接对象、进入受保护的作用域、退出作用域时自动关闭连接。你不需要显式调用conn.Close(),因为Using块结束时连接就已经被释放了。这是我在实际开发中最推荐新手养成的好习惯——凡是数据库操作,先写Using,再想业务逻辑。

有人可能见过老代码里只写conn.Open(),然后就不管了,或者用Try...End Try手动Close。那是VB6时代遗留下来的坏习惯,在.NET里你有更好的工具,就不要学那种写法了。特别是维护老系统的人,看到一个没包Using的数据库函数,第一反应应该是给它套上Using,这往往能解决一部分程序越跑越慢、连接数飙高的问题。

3.2 查询数据:SqlDataReader与DataTable怎么选

先看SqlDataReader的使用方式。它适合“读一遍就走”的场景,比如统计汇总、导出数据、流式生成文件。典型代码如下:

Using conn As New SqlConnection(connStr) conn.Open() Dim sql As String = "SELECT Id, Name, Age FROM Users" Using cmd As New SqlCommand(sql, conn) Using reader As SqlDataReader = cmd.ExecuteReader() While reader.Read() Console.WriteLine($"{reader("Id")} {reader("Name")} {reader("Age")}") End While End Using End Using End Using

注意reader("Name")这种方式虽然简单直观,但它每次都要按名字去查找列的索引,在循环里做这件事消耗不小。如果数据量很大,可以用reader.GetOrdinal("Name")先拿到列号,再用GetString读取,性能会有明显提升。在百万级数据的循环里,这种细节就会体现在运行时间上。

再来看DataAdapter填充DataTable的方式,适合把结果集缓存到内存后二次使用:

Using conn As New SqlConnection(connStr) Dim sql As String = "SELECT Id, Name, Age, CreateTime FROM Users" Dim da As New SqlDataAdapter(sql, conn) Dim dt As New DataTable() da.Fill(dt) For Each row As DataRow In dt.Rows Console.WriteLine($"{row("Name")} {row("Age")}") Next End Using

Fill方法会自动打开和关闭连接,所以这里不写conn.Open()也不会有问题。但我个人习惯还是先Open,显式控制连接的生命周期,避免遇到某些特殊驱动行为时掉坑。

如果查询的结果需要绑定到DataGridView,直接设置DataGridView1.DataSource = dt就完事了。DataTable作为数据源的好处是它可以被多种控件直接识别,排序、分页、筛选这些操作控件自己就会做,不需要你写额外代码。

3.3 写数据:增删改的标准流程

插入、更新、删除这几类操作统一用Command对象的ExecuteNonQuery方法来执行。这个方法的名字很直白:执行一个不返回结果集的SQL语句,返回受影响的行数。标准插入代码如下:

Using conn As New SqlConnection(connStr) conn.Open() Dim sql As String = "INSERT INTO Users(Name, Age) VALUES(@Name, @Age)" Using cmd As New SqlCommand(sql, conn) cmd.Parameters.AddWithValue("@Name", "王五") cmd.Parameters.AddWithValue("@Age", 28) Dim affected As Integer = cmd.ExecuteNonQuery() Console.WriteLine($"影响了 {affected} 行") End Using End Using

这里用到了参数化查询,也就是SQL语句里通过@Name这种占位符传值,后面再给参数赋值。为什么非要这样写?最核心的原因是安全——防SQL注入。另一个原因是可读性,你把参数和业务值分离,代码结构更清楚。后面我会专门展开讲参数化查询。

更新的逻辑类似,执行UPDATE语句。删除也类似,执行DELETE语句。注意在执行删除之前最好先确认条件语句的WHERE写得对不对,不然一不小心把整张表清空,这种事故我见过不止一次。生产数据操作前,先在测试环境跑一遍同样的SQL,确保影响行数符合预期,再拿到线上执行。这是一个枯燥但救命的习惯。

3.4 配合UI:把数据绑定到控件

WinForms开发里最常见的需求就是下拉框绑定和表格绑定。下拉框的经典写法是设置DataSource、DisplayMember、ValueMember三个属性:

Dim dt As DataTable = GetUserList() ComboBox1.DataSource = dt ComboBox1.DisplayMember = "Name" ' 显示名称字段 ComboBox1.ValueMember = "Id" ' 隐藏的主键字段

这样绑定之后,用户看到的是姓名,取Value的时候拿到的是Id,非常方便后续做关联查询或者保存业务单据。这里有个细节:绑定之前最好先清空Items,否则反复刷新时会出现重复项。可以调用ComboBox1.Items.Clear(),但如果已经设置了DataSource,清空Items可能不生效,更好的做法是把DataSource设为Nothing再重新绑定。

DataGridView绑定就简单了,直接设置DataSource即可:

Dim dt As DataTable = GetUserList() DataGridView1.DataSource = dt

如果你想让表格某些列不显示,或者给列换个中文标题,可以修改DataGridView的Columns属性。新手最容易犯的错是:绑定数据之后再去代码里手动加列,结果出现两套列。正确的做法是设置DataGridView1.AutoGenerateColumns = False,再手动定义Columns,这样列完全由你控制。

4. 参数化查询:安全红线不能碰

4.1 SQL注入是怎么翻车的

先说个真实案例。某内部系统有一个登录窗口,代码写成了字符串拼接:

Dim sql As String = "SELECT * FROM Users WHERE Name = '" & txtName.Text & "' AND Password = '" & txtPwd.Text & "'"

如果在用户名输入框中填入这样的内容:

' OR 1=1 --

拼接后的SQL变成:

SELECT * FROM Users WHERE Name = '' OR 1=1 --' AND Password = ''

两个连续减号把后面的条件注释掉了,1=1恒真,于是整张表的数据都被查出来了。这只是最简单的一招,更狠的还能执行删除表、写文件、提权。SQL注入曾经是OWASP列表里常年排第一的Web安全风险,放在桌面应用也同样成立。很多人觉得“我们这是内网系统,就几个人用”,但内网不等于安全,一次失误就能造成严重数据事故。

很多VB6老代码里,字符串拼接SQL是常态,因为当年ADO操作就是那么顺手写。但是到了VB.NET时代,ADO.NET提供了完善的参数化机制,你没有任何理由继续用拼接方式。一旦维护到带拼接的老代码,应该立刻改造,这是底线问题。

4.2 参数化查询的标准写法

参数化查询的核心思想:SQL语句中只写占位符,具体值通过Parameters集合传递,让数据库驱动把值作为“数据”而不是“SQL代码”来处理。SQL Server的占位符以@开头,代码如下:

Dim sql As String = "INSERT INTO Users(Name, Age) VALUES(@Name, @Age)" Using cmd As New SqlCommand(sql, conn) cmd.Parameters.AddWithValue("@Name", txtName.Text) cmd.Parameters.AddWithValue("@Age", Integer.Parse(txtAge.Text)) cmd.ExecuteNonQuery() End Using

这样用户输入' OR 1=1 --也只会被当作一个普通的字符串值插入到Name字段中,根本不参与SQL语法解析。无论用户输入什么,都影响不了SQL语句的结构。参数化不仅防注入,还让数据库有机会缓存执行计划,重复执行相同结构的SQL时性能更好。

关于AddWithValue,微软官方文档其实建议慎用。因为AddWithValue会自行推断参数类型和长度,推断不准时可能引发隐式转换,导致查询不走索引或者数据截断。如果你想写得更严谨,建议用Add加显式类型和长度:

cmd.Parameters.Add("@Name", SqlDbType.NVarChar, 50).Value = txtName.Text cmd.Parameters.Add("@Age", SqlDbType.Int).Value = Integer.Parse(txtAge.Text)

这两行代码明确告诉数据库参数的类型和长度,NVarChar和表结构保持一致,查询优化器能更准确地选择执行计划。在数据量小的内部系统里,AddWithValue和Add的差异感觉不明显,但认真写的代码和应付事的代码,区别就在这些细节上。

5. 事务与异常处理:数据一致性保命绳

5.1 什么时候必须用事务

事务就是把多个数据库操作打包成一个整体:要么全部成功,要么全部回滚。最经典的例子是转账。A账户扣钱100元,B账户加钱100元。如果第一步执行成功,第二步执行失败,A的钱扣了,B的钱没到账,这种数据不一致在财务系统里就是事故。用事务包住这两个操作,任何一步失败就整体回滚,数据永远处于一致状态。

VB.NET里使用事务的标准套路:

Using conn As New SqlConnection(connStr) conn.Open() Dim trans As SqlTransaction = conn.BeginTransaction() Try Dim sql1 As String = "UPDATE Account SET Balance = Balance - 100 WHERE Id = 1" Using cmd1 As New SqlCommand(sql1, conn, trans) cmd1.ExecuteNonQuery() End Using Dim sql2 As String = "UPDATE Account SET Balance = Balance + 100 WHERE Id = 2" Using cmd2 As New SqlCommand(sql2, conn, trans) cmd2.ExecuteNonQuery() End Using trans.Commit() Console.WriteLine("转账成功") Catch ex As Exception trans.Rollback() Console.WriteLine("转账失败:" & ex.Message) End Try End Using

关键是SqlCommand的构造函数传入了transaction对象,保证这条命令是在事务中执行的。如果你忘记传transaction,命令会默认以自动提交模式执行,事务就形同虚设了。这条细节我遇到过好几个人问为什么回滚不生效,基本都是这个原因。

为什么在这里用Try...Catch而不是只靠Using?因为事务的提交/回滚是业务逻辑的一部分,你需要根据执行结果决定调用Commit还是Rollback,这是普通资源释放处理不掉的。记住一个判断标准:如果几个操作之间有业务关联,必须同时成功或同时失败,那就要上事务,不要心存侥幸。

5.2 异常处理的正确姿势

数据库操作可能抛出各种异常:连接超时、主键冲突、约束违反、字符截断、死锁等。实际项目里,我建议区分异常类型来处理,至少分成数据库异常和通用异常两层:

Try Using conn As New SqlConnection(connStr) conn.Open() ' 数据库操作 End Using Catch ex As SqlException ' 这里可以对数据库特有错误做针对性处理 Console.WriteLine("数据库操作失败:" & ex.Number & " " & ex.Message) Catch ex As Exception Console.WriteLine("系统异常:" & ex.Message) End Try

SqlException有一个Number属性,表示SQL Server错误码。比如主键冲突通常对应2627号错误,死锁对应1205号,超时对应-2。拿到这些错误码之后,你就可以写一些非常有针对性的提示文案。举个例子,如果捕获到2627,说明是主键重复,你可以在界面上提示“编号已存在,请勿重复录入”,而不是显示一段英文原始错误信息。

数据库操作的异常处理里还有一条原则:不要在Catch块中无限重试。如果连接超时,重试一次是合理的;但如果是SQL语法错误、约束错误,重试一万次也白搭。重试逻辑一定要限制次数和间隔,不然在高并发场景下反而会加重数据库压力,把一个本来能恢复的问题拖成雪崩。

6. 性能调优与连接池

6.1 连接池为什么能事半功倍

建立数据库连接是一件相对昂贵的操作。它需要网络握手、身份验证、权限检查、会话初始化,纯从性能角度看,比执行一条简单SQL花的时间还多。如果每次都新建连接、用完关闭,在高频操作下会浪费很多时间在握手上面。

ADO.NET默认开启了连接池机制。同一连接字符串下,连接对象关闭后不是真的断开,而是被放进池里,下次创建同样连接字符串的连接时会直接从池里取。这就好比你去饭店吃饭,餐具不是每次都用完就扔,而是洗好放回柜子,下次直接拿出来用。

关于连接池,最重要的是理解“连接字符串是池的区分键”。只要连接字符串有一个字符不同,就会被视为不同的池,导致无法复用连接。有些人习惯在代码里动态拼接连接字符串,明文密码每次都一样,但多了个空格,性能就下降一个档次,池化效果大打折扣。建议把连接字符串统一放在配置文件里,整个应用程序使用同一个字符串。

如果遇到连接池耗尽,最常见的原因是连接对象没有在写好的Using块中释放。经典症状是程序运行一段时间后开始报“超时时间已到,在从池中获取连接之前”。数据库本身没问题,就是连接被占满没有归还。排查方法是在SQL Server管理工具里执行SELECT * FROM sys.dm_exec_sessions WHERE program_name LIKE '%你的程序%',看看是不是会话数一路暴涨。

6.2 三个立竿见影的优化习惯

第一个习惯:只在需要时才打开连接,用完立刻释放。不要把一个连接保持几个钟头不用,那跟占着茅坑不拉屎没什么区别。数据库连接是短生命周期资源,每操作一次就开一次、关一次,有连接池托底,性能开销很小。

第二个习惯:SELECT语句只取你需要的列,不要动不动就SELECT *。很多人写代码图方便,把几十个字段全查出来,客户端的内存、网络传输全都被白占。你需要哪几列就写哪几列,尤其是配合DataTable绑定时,多余的列还会增加控件渲染负担。

第三个习惯:大数据量查询时分页,不要一次性load全部数据到内存。SQL Server可用OFFSET ... FETCH NEXT做分页,例如:

SELECT Id, Name, Age FROM Users ORDER BY Id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;

Offset参数表示跳过多少行,Fetch表示取多少行。页大小乘以页码减一就是偏移量。如果数据量是几十万行,分页的效果非常明显。这里要强调的是:分页查询必须要有确定的排序字段,否则数据库返回的顺序本身就不稳定,翻页时会出现重复或遗漏。

7. 典型问题排查实录

7.1 连接超时与连接失败的排查清单

连接失败是新手问得最多的问题。我整理了一个排查顺序,从最常见到最少见排列,遇到问题照着走就行:

现象检查点说明
登录失败账号、密码SQL Server验证模式下要检查是否启用了混合认证模式
找不到服务器地址、端口地址写localhost或.,不要带空格的尾巴
网络错误防火墙、服务状态确认SQL Server服务真的在运行,防火墙是否放行端口
证书错误TrustServerCertificate本地开发环境加上TrustServerCertificate=True
超时连接池是否已满检查代码中是否有连接没释放,Using是否漏写

如果你确认代码没写错,先打开SQL Server配置管理器,看看SQL Server服务是不是停止状态。这个情况往往出现在服务器重启之后,系统起了一半,数据库服务没起来,客户端就开始猛报“找不到服务器”。然后检查连接字符串里的端口,改了默认端口必须显式写出来。

调试连接问题的小技巧:把连接字符串打印出来,检查一下是否有前后空格或不可见字符。我有一次排查半天才发现,Data Source前面多了一个空格,导致连接字符串整体失效。这类肉眼看不到的坑,通常是复制粘贴造成的。

7.2 中文乱码问题

乱码问题可以从三个方向排查:数据库字段类型、客户端传输、显示环境。数据库字段类型是最大变量。SQL Server里VARCHAR是按照数据库排序规则存储的,和客户端代码页不一致时中文会乱码;NVARCHAR按Unicode存储,对中文支持最稳定。建表时尽量用NVARCHAR,基本上省去一批乱码烦恼。

第二个方向是连接字符串里的编码设置。MySQL连接字符串可以显式指定Charset=utf8mb4,这样可以确保传输层使用正确编码。SQL Server一般通过集成协议自动处理,但如果你发现中文从应用读出来是乱码,可以检查一下是否把NVARCHAR列的值读进了VARCHAR变量,那等于把Unicode数据硬塞进非Unicode容器,必然会出问题。

第三个方向容易被忽略,就是控制台窗口的字符集。Console.WriteLine输出中文时,如果系统区域语言设置不是中文,控制台看着也是乱码,但数据在数据库里其实是好的。这时候不要乱改代码,先确认是不是显示问题。用MessageBox弹个窗看,如果弹窗正常、控制台不正常,就是显示环境问题,和数据库操作没有关系。

7.3 资源泄漏与程序卡死

资源泄漏的典型表现:程序刚启动一切正常,运行几个小时后越来越慢,最后连操作都响应不了。打开任务管理器看内存或线程数,会发现它们在持续增长。造成这种问题的原因通常是三类:要么连接没释放,要么DataReader没有关闭,要么事件处理器重复绑定。

关于连接没释放,前面强调过很多次,Using是标配。但有一点要特别说:DataReader本身会持有连接,如果你用DataReader循环读数据,中途Break退出,一定要确保代码走到Using块的结束,否则连接可能一直不归还。所以DataReader的使用一定要包在Using里,循环要有确定的退出边界,不要存在提前Return的情况却不释放资源。

程序卡死的另一大常见原因是主线程上执行了耗时数据库操作。WinForms界面上按钮一点,直接在主UI线程同步执行一条大查询,界面会假死白屏好几秒。解决办法是改成异步,可以用Async/Await配合ExecuteReaderAsync、ExecuteNonQueryAsync这些方法。这里给一个简单的异步查询示例:

Private Async Function LoadDataAsync() As Task(Of DataTable) Dim dt As New DataTable() Using conn As New SqlConnection(connStr) Dim sql As String = "SELECT Id, Name, Age FROM Users" Using cmd As New SqlCommand(sql, conn) conn.Open() Using reader As SqlDataReader = Await cmd.ExecuteReaderAsync() dt.Load(reader) End Using End Using End Using Return dt End Function

调用时用Dim dt As DataTable = Await LoadDataAsync(),UI就不会被阻塞。不过要注意:Async不等于更快,它只是让线程在等待数据库响应时腾出手来继续响应界面。数据库查询本身该多久还多久,该用索引优化还是得用索引优化。

最后分享一个我在实际维护老项目时养成的习惯:凡是数据库操作代码,一律先写Using,再想业务逻辑;所有SQL语句统一走参数化;涉及多条修改的统一包事务。刚开始你会觉得这三条是形式主义,但等系统上线跑了一段时间后,你就会发现,那些不守这三条规矩的同事,在凌晨处理数据问题时比你勤快得多。这套知识点本身不难,难的是把好习惯焊死在代码里,然后不断在真实项目里打磨。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询