资讯动态

VB.NET连接数据库实战:ADO.NET增删改查与事务处理详解

发布时间:2026/10/15 5:16:19 来源:尧图企业网站定制
1. 项目概览与真实需求拆解1.1 为什么VB.NET连接数据库仍然是必修课聊到VB.NET连接数据库很多人第一反应是这不是上个时代的老技术吗但实际走进一线开发环境你会发现VB.NET配合ADO.NET操作数据库仍然是大量桌面管理类系统、企业内部工具、供应链客户端的主力技术栈。尤其是一些制造业、物流业、财务软件的老系统很多核心模块就是当年用VB.NET写出来的后续维护甚至二次开发都绕不开这套数据库操作基本功。我接触过一个典型的例子某公司的仓储管理系统前端是VB.NET开发的WinForms窗体后端是一套SQL Server数据库负责整个仓库的入库、出库、盘点、调拨。这个系统从最早的单机版一路演进到局域网多客户端版期间换了三代维护团队但数据库操作层始终是ADO.NET那套东西。新来的开发者如果只会拖控件、写界面不理解连接对象、命令对象、数据集之间的配合关系一旦遇到程序偶尔卡死数据没保存上查询结果不对这类问题就完全没有排查头绪。这个标题背后真正要解决的其实是一整套从界面到数据库的数据链路问题。VB.NET连接并操作数据库不只是一行连接字符串的事它至少包含四层内容如何建立到数据库的可靠连接连接串、认证方式、连接池如何安全高效地执行SQL语句增删改查、参数化、事务如何把数据库结果正确呈现到界面上数据绑定、UI刷新如何排查连接与操作过程中的异常超时、权限、数据类型不匹配1.2 这篇博文适合谁看刚入门的.NET桌面开发初学者需要一条清晰的主线搞明白Connection、Command、DataAdapter、DataSet这些对象到底是干嘛的以及它们怎么配合。刚接手VB.NET老项目的开发人员系统里面全是ADO.NET代码你的任务是在现有架构上改功能、修Bug而不是推倒重来所以必须先吃透这套东西。准备做小型信息管理系统的个人开发者比如帮某个小公司做个进销存、做个资料登记工具VB.NETSQL Server或者轻量级数据库是成本最低、最容易交付的组合之一。不需要你有多深的理论基础只要有一点VB.NET语法概念能够照着代码抄就行。读完这篇内容你可以完成一套完整的数据库连接与操作框架遇到常见问题也能定位到方向。2. 技术选型为什么是ADO.NET而不是ORM框架2.1 两种路线对比打开VS新建项目时你会发现数据库操作有两派方案一派是**EFEntity Framework**这一类ORM框架把数据库表映射成对象写起来很爽。比如Dim user db.Users.FirstOrDefault(Function(x) x.Id 1)另一派就是传统ADO.NET你直接写SQL语句自己管理连接和命令Dim conn As New SqlConnection(connStr) Dim cmd As New SqlCommand(SELECT * FROM Users WHERE Id Id, conn) cmd.Parameters.AddWithValue(Id, 1) conn.Open() Dim reader As SqlDataReader cmd.ExecuteReader()如果你做的是一个全新的、长期演进的大型互联网系统EF这类ORM在某些场景下确实能加速开发。但对于标题里的VB.NET连接并操作数据库这个经典场景我的建议非常明确优先扎扎实实掌握ADO.NET再去谈ORM。原因有三点太多老系统的代码就是ADO.NET你绕不开它。学会了ADO.NET维护老项目立马上手。ADO.NET的整个执行链路透明、可控。一条查询发出去之后经历了连接打开、命令发送、数据读取、结果物化每一个环节都看得见摸得着。出了问题知道去哪看。不需要额外的实体映射配置文件一多、配置一乱反而增加维护成本。2.2 不同数据库的驱动选择VB.NET连接不同数据库步骤几乎一样核心区别只是数据库连接字符串和命名空间不同。数据库核心命名空间连接对象典型连接字符串SQL ServerSystem.Data.SqlClientSqlConnectionData Source服务器名;Initial Catalog库名;User IDsa;Password***;MySQLMySql.Data.MySqlClient需安装驱动包MySqlConnectionServer127.0.0.1;Database库名;Uidroot;Pwd***;SQLiteSystem.Data.SQLite或Microsoft.Data.SqliteSQLiteConnectionData Source本地文件路径;AccessSystem.Data.OleDbOleDbConnectionProviderMicrosoft.ACE.OLEDB.12.0;Data Source文件路径;我以前给一个客户做过一个小工具数据库用的是Access文件程序给所有业务人员装上之后隔三差五有人报打不开数据库后来发现是两台电脑上缺少Access数据库引擎ACE驱动。这个问题提醒我选用什么数据库不只是开发端的偏好也要考虑部署端的环境依赖。SQL Server一般公司IT都有条件装SQLite单文件部署最简单Access驱动缺失很常见部署前必须确认目标机器有没有对应Provider。我个人最推荐的教学组合是VB.NET SQL Server因为SQL Server在连接串标准性、权限配置、网上资料丰富度方面都是最均衡的。SQLite适合做演示和小工具但参数特性少一点学起来反而容易忽略一些关键概念。3. 连接数据库实操全流程3.1 一个最小的可运行示例先给你一个抄了就能跑的版本。假设你本机装好了SQL Server有一个名叫DemoDB的数据库里面有一张Student表字段有Id、Name、Score。打开VS新建一个Windows 窗体应用.NET Framework在窗体上放一个按钮、一个DataGridView。在按钮的Click事件里写Imports System.Data.SqlClient Public Class Form1 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click 1. 定义连接字符串 Dim connStr As String Data Sourcelocalhost;Initial CatalogDemoDB;User IDsa;Password123456; 2. 创建连接对象 Dim conn As New SqlConnection(connStr) 3. 创建命令对象指定SQL和连接 Dim sql As String SELECT Id, Name, Score FROM Student ORDER BY Id Dim cmd As New SqlCommand(sql, conn) 4. 创建数据适配器填充数据表 Dim da As New SqlDataAdapter(cmd) Dim dt As New DataTable() da.Fill(dt) 5. 绑定到界面控件 DataGridView1.DataSource dt End Sub End Class这套流程的核心对象配合我用一句话概括Connection负责建立连接到数据库的通路Command负责上传SQL指令DataAdapter负责把数据库返回的结果转成内存中的DataTableDataGridView负责把DataTable画到屏幕上。每一步都有明确职责任何一个环节出问题现象都会不一样。3.2 连接字符串是怎么解析的很多新手在实际操作中死磕的就是连接字符串这一行某一项写错了最终报的错五花八门。我把SQL Server最常见配置拆开讲。连接串项含义容易踩的坑Data Source服务器地址本机建议写localhost或(local)不要无脑写电脑名命名实例要写成服务器名\实例名Initial Catalog数据库名称必须确保存在否则连上后直接报错User ID登录名如果用SQL Server身份验证必须确认是sa还是其他账号Password登录密码密码含特殊字符时不需要转义直接写进去即可TrustServerCertificate是否信任服务器证书本地开发时常设True避免新版驱动要求强制加密时报证书错误Connect Timeout连接超时秒数默认15秒局域网内排查问题时可以临时调短到3秒快速失败有一次我调试一个系统服务器都正常SQL语句放到查询分析器里也能跑但程序就是报登录失败。后来查出来是连接串里没有TrustServerCertificateTrue客户端和服务器的加密协议协商失败。这种问题最坑因为在老版本的驱动里完全不需要这项配置升级了驱动之后突然就需要了。3.3 连接对象的生命周期管理在写VB.NET程序时最容易被忽视但影响最大的问题就是连接没关。很多人习惯这样写Dim conn As New SqlConnection(connStr) conn.Open() 执行操作…… 没有Close()这样做在测试阶段一切正常但程序运行一段时间之后就会出现连接池已满或者超时时间已到但尚未获得连接的异常。正确的写法有两个主流方案。方案一显式关闭Dim conn As New SqlConnection(connStr) Try conn.Open() 执行命令 Finally conn.Close() End Try方案二用Using代码块推荐Using conn As New SqlConnection(connStr) conn.Open() Using cmd As New SqlCommand(sql, conn) 执行命令 End Using End UsingUsing语句会在代码块结束时自动调用Dispose()而针对连接对象释放时也会自动关闭连接。我给你的建议就一句话凡是创建了实现了IDisposable接口的对象连接、命令、适配器、DataReader要么用Using要么在Finally里手动释放二选一但绝不能裸奔。4. 核心增删改查与边界场景处理4.1 查询数据的不同方式查询是数据库操作里最频繁的动作但查询结果怎么拿有几种不同方案各有用武之地。SqlDataReader只能前进的只读流占用资源最小速度最快适合从头到尾遍历一遍的场景。但它在读取时连接必须保持打开状态。DataTable SqlDataAdapter一次性把结果存到内存里断开连接后仍然可以使用数据。适合需要把数据显示在界面上、翻页、排序的场景。DataSet相当于一个内存中的小数据库可以包含多张表表之间还能建立关系。适合在客户端做较复杂的数据操作。举一个直观例子如果你想批量导出所有记录到Excel或文本文件遍历一遍就够了用SqlDataReader最合适。但如果界面需要支持筛选、排序比如点击列头排序那就应该用DataTable。 DataReader读取方式 Using conn As New SqlConnection(connStr) conn.Open() Using cmd As New SqlCommand(SELECT Name, Score FROM Student WHERE Score minScore, conn) cmd.Parameters.AddWithValue(minScore, 60) Using reader As SqlDataReader cmd.ExecuteReader() While reader.Read() Dim name As String reader(Name).ToString() Dim score As Integer CInt(reader(Score)) ListBox1.Items.Add(name - score.ToString()) End While End Using End Using End Using注意这里reader(Name)返回的是Object类型要转成具体类型再使用。你虽然可以直接ToString()但如果数据库里存的是NULL它会返回空字符串而不是抛异常——这既是特性也是坑因为NULL和空字符串在业务语义上是两码事。4.2 增删改的标准套路增删改Insert/Update/Delete本质上都是执行不返回结果集的SQL命令用ExecuteNonQuery()来执行返回值是受影响的行数。 插入一条记录 Using conn As New SqlConnection(connStr) conn.Open() Dim sql As String INSERT INTO Student (Name, Score) VALUES (name, score) Using cmd As New SqlCommand(sql, conn) cmd.Parameters.AddWithValue(name, 张三) cmd.Parameters.AddWithValue(score, 85) Dim rows As Integer cmd.ExecuteNonQuery() MessageBox.Show(成功插入 rows 行) End Using End Using同样的模式可以套用到更新和删除区别只在SQL语句不同。这里我强调一个实战上很有用的细节ExecuteNonQuery返回的整数非常重要如果返回0说明你执行的SQL虽然语法正确但实际上没有影响任何行——比如更新的条件不对或者删除的目标记录根本不存在。利用好这个返回值可以在界面上给用户更明确的反馈。4.3 参数化查询这条防线不能省写数据库操作时最不能图省事的地方就是把变量直接拼接到SQL字符串里。 危险写法 Dim sql As String SELECT * FROM Student WHERE Name txtName.Text 一旦用户在文本框里输入 OR 11这个SQL就变成了SELECT * FROM Student WHERE Name OR 11所有记录都会被查出来。如果后面接的是删除语句后果更严重。参数化查询就是为了堵住这个洞把SQL语句的结构和数据分开。你传给数据库的是一个SQL模板参数值作为独立的数据类型传给数据库引擎引擎会专门处理其中的特殊字符不会把它们解析成SQL语法。用参数化之后上面那条危险的查询应该写成Dim sql As String SELECT * FROM Student WHERE Name name cmd.Parameters.AddWithValue(name, txtName.Text)这是一条必须成为肌肉记忆的原则任何涉及用户输入的SQL操作一律参数化不要拼接。4.4 事务多条语句要么全成要么全不干在实际业务里很多操作是一组操作必须同时成功或同时失败的。比如转账操作A扣钱、B加钱这两步必须都成功如果A扣了钱B没加上就是重大事故。VB.NET中执行事务的标准步骤Using conn As New SqlConnection(connStr) conn.Open() Dim tran As SqlTransaction conn.BeginTransaction() Try Using cmd As New SqlCommand(UPDATE Account SET Balance Balance - 100 WHERE UserId from, conn, tran) cmd.Parameters.AddWithValue(from, A) cmd.ExecuteNonQuery() End Using Using cmd As New SqlCommand(UPDATE Account SET Balance Balance 100 WHERE UserId to, conn, tran) cmd.Parameters.AddWithValue(to, B) cmd.ExecuteNonQuery() End Using tran.Commit() Catch ex As Exception tran.Rollback() MessageBox.Show(操作失败已回滚 ex.Message) End Try End Using事务的关键点在于创建命令时要把事务对象作为第三参数传进去命令才会成为该事务的一部分。如果不传命令自动独立提交事务控制就失去了意义。这个细节非常容易错一旦漏掉表面上代码没报错但事务压根没起作用。5. 数据绑定与界面交互5.1 为什么直接用DataGridView绑定WinForms里面显示数据库数据最常用的控件就是DataGridView。上文中那段示例代码里DataGridView1.DataSource dt这一行看起来简单背后做了很多事DataGridView会读取DataTable的列结构自动生成表头列的排序、宽度调整是默认开启的数据修改后可以通过DataGridView1.Rows(r).Cells(c).Value直接读取单元格内容提交更新。在实际项目中界面和数据库交互最常见的方式是窗体加载时填充DataTable到内存用户操作DataGridView进行浏览与编辑点击保存按钮后遍历DataGridView修改过的行逐条UPDATE到数据库。这种方式的好处是反馈及时用户操作的是内存数据界面上改错了不会立刻影响到数据库直到你主动执行保存。缺点是要自己处理并发两个用户同时改同一行怎么办不过对于大多数内部管理系统这个复杂度是可接受的。5.2 BindingSource一个值得用的中间层如果表数据量大或者你需要做筛选、排序、逐条导航建议在DataGridView和DataTable之间加一个BindingSource。Dim bs As New BindingSource() bs.DataSource dt DataGridView1.DataSource bs有了BindingSource之后你可以调用bs.Filter Score 60按条件过滤bs.Sort Name DESC排序甚至用bs.Current定位当前行。这类操作在只绑定DataTable时做起来比较别扭。举一个我实际开发中的场景一个订单管理窗体上方有未完成已完成两个筛选按钮下方是订单列表。我做的就是在两个按钮的Click里切换bs.Filter完全不用重新查数据库。这对体验提升非常明显。5.3 界面多控件联动除了表格展示数据库操作还常涉及主从表联动。比如左边一个ListBox显示客户列表点击某个客户右边DataGridView显示该客户的订单。实现思路很简单左侧绑定客户表的同时在SelectedIndexChanged事件里取出当前客户的Id再查一次订单表重新填充右侧DataGridView。Private Sub ListBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ListBox1.SelectedIndexChanged If ListBox1.SelectedItem Is Nothing Then Return Dim customerId As Integer CInt(ListBox1.SelectedValue) Dim dtOrders As DataTable GetOrdersByCustomer(customerId) DataGridView1.DataSource dtOrders End Sub注意点ListBox的DisplayMember显示给用户看的字段和ValueMember用于取值的字段要设置正确否则SelectedValue取不到你想要的值甚至报错。这个坑我见很多新手踩过——界面上明明显示的是客户名称代码里一取SelectedValue返回的却是索引或空值就是因为ValueMember没配对。6. 常见问题排查与避坑手册下面这些是我在实际开发和维护中反复遇到、且网上资料说得不够透的问题整理成了一份速查表希望能帮你少走弯路。6.1 无法连接服务器排查清单现象思路报错在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误检查网络是否通ping、防火墙是否开放端口、SQL Server服务是否启动报错用户 xx 登录失败检查账号密码、SQL Server是否启用混合认证模式报错证书链是由不受信任的机构颁发的连接串加TrustServerCertificateTrue或换用加密方式报错连接超时时间已到先缩短Connect Timeout3快速定位检查是否有网络隔离有一次我带教一个新人他死活连不上本地数据库我远程一看连接串写的是Data Source.\SQLEXPRESS但他的实例名是安装时自定义的根本不叫SQLEXPRESS。这类问题归根结底一句话先搞清楚你装的SQL Server实例到底叫什么名字。最简单的办法是在本机打开SQL Server Management StudioSSMS看连接对话框里的服务器名下拉框显示的是什么直接把那个名字抄到连接串里至少不会因为实例名不对而卡住。6.2 参数化查询中的常见异常参数化查询里最容易碰到的是SqlException: 过程需要参数 xxx但未提供该参数。这通常是因为SQL文本里用到了xxx但代码里没有用AddWithValue给它赋值或者变量名拼错了。另一种常见情况是用了Parameters.AddWithValue(id, Nothing)而SQL要求该参数不能为空就会报不能将值NULL插入列的错误。实战技巧如果某个参数可能为空SQL条件要写成(字段 value OR value IS NULL)这种形式或者干脆用条件拼接SQL。参数化解决的是注入问题不是参数不确定问题。还有一类迷惑性的问题AddWithValue传进去一个字符串00123但数据库字段是int类型SQL Server会尝试转换如果转不了就会报转换失败。建议养成习惯从界面拿到的是字符串往参数里放之前先明确转换目标类型再赋值。6.3 中文乱码问题VB.NET操作数据库遇到乱码通常出现在对Access/SQLite这类文件型数据库的操作中或者是读取老系统编码不一致的数据时。SQL Server的中文乱码问题极有可能是创建表时没有指定正确的排序规则或者N前缀缺失。往SQL Server写入中文时标准做法是使用N中文形式Unicode字符串在参数化查询中则无需操心但如果你用拼接SQL写中文就必须写成N张三否则某些排序规则下中文有可能写入成?. 这个细节在维护老系统时经常踩中。处理思路是先确认数据库排序规则如Chinese_PRC_CI_AS再确认代码文件编码保存为UTF-8带签名避免源代码里的中文字符串被编译成乱码。6.4 程序偶尔卡死 / 响应慢如果程序运行一段时间后开始变慢甚至卡死常见原因列举如下连接未释放连接池耗尽。所有操作没加Using连接对象都占着后续请求排队等待表现为一段时间后有大量超时异常。修复方法很简单全面检查给所有连接、命令、适配器、DataReader加Using。在UI线程执行耗时查询如果数据库查询要2~3秒UI线程就会被阻塞窗口看起来没响应且无法拖动。改进方法是用Async或后台线程执行数据库操作完成后回到UI线程更新界面。DataReader未关闭而重入如果同一个连接上还有未关闭的DataReader再执行其他命令时会报已有打开的与此连接关联的DataReader必须先将它关闭。这也是老代码里非常高频的报错。排查时看一眼所有读取操作是否有嵌套读取的情况以及是否每层都用了Using。6.5 数据明明修改了但显示没变化这类问题一般不是数据库的问题而是数据绑定没有刷新。我的经验是不管用哪种方式更新了数据库及时刷新界面数据源总没错。常见做法就是重新Fill一次DataTable再把DataSource重新赋一遍。如果你用的是BindingSource还要调用一下bs.ResetBindings(False)让界面强制重绘。还有一种隐蔽情况DataTable里如果有主键约束当你重新Fill时使用的主键变化导致Fill抛ConstraintException。处理办法是在Fill之前dt.Clear()或者把主键约束清掉。6.6 部署到目标机器后连接失败开发环境连得好好的部署到别的电脑就连不上这种情况的排查顺序是先看目标机器能不能ping通数据库服务器。再看目标机器上有没有装对应的数据库驱动特别是MySQL、Access OLEDB。确认连接串里写的是服务器地址不是localhost——部署后localhost指向的是目标机器自己如果数据库不在本机必然失败。检查防火墙端口是否放行SQL Server默认1433MySQL默认3306。另外如果目标机器是64位系统而你的程序编译成了AnyCPU但引用的某个驱动只有32位版会出现未能加载文件或程序集的报错。这时要么把程序编译目标改成x86要么换64位驱动。7. 进阶技巧与老项目的实用心得7.1 对老项目进行数据库层改造的切入点如果你的职责是维护一个多年没动过的VB.NET老系统代码量庞大、逻辑混乱我建议从三个方向逐步渗透规范连接管理把所有数据库操作从窗体事件里抽到独立的数据库访问类比如DbHelper统一封装连接字符串和增删改查方法。这是性价比最高的一步——很多现成的Helper类代码可以直接参考改造几小时就能完成之后所有新增窗体都不需要再复制粘贴连接代码。全面参数化全局搜索代码里出现SELECT 、INSERT 等关键字且配合拼接变量的地方逐一改成参数化。这一步急不得一条一条改改完跑一遍相关功能回归测试。增加日志在数据库操作层插入日志记录比如记录执行了哪条SQL、耗时多少毫秒。老系统最头疼的是出了问题无法定位有了日志之后用户报错时直接拉日志看具体SQL基本能还原现场。7.2 性能优化从能跑到跑得快数据量一旦上来肉眼可见的优化手段是按照以下顺序尝试减少往返查询能用一条SQL查出来的数据就不要拆成N条查询减少网络/数据库上下文切换开销。使用索引给 SELECT 频繁用到的 WHERE 字段、JOIN 字段加索引。需要注意的是对经常变动的表索引不是越多越好索引写维护成本高。分页加载大表查询不要在加载窗体时就全部取出用OFFSET ... FETCH NEXT或ROW_NUMBER()做分页仅加载当前页数据到内存。用ExecuteReader代替Fill大结果集如果只是遍历算个汇总没必要把所有行加载到DataTable里使用DataReader边读边算更节省内存。有一个少被人提及但非常实用的技巧把多个具备相同连接串的查询合并到同一个连接生命周期内利用连接池复用避免频繁Open/Close。特别是在WinForms窗体中用户连续点击查询按钮时如果每次都经历了完整的Open/Close周期开销是日积月累的。7.3 关于数据库操作的一点个人体会做这行时间长了我越来越觉得数据库操作的难点从来不在语法上而在习惯和工程素养上。语法文档翻一翻就会但每条连接都要释放每个用户输入都要参数化每类操作都要有日志这些习惯是需要靠真实项目重复练习才能刻进脑海的。如果让我给一个学习路径的建议那就是别急着上一堆框架。先拿一个空白窗体用最原始的方式把增删改查全部手写一遍再把一段包含事务的批量更新跑通最后把界面控件绑定和数据联动做好。这一整套下来你对VB.NET连接数据库这件事才算真正入门。之后再去接触ORM、数据服务层设计都会顺手得多。最后再分享一个小技巧写连接字符串的时候可以先在代码里写上MsgBox(connStr)把它弹出来看一眼确认没有因为拼接把引号或分号打错。看似多此一举但每次排查连接问题都省下不少冤枉时间。这个习惯我保留到现在遇到疑难连接错误仍然先用最笨的办法验证最基础的环节。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑