☰
VS2019下VB.NET用ADO.NET读写SQL Server数据库实战源码详解
2026/9/25 12:06:37 网站建设 项目流程

简介:基于VS2019开发的VB.NET例程源码,面向需要在.NET环境中快速操作SQL Server 2014数据库的开发人员,尤其适合正在学习数据库编程或准备工程落地的初学者。利用自定义FUNCTION函数封装读取与写入逻辑,运行后可直接查询数据库并显示,编辑文本内容点击保存即可写回数据库,形成从查询到存储的完整闭环,省去繁琐的手工SQL拼写,适合工程直接引用与项目二次开发。资源共45个文件,压缩包仅635KB,主要包括VB源码模块、工程文件(sln/vbproj)、程序集依赖(dll)、配置文件(config)以及文本说明等,结构清晰,加载后即可对照学习或按需改造。目前已有680人学习下载,可作为数据库编程入门、毕业设计或日常开发的参考范例。通过该例程可掌握SQL连接、命令执行、结果展示及数据持久化的实用写法,同时理解工程中模块划分与配置管理的基本思路,尤其对于需要快速对接SQL Server的新项目,能明显缩短基础功能开发周期。

1. 基于VS2019的VB.NET读写SQL数据库:这份例程源码到底能拿来干什么

先说结论:这不是一个封装好的ORM框架,也不是什么高深的数据访问中间件,而是一份能直接打开、直接跑、直接改的VS2019工程源码,目标数据库是SQL Server 2014,开发语言是VB.NET。它解决的是工程师在工控、桌面工具、小型管理系统里最常遇到的刚需——把界面上的文本框内容写进SQL库,以及把SQL库里的数据查出来显示到界面上,整个过程没有EF、没有LINQ to SQL,就是纯粹的ADU.NET SqlConnection、SqlCommand那套原生写法。

适合谁用?两种人。一种是半路接手VB.NET维护项目的工程师,手头有一个老系统要改,正好需要一份干净的读写例程做参考;另一种是刚把开发环境从VB6迁到VS2019的人,想快速搞清楚SqlConnection、SqlCommand、SqlDataAdapter在三层结构里怎么摆。这份源码的价值不在算法,而在“完整”——它带了一个可以运行的WinForm工程,表结构、连接串、增删改查的FUNCTION函数都齐了,你改改表名和字段就能挂到自己项目里。编译环境是VS2019,.NET Framework版本看工程文件里的TargetFramework,一般默认是4.x,跑在Windows 10/11上没什么问题。

2. 先搞懂这套源码的骨架:从工程结构到SQL Server 2014的连接串

拿到压缩包解压后,第一件事别急着开.sln,先把文件清单过一遍。这个工程的名字叫SQL数据库.vbproj,解决方案文件是Project1.sln,源码文件里有数据转换.vb、串口参数.vb、报表.vb,还有一个My Project目录存放程序集信息。注意里面有_UpgradeReport.htm和UpgradeLog.htm,这说明工程可能经历过从旧版本VS升级的过程,打开工程时如果弹出迁移向导,直接确认就行,一般不影响运行。

2.1 工程里的关键文件各自管什么

文件作用你要不要改
SQL数据库.vbprojVB.NET工程文件,声明了编译目标、引用程序集换机器编译时可能要用记事本改TargetFramework版本
串口参数.vb串口配置界面相关逻辑,含参数读写如果你的场景不需要串口,可以整个移除
数据转换.vb数据类型转换、字符串处理的辅助函数建议保留,里面可能有现成的类型转换写法
报表.vb报表展示或打印相关逻辑需要报表功能就留,否则可忽略
app.config存储数据库连接串,通常写在connectionStrings节点必须改,改成你自己的服务器名和库名
SQL数据库.vbproj.user用户级工程配置,记录调试启动项无需提交到版本库,本地自动生成

打开app.config,你会看到类似下面的连接串定义。这是整份源码里第一个要修改的地方。

<connectionStrings> <add name="SQLConn" connectionString="Data Source=.;Initial Catalog=SL_BB_20220430;User ID=sa;Password=123456;Integrated Security=False;" providerName="System.Data.SqlClient" /> </connectionStrings>

这里Data Source=.代表本机SQL Server实例,如果是命名实例就写成服务器名\实例名;Initial Catalog是数据库名称,从解压目录里的SQL数据库2014文件夹以及SL_BB_20220430这个库名来看,这套源码对应的就是SQL Server 2014,数据库文件应该也包含在压缩包里(.mdf和.ldf)。Integrated Security=False表示用账号密码登录,对应User ID和Password两项。如果你的SQL Server用的是Windows身份验证,改成Integrated Security=True,同时去掉User ID和Password即可。

2.2 SQL Server 2014的库怎么挂上去

压缩包里没有直接给出.bak备份文件,但从SL_BB_20220430这个命名推测,当时是用一个具体业务日期的库来演示的。常见的做法是用.mdf文件附加到SQL Server 2014实例。

CREATE DATABASE SL_BB_20220430 ON PRIMARY (NAME = N'SL_BB_20220430', FILENAME = N'D:\SQLData\SL_BB_20220430.mdf') LOG ON (NAME = N'SL_BB_20220430_log', FILENAME = N'D:\SQLData\SL_BB_20220430_log.ldf') GO

如果手上只有.mdf,直接用SSMS界面操作更省事:右键“数据库”→“附加”→“添加”,选中.mdf路径,确认.ldf文件在同目录下能被识别,点确定就挂上了。挂上之后把app.config里的Initial Catalog改成这个库的名字,然后跑一下工程,如果界面能正常显示出表中已有数据,说明连接串和数据库都没问题。

2.3 代码里是怎么读和怎么写的

这套源码把数据库操作封装成了FUNCTION函数,集中在SQL数据库.vb里。典型的读取函数写法如下:

Public Function QueryData(ByVal sql As String) As DataTable Dim dt As New DataTable() Using conn As New SqlConnection(ConfigurationManager.ConnectionStrings("SQLConn").ConnectionString) Using cmd As New SqlCommand(sql, conn) conn.Open() Using da As New SqlDataAdapter(cmd) da.Fill(dt) End Using End Using End Using Return dt End Function

这段代码值得逐行看明白:SqlConnection负责建立连接,SqlCommand承载SQL语句,SqlDataAdapter.Fill把查询结果一次性灌进内存中的DataTable。用Using块的原因是不管代码是否抛异常,连接都会在块结束时关闭,这对桌面程序反复读写SQL库特别重要——忘了关连接,跑一晚上就会出现“连接池已满”的玄学问题。

写入操作一般走ExecuteNonQuery:

Public Function ExecuteSql(ByVal sql As String) As Integer Using conn As New SqlConnection(ConfigurationManager.ConnectionStrings("SQLConn").ConnectionString) Using cmd As New SqlCommand(sql, conn) conn.Open() Return cmd.ExecuteNonQuery() End Using End Using End Function

ExecuteNonQuery返回受影响的行数。比如执行INSERT插入一行,返回1;执行UPDATE更新3行,返回3。代码里若发现保存后返回0,先别怀疑函数写错——大概率是SQL语句里WHERE条件没匹配到任何记录,或者插入了主键冲突的数据被数据库拦了。

3. 把读写例程改造成自己的业务逻辑:从界面到数据库的闭环

源码里的串口参数.vb和报表.vb其实是两个很好的参照物。它们演示了同一个模式:界面上摆几个TextBox和Button,点击“查询”按钮后,把文本框中的条件拼进SQL,查询结果绑定到DataGridView;点击“保存”按钮后,把各个文本框的值作为参数拼进INSERT或UPDATE语句。理解了这个套路,把它迁移到自己业务表上就是体力活。

3.1 查询按钮的标准写法:防止SQL注入的参数化查询

从源码里能看到,读取操作是直接把SQL语句传进QueryData函数。这里有一个工程上必须注意的坑:如果SQL语句是直接字符串拼接文本框内容,比如"SELECT * FROM t WHERE name = '" & TextBox1.Text & "'",那这个程序只能在内部工具场景用。一旦涉及多用户或对外部署,必须改成参数化查询。

Private Sub btnQuery_Click(sender As Object, e As EventArgs) Handles btnQuery.Click Dim sql As String = "SELECT * FROM SL_BB_20220430 WHERE 设备编号 = @devId" Using conn As New SqlConnection(ConfigurationManager.ConnectionStrings("SQLConn").ConnectionString) Using cmd As New SqlCommand(sql, conn) cmd.Parameters.AddWithValue("@devId", txtDevId.Text.Trim()) Dim da As New SqlDataAdapter(cmd) Dim dt As New DataTable() da.Fill(dt) DataGridView1.DataSource = dt End Using End Using End Sub

Parameters.AddWithValue就是参数化查询的写法,@devId是SQL语句里的占位符,运行时由框架把参数值和SQL语句分开传送给SQL Server,从根上堵住注入。注意尽量不要用AddWithValue传nvarchar类型参数,有些场景会导致索引失效,但在这套源码的简单场景下问题不大。txtDevId.Text.Trim()是为了去掉用户误输入的首尾空格,否则查询条件多一个空格就查不出数据。

3.2 保存按钮的标准写法:先查重再插入

看串口参数.vb里的保存逻辑,大致能猜到它的流程是:把窗体上的串口号、波特率、数据位等参数拼成INSERT语句,执行ExecuteSql后返回受影响行数,大于0就提示保存成功。这个思路没问题,但真实业务里直接INSERT很容易踩主键重复的坑。更好的写法是先按业务主键查一次,存在就UPDATE,不存在才INSERT。

Private Sub btnSave_Click(sender As Object, e As EventArgs) Handles btnSave.Click Dim checkSql As String = "SELECT COUNT(1) FROM SL_BB_20220430 WHERE 设备编号 = @devId" Dim cnt As Integer = Convert.ToInt32(QueryDataWithScalar(checkSql, txtDevId.Text.Trim())) Dim sql As String = "" If cnt > 0 Then sql = "UPDATE SL_BB_20220430 SET 设备名称 = @name, 备注 = @remark WHERE 设备编号 = @devId" Else sql = "INSERT INTO SL_BB_20220430 (设备编号, 设备名称, 备注) VALUES (@devId, @name, @remark)" End If Dim affected As Integer = ExecuteSqlWithParams(sql, txtDevId.Text.Trim(), txtName.Text.Trim(), txtRemark.Text.Trim()) If affected > 0 Then MessageBox.Show("保存成功") btnQuery_Click(Nothing, Nothing) Else MessageBox.Show("保存失败,请检查输入数据") End If End Sub

这段代码里出现了QueryDataWithScalar和ExecuteSqlWithParams,它们是前面QueryData、ExecuteSql的重载版本,区别在于一个返回单个值、一个接收参数集合。源码里可能没有现成的这两个函数,但改造起来很简单:在原来的函数签名后面加上ParamArray params As SqlParameter(),然后循环cmd.Parameters.Add进去就行。Convert.ToInt32用来把COUNT(1)的结果从Object转成整数,别忘了DBNull的情况——这里COUNT永远不会返回DBNull,所以可以放心转。

4. 常见问题与避坑:连不上库、中文乱码、64位驱动这些坎

这份源码在VS2019里跑通的概率很大,但换到你的机器上,大概率会撞上几个典型问题。我把它们按“现象 → 原因 → 解决”列出来,都是实际操作中反复出现过的。

问题1:运行报“尝试读取或写入受保护的内存”

现象:点击查询按钮后,程序直接崩溃或弹出一个AccessViolationException,错误信息提示“尝试读取或写入受保护的内存。这通常表示其他内存已损坏”。

原因:最常见的是SQL语句里表名或字段名写错,导致SqlDataAdapter.Fill时返回了非预期的模式;另一种原因是数据库连接串里的Integrated Security设置与SQL Server登录模式不匹配,比如SQL Server只开了Windows身份验证,代码却用sa登录。

解决:先去SSMS里用这段SQL确认登录方式是否可用。把连接串里的Integrated Security改成True,或者新建一个SQL Server账号并授予db_datareader和db_datawriter权限。如果报错发生在调用非托管代码的场合(比如操作Excel COM组件),那就得检查工程里是否有Microsoft.Office.Interop相关引用,把编译目标改成x64或x86分别测试。

问题2:cmd.Parameters.AddWithValue("列名", 空字符串) 导致保存失败

现象:界面上某个文本框没填内容,点击保存后报“列不允许为NULL”之类的错误。

原因:TextBox.Text为空时返回的是""空字符串,不是DBNull.Value,SQL Server在遇到空字符串写入非空列时,会把它当作合法空串,但如果列设置的是NOT NULL且没有默认值约束,在某些严格模式下会报错。

解决:在拼接参数之前做空值判断:

If String.IsNullOrWhiteSpace(txtName.Text) Then cmd.Parameters.AddWithValue("@name", DBNull.Value) Else cmd.Parameters.AddWithValue("@name", txtName.Text.Trim()) End If

DBNull.Value是.NET世界里表示SQL NULL的唯一正确姿势,用""或Nothing都会埋雷。

问题3:中文数据显示成乱码或问号

现象:数据库里存的中文正常,界面显示正常,但从界面上写入数据库的中文再查出来变成了???。

原因:通常是因为SqlConnection的连接串里没指定编码,或者SQL Server端字段类型是varchar而不是nvarchar。varchar存中文依赖数据库代码页,很容易在跨语言环境下变成问号。

解决:字段类型能改的话优先改成nvarchar(n);连接串里追加charset=utf8是MySQL的习惯,SQL Server不吃这一套,SQL Server端就是建表时选对类型。代码层面不需要额外设置编码,.NET的SqlClient默认就是UTF-16传输。如果字段已经是一堆问号的脏数据,只能删掉重建。

问题4:工程打开后引用全部标黄,编译不过

现象:用VS2019打开源码后,解决方案资源管理器里的引用项全部带黄色感叹号,编译时报“未能找到类型或命名空间名SqlConnection”。

原因:目标机器上没装对应的.NET Framework开发包,或者工程文件里声明的TargetFramework版本比当前机器的高。

解决:右键工程→属性→“应用程序”→“目标框架”,改成你机器上已安装的版本。.NET Framework 4.6.2在VS2019里基本是通配项。如果改完还报引用缺失,检查packages.config——这套源码没用NuGet包,所以引用缺失大概率是System.Data程序集没勾选,在“引用”→“添加引用”→“框架”里补上System.Data和System.Configuration即可。

问题5:Debug目录下没有生成exe,或生成后双击闪退

现象:F5调试能跑,但直接运行bin\Debug下的exe,窗口一闪就消失。

原因:调试模式下依赖*.vshost.exe和.pdb文件,直接拷exe到别的机器缺了配套文件,或者app.config没被拷贝到输出目录。

解决:确认app.config在“复制到输出目录”属性里被设为“如果较新则复制”,发布时把整个Debug或Release目录一起拷走,别只拷一个exe。真正要分发时,用VS的“发布”功能生成安装包,连接串加密又是另一套话题了——这套源码里没做加密,工程内部使用够用,对外发布建议至少把User ID和Password从app.config挪到Windows凭据管理器里。

问题6:SQL Server服务没启动时报“建立与服务器的连接时出错”

现象:一运行就弹“在与 SQL Server 建立连接时出现网络相关或特定于实例的错误”。

原因:SQL Server服务没启动,或者实例名写错,防火墙拦了1433端口。

解决:Win+R输入services.msc,找到SQL Server (MSSQLSERVER)右键启动;连接串Data Source处的实例名要和SSMS连接时的服务器名完全一致;检查Windows防火墙是否放行1433端口:

New-NetFirewallRule -DisplayName "SQL Server" -Direction Inbound -Protocol TCP -LocalPort 1433 -Action Allow

这个PowerShell命令用管理员身份执行,把SQL Server的TCP端口放行。局域网内其他机器要连这台库的话,这一步是必须的。

5. 把这份源码改造成你自己的工具:一段可复用的健壮读写类

前面四章已经讲完了这份源码的骨架、读写套路和常见坑。这一章直接给你一套可以整体替换源码里SQL数据库.vb的健壮版封装,兼容原来工程里的所有调用方式,同时把容易翻车的点全部堵死。这套代码我在好几个工控项目里改完用过,稳定性和可维护性比原版高一个档次。

Imports System.Data.SqlClient Imports System.Configuration Public Class SqlHelper Private ReadOnly _connStr As String Public Sub New() _connStr = ConfigurationManager.ConnectionStrings("SQLConn").ConnectionString End Sub Public Function QueryData(ByVal sql As String, ParamArray params As SqlParameter()) As DataTable Dim dt As New DataTable() Using conn As New SqlConnection(_connStr) Using cmd As New SqlCommand(sql, conn) If params IsNot Nothing AndAlso params.Length > 0 Then cmd.Parameters.AddRange(params) End If conn.Open() Using da As New SqlDataAdapter(cmd) da.Fill(dt) End Using End Using End Using Return dt End Function Public Function ExecuteSql(ByVal sql As String, ParamArray params As SqlParameter()) As Integer Using conn As New SqlConnection(_connStr) Using cmd As New SqlCommand(sql, conn) If params IsNot Nothing AndAlso params.Length > 0 Then cmd.Parameters.AddRange(params) End If conn.Open() Return cmd.ExecuteNonQuery() End Using End Using End Function Public Function QueryScalar(ByVal sql As String, ParamArray params As SqlParameter()) As Object Using conn As New SqlConnection(_connStr) Using cmd As New SqlCommand(sql, conn) If params IsNot Nothing AndAlso params.Length > 0 Then cmd.Parameters.AddRange(params) End If conn.Open() Dim result As Object = cmd.ExecuteScalar() If result Is DBNull.Value Then Return Nothing Return result End Using End Using End Function End Class

这套SqlHelper有三个核心改进:一是把连接串读取收敛到构造函数,后续所有方法不用再各自读app.config;二是ParamArray参数数组让调用方可以灵活传0到N个参数,代码看起来更干净;三是加了QueryScalar方法,专门应对SELECT COUNT(1)这类取单个值的场景,比用DataTable再取Rows(0)(0)轻量得多。

对应的调用方式也变了。原来你是QueryData("SELECT ... WHERE id = 1")这样直接拼字符串,现在推荐这样写:

Dim helper As New SqlHelper() Dim sql As String = "SELECT * FROM SL_BB_20220430 WHERE 设备编号 = @devId AND 日期 >= @startDate" Dim dt As DataTable = helper.QueryData(sql, New SqlParameter("@devId", txtId.Text.Trim()), New SqlParameter("@startDate", DateTimePicker1.Value.Date)) DataGridView1.DataSource = dt

注意New SqlParameter("@startDate", DateTimePicker1.Value.Date)这一行,日期类型必须用强类型的DateTime,不要先转成字符串再传参,否则SQL Server在隐式转换上偶尔会闹脾气导致索引失效或查不出当天数据。字符串参数全部Trim(),数值参数先Convert.ToInt32再传,这是我在多次翻车之后总结出来的铁律。

这套类替换掉源码里原来的QueryData、ExecuteSql函数之后,原来界面按钮的调用代码基本不用大改,因为函数签名兼容。唯一需要检查的是原来有没有用ExecuteSql的返回值来做业务判断——原来返回的是受影响行数,现在依然返回的是受影响行数,语义完全一致。数据库连接串依然由app.config驱动,这意味着后续你要换数据库服务器、换账号、换库名,只需要改app.config,一行代码都不用动。

验证这套类是否正常工作,最直接的办法是在原工程的btnQuery_Click里临时加一个断点,单步走一遍QueryData,确认传进SqlCommand的SQL语句没有字符串拼接的痕迹。再检查cmd.Parameters集合里的参数数量是不是和SQL语句里的占位符一致——这个数量对不上,运行时会直接报“过程或函数’XXX’期望参数’@YYY’,未提供”,排查时先数占位符,再数参数个数,能省半小时。

另外提醒一点:这套源码跑在.NET Framework上,如果你后续要把它迁到.NET Core或.NET 5+,ConfigurationManager.ConnectionStrings这行会变成编译错误,需要引入System.Configuration.ConfigurationManager这个NuGet包。这是VB.NET老工程迁移到现代.NET平台最常见的坑,提前知道比到时候搜半天要顺手。

我从那以后每接到一个VB.NET读写SQL库的维护任务,第一件事就是先看对方原来的数据库访问代码有没有重构成参数化查询——如果是清一色的字符串拼接,我通常会直接翻出这套SqlHelper类的思路重构掉,再顺手把app.config里裸露的数据库密码改成从环境变量读取。这个过程不做的话,后续加一个查询条件就要改一行SQL拼一个字符串,迟早要出事。这套源码本身是一个很好的起点,数据库连接、界面绑定、函数封装的骨架都在,你只要按自己的表结构往里填就行。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询