ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

C# Winform连接SQL Server实战:从本地到局域网的增删改查与连接配置

C# Winform连接SQL Server实战:从本地到局域网的增删改查与连接配置

简介:一份面向C# Winform新手的SQL Server数据库操作完整示例,集中演示网络连接与本地连接两种方式下的增删改查实现。压缩包共28个文件,包含C#源码(.cs)、工程与解决方案配置(.sln/.csproj)、数据库查询脚本(.sql)、配置说明(.txt)以及可直接运行的exe程序,整体仅168KB,轻量且便于逐项对照学习。已有2674人学习下载,适合刚接触桌面数据库开发或准备课程设计的读者。通过阅读说明文档与SQL脚本,能快速理解连接字符串配置、ADO.NET常用类用法、数据绑定展示等关键环节;配合源码和可执行程序,可实际运行验证增删改查效果,并在此基础上扩展业务逻辑,尤其适合作为Winform+SQL Server入门参考。

1. C# Winform 连 SQL Server:本地连得上、网络连不上才是真正的第一课

新手拿到这套 C# Winform 增删改查项目,最容易出现的状态不是代码跑不通,而是数据库连接字符串改来改去、报错换个不停。这个资源的价值在于它把本地连接和网络连接两种模式同时给了出来,还附带了建库脚本和查询语句,等于把从零到跑通的最小闭环一次备齐。说白了,增删改查本身不难,难的是环境,而环境问题九成集中在连接链路。这套东西适合三类人:刚学完 C# 语法想碰真实数据库的学生、公司里被迫维护 Winform 老系统的半路出家开发者、以及想快速搭一个内部工具给同事用的非专职程序员。如果你已经能独立写出 SQL 却还没在 Winform 里成功显示过一条记录,那这篇就是写给你看的。

2. 先解决连不连得上:连接字符串的四种形态与配置细节

2.1 把 App.config 里的连接字符串看懂而不是抄走

这套代码里所有数据库访问都通过一个统一入口,连接字符串单独放在 App.config 的 connectionStrings 节点下。我见过太多人直接复制别人机器上的配置,结果拿回家秒报错,原因就是连接字符串里的每一个参数都跟运行环境强绑定。先把四种形态列出来,后面再讲怎么改。

连接形态连接字符串示例适用场景
本地 Windows 身份验证Data Source=.;Initial Catalog=DemoDB;Integrated Security=True本机开发调试
本地 SQL 身份验证Data Source=.;Initial Catalog=DemoDB;User ID=sa;Password=123456本机模拟服务器环境
局域网 SQL 身份验证Data Source=192.168.1.100;Initial Catalog=DemoDB;User ID=sa;Password=123456同事电脑连你的机器
远程服务器连接Data Source=114.x.x.x,1433;Initial Catalog=DemoDB;User ID=sa;Password=xxx部署到云服务器后

Data Source 里的句点代表本机,换成 IP 就是走网络;Initial Catalog 指定数据库名;Integrated Security=True 表示用当前 Windows 账户登录,而 User ID 和 Password 走的是 SQL Server 自己的账号体系。很多人搞不懂为什么本机能连、别人连不上,就是因为第一台机器用的 Windows 身份验证,而网络连接必须要 SQL 身份验证加账号密码。这套资源里本地和网络两套配置都给全了,对照着改就能少踩一半坑。

2.2 启动时读取连接字符串

配置写在 App.config 里,读取则在程序启动入口完成。项目里一般会有个公共类专门管数据库连接,比如 DbHelper。启动时先确保配置被正确读取,再谈后续操作。

// App.config 里的关键节点 <connectionStrings> <add name="SqlConn" connectionString="Data Source=.;Initial Catalog=DemoDB;Integrated Security=True" providerName="System.Data.SqlClient" /> </connectionStrings>
// Program.cs 里启动时读取 string connStr = ConfigurationManager.ConnectionStrings["SqlConn"].ConnectionString; if (string.IsNullOrEmpty(connStr)) { MessageBox.Show("数据库连接字符串未配置,请检查 App.config"); Application.Exit(); }

读取逻辑写在 Main 方法最前面,连不上就直接弹窗提示而不是进主界面后再慢慢报错,这对新手排障友好得多。ConfigurationManager 是 System.Configuration 命名空间下的类,需要项目引用 System.Configuration.dll,这一步容易漏。我一般会顺手加一个日志输出,把当前读到的连接字符串打印出来,但注意别把密码打到生产日志里。连接字符串里如果密码是明文,至少保证这个配置文件不会进版本库。

2.3 数据访问层的封装思路

这套资源里的增删改查不是每个窗体里各写一套,而是把连接、执行、查询统一封装成方法。新手最容易犯的错是每个按钮里 new SqlConnection 然后散布一堆重复代码,改一个连接方式就得改几十处。封装之后至少有三个好处:连接字符串只在配置里改一次、连接对象统一 using 管理、SQL 执行结果统一返回 DataTable 或者影响行数。

public static class DbHelper { // 外部传入连接字符串,避免类内部硬编码 public static DataTable ExecuteQuery(string sql, string connStr, params SqlParameter[] parameters) { using (SqlConnection conn = new SqlConnection(connStr)) { using (SqlCommand cmd = new SqlCommand(sql, conn)) { if (parameters != null) cmd.Parameters.AddRange(parameters); SqlDataAdapter adapter = new SqlDataAdapter(cmd); DataTable dt = new DataTable(); try { conn.Open(); adapter.Fill(dt); } catch (SqlException ex) { throw new Exception("SQL 执行失败:" + ex.Message); } return dt; } } } // 执行增删改,返回受影响行数 public static int ExecuteNonQuery(string sql, string connStr, params SqlParameter[] parameters) { using (SqlConnection conn = new SqlConnection(connStr)) { using (SqlCommand cmd = new SqlCommand(sql, conn)) { if (parameters != null) cmd.Parameters.AddRange(parameters); try { conn.Open(); return cmd.ExecuteNonQuery(); } catch (SqlException ex) { throw new Exception("SQL 执行失败:" + ex.Message); } } } } }

代码逻辑不复杂:查询走 SqlDataAdapter 填 DataTable,增删改走 ExecuteNonQuery 拿影响行数。两个方法都接收连接字符串作为参数,而不是自己硬编码,这是为了支持后面切换本地和网络模式。SqlParameter 数组用 params 传参,调用方可以传也可以不传。注意 using 块保证连接即使异常也会释放,这是新手最容易忽略的,连接没释放会导致连接池爆掉,后面表现为“偶尔能连偶尔连不上”,非常难受。

3. 增删改查的完整落法:参数化查询与 DataGridView 绑定

3.1 用户表设计与建表脚本

这套资源里附带的 SQL 脚本包含建库、建表和基础数据。先看建表语句,别急着运行,逐行读一遍再执行。

-- 建库 IF DB_ID('DemoDB') IS NULL CREATE DATABASE DemoDB; GO USE DemoDB; GO -- 建用户表 IF OBJECT_ID('Users', 'U') IS NULL BEGIN CREATE TABLE Users ( Id INT IDENTITY(1,1) PRIMARY KEY, UserName NVARCHAR(50) NOT NULL, Age INT NULL, Email NVARCHAR(100) NULL, CreateTime DATETIME DEFAULT GETDATE() ); END GO -- 初始化两条数据,便于后面验证查询 INSERT INTO Users (UserName, Age, Email) VALUES ('张三', 25, 'zhangsan@example.com'), ('李四', 30, 'lisi@example.com'); GO

IDENTITY(1,1) 自增主键不需要手动赋值,增删改查里 Insert 语句不写 Id 列就行。UserName 用 NVARCHAR 是因为要考虑中文,VARCHAR 在某些字符集下会乱码。CreateTime 给了默认值,插入时不传也能自动生成。这个表结构简单到不能再简单,但对新手来说恰好合适:字段类型涵盖了字符串、整数、日期三种最常用类型,后续扩展也方便。

3.2 增删改四个方法的完整模板

有了 DbHelper 之后,具体窗体内的操作就变得很规矩:拼 SQL、传参数、执行、刷新列表。很多入门教程喜欢拼字符串,比如 string sql = "INSERT INTO Users VALUES('" + textBox.Text + "'"),这种写法在遇到英文单引号时会直接炸,而且存在注入风险。这套代码用的参数化,是应该从一开始就养成的习惯。

// 新增 private void btnAdd_Click(object sender, EventArgs e) { string sql = "INSERT INTO Users (UserName, Age, Email) VALUES (@Name, @Age, @Email)"; SqlParameter[] paras = { new SqlParameter("@Name", SqlDbType.NVarChar) { Value = txtName.Text.Trim() }, new SqlParameter("@Age", SqlDbType.Int) { Value = Convert.ToInt32(txtAge.Text.Trim()) }, new SqlParameter("@Email", SqlDbType.NVarChar) { Value = txtEmail.Text.Trim() } }; int rows = DbHelper.ExecuteNonQuery(sql, connStr, paras); if (rows > 0) { MessageBox.Show("添加成功"); LoadData(); } }
// 修改:按选中行的 Id 更新 private void btnUpdate_Click(object sender, EventArgs e) { if (dataGridView1.CurrentRow == null) return; int id = Convert.ToInt32(dataGridView1.CurrentRow.Cells["Id"].Value); string sql = "UPDATE Users SET UserName=@Name, Age=@Age, Email=@Email WHERE Id=@Id"; SqlParameter[] paras = { new SqlParameter("@Name", SqlDbType.NVarChar) { Value = txtName.Text.Trim() }, new SqlParameter("@Age", SqlDbType.Int) { Value = Convert.ToInt32(txtAge.Text.Trim()) }, new SqlParameter("@Email", SqlDbType.NVarChar) { Value = txtEmail.Text.Trim() }, new SqlParameter("@Id", SqlDbType.Int) { Value = id } }; DbHelper.ExecuteNonQuery(sql, connStr, paras); MessageBox.Show("修改成功"); LoadData(); }
// 删除:先取选中行的主键 private void btnDelete_Click(object sender, EventArgs e) { if (dataGridView1.CurrentRow == null) return; int id = Convert.ToInt32(dataGridView1.CurrentRow.Cells["Id"].Value); if (MessageBox.Show("确认删除该记录?", "提示", MessageBoxButtons.YesNo) == DialogResult.Yes) { string sql = "DELETE FROM Users WHERE Id=@Id"; SqlParameter[] paras = { new SqlParameter("@Id", SqlDbType.Int) { Value = id } }; DbHelper.ExecuteNonQuery(sql, connStr, paras); LoadData(); } }
// 查询全部并绑定到 DataGridView private void LoadData() { string sql = "SELECT Id, UserName, Age, Email, CreateTime FROM Users ORDER BY Id DESC"; DataTable dt = DbHelper.ExecuteQuery(sql, connStr); dataGridView1.DataSource = dt; }

四个操作从代码形态上看差异很小,区别只在 SQL 语句本身。新增和修改都用了 @ 占位符,参数类型和长度尽量跟数据库字段对齐,NVarChar 类型在中文环境下是必须的。Convert.ToInt32 解析文本框内容时如果输入非数字会抛异常,我通常会加 int.TryParse 做校验,不然用户手滑输入字母,程序直接崩掉。删除前弹确认框是个好习惯,免得误删后找不到后悔药。每次增删改成功后都调用 LoadData 刷新列表,这样界面上看到的永远是最新数据。

3.3 查询条件拼接的正确姿势

列表查全部简单,但实际场景里肯定要按条件筛。这套资源里应该也有一个查询按钮,我补一个常见的模糊查询写法,注意看参数怎么配合 LIKE。

private void btnSearch_Click(object sender, EventArgs e) { string sql = "SELECT Id, UserName, Age, Email, CreateTime FROM Users WHERE 1=1"; List<SqlParameter> paras = new List<SqlParameter>(); if (!string.IsNullOrWhiteSpace(txtSearchName.Text)) { sql += " AND UserName LIKE @Name"; paras.Add(new SqlParameter("@Name", SqlDbType.NVarChar) { Value = "%" + txtSearchName.Text.Trim() + "%" }); } if (!string.IsNullOrWhiteSpace(txtMinAge.Text)) { sql += " AND Age >= @MinAge"; paras.Add(new SqlParameter("@MinAge", SqlDbType.Int) { Value = Convert.ToInt32(txtMinAge.Text) }); } DataTable dt = DbHelper.ExecuteQuery(sql, connStr, paras.ToArray()); dataGridView1.DataSource = dt; }

WHERE 1=1 不是凑数,它的作用是让后面每个条件都能直接用 AND 拼接,不用费劲判断“这个条件是不是第一个”。这是新手写动态 SQL 最容易出错的地方:第一个条件到底用 WHERE 还是 AND,拼接边界特别容易出问题。参数列表用 List 动态收集,最后转数组传给 DbHelper。LIKE 通配符写在参数值里而不是 SQL 里,不要写成 LIKE '@Name%',那会被当成普通字符串。Age 大于等于的查询给个示例,说明参数化不只适用于等值匹配,范围查询同样有效。

4. 从本机搬到局域网:网络连接与防火墙的两个拦路虎

4.1 SQL Server 配置管理器里要开的三个开关

很多人的代码写得好好的,换个机器就连不上,报错信息翻来覆去就那几句:找不到服务器、连接超时、用户登录失败。排查顺序有讲究:先确认 SQL Server 服务在跑,再确认协议里 TCP/IP 已启用,最后确认端口。这套资源里网络连接的坑集中在 SQL Server 本身的默认配置上,新装数据库 TCP/IP 默认可能是禁用的,走网线必然失败。

打开 SQL Server 配置管理器,按下面三步操作:

  1. 找到“SQL Server 网络配置”下的“MSSQLSERVER 的协议”,右键 TCP/IP 选择启用
  2. 右键 TCP/IP → 属性 → IP 地址页签,拉到最下面 IPAll,把 TCP 端口改为 1433
  3. 服务管理里重启 SQL Server 服务,让配置生效

改完这些,本机用 IP 连接测试一下,比如 SqlCmd 或者直接在代码里把 Data Source 改成 127.0.0.1,能通再让同事试。TCP/IP 启用后端口要不要改默认的 1433,我建议新手先用默认值,跑通了再考虑改端口这种加固操作。改完端口忘了重启服务,配置不生效,这种低级错误我踩过不止一次。

4.2 防火墙入站规则:最常见的网络连接失败原因

SQL Server 配置管理器改完之后,同局域网别人连你,报错大概率是连接超时。这个问题的头号原因就是 Windows 防火墙把 1433 端口拦了。之前的代码和数据库没有任何问题,纯粹是网络层不让进,属于环境问题。

# 以管理员身份运行,放行 1433 端口 netsh advfirewall firewall add rule name="SQL Server 1433" dir=in action=allow protocol=TCP localport=1433

加一条入站规则允许 TCP 1433 端口,加了之后立刻再测。Windows 防火墙有域、专用、公用三套配置,开发机通常处于专用网络,测试时如果还不行就检查当前网络配置文件是不是公用,公用网络的防火墙策略往往更严。有些公司电脑还有安全软件自带的防火墙,需要在软件里也放行一次。做完这些,代码不需要改一行,网络连接就通了。

对于文件资源和文档资源,需要用表格、步骤、参数描述来替代代码块。比如这套资源附带的 SQL Server 配置说明,可以整理成下表:

配置项默认值需要改成的值配置路径
TCP/IP 协议已禁用已启用SQL Server 配置管理器 → SQL Server 网络配置 → MSSQLSERVER 的协议
TCP 端口空1433TCP/IP 属性 → IP 地址 → IPAll → TCP 端口
SQL Server 服务运行中重启SQL Server 配置管理器 → SQL Server 服务

4.3 账号登录模式的切换:SQL Server 身份验证必须打开

网络连接还有一个前置条件:数据库要允许 SQL 账号密码登录。默认安装时 Windows 身份验证模式通常能用,但网络连接必须用 SQL 身份验证。这个设置在 SSMS 里:右键服务器实例 → 属性 → 安全性 → 服务器身份验证,选择“SQL Server 和 Windows 身份验证模式”,然后重启服务。

同时还要检查 sa 账号的状态和密码策略。sa 账号如果在安装时选了 Windows 身份验证模式,默认是禁用的,需要手动启用并设密码。密码强度不够时,有些版本会因为策略限制拒绝登录,报错提示“不能满足密码策略要求”,这时需要把密码设得复杂一些。这一步做完,配合上面的 TCP/IP 启用和防火墙放行,局域网连接才真正具备条件。

5. 常见问题排查:五个高频报错的现象、原因与解法

5.1 报错“在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误”

现象:程序启动后查询数据,弹窗提示找不到服务器或实例,错误描述里包含 network-related 或 instance-specific 字样。

原因:这个报错是网络类问题的总入口,九成是连接字符串的 Data Source 写错,或者目标端口连不通。本机用句点没事,换到网络模式写 IP 时没带端口号,连接就超时;还有一种情况是 SQL Server 服务根本没启动。

解决:先用 SqlCmd 手动测连接,排除程序干扰。命令行执行 sqlcmd -S 192.168.1.100,1433 -U sa -P 密码,能进 SQL 提示符说明网络通、认证通,问题在程序配置;连不上则依次检查服务状态、TCP IP 协议、防火墙规则。

5.2 报错“用户 'sa' 登录失败”

现象:提示用户 sa 登录失败,但账号密码明明输对了。

原因:sa 账号没有启用,或者 SQL Server 处于仅 Windows 身份验证模式,直接把 SQL 账密认证拒绝了。

解决:先用 Windows 身份验证方式登录 SSMS,在安全性 → 登录名 → sa 上右键属性,状态页签里启用登录,然后服务器属性里把身份验证模式切成混合模式。改完必须重启 SQL Server 服务,不重启不生效,这是翻车重灾区。

5.3 报错“已成功与服务器建立连接,但是在登录过程中发生错误”

现象:能连上服务器,但在登录过程中报错,错误代码类似 18456。

原因:密码错误是最常见原因,其次是对应账号被锁定,或者映射的数据库不存在。18456 这个错误码对应很多子情况,SQL Server 日志里会给出具体错误号,比如 18456 后面跟 18470 表示账号被禁用。

解决:打开 SSMS 看 SQL Server 日志里详细错误号,但更快的办法是直接在 SSMS 里用目标账号试登录一次。如果 SSMS 能进,程序里多半是连接字符串的密码或者数据库名不匹配;如果 SSMS 都进不去,回到账号启用与密码重置那一环重新处理。

5.4 报错“连接超时”或“超时时间已到”

现象:本地秒连,局域网连接要等 30 秒左右才弹出超时报错。

原因:TCP 连接没有被目标机器接受,防火墙是最主要因素,其次是端口没监听。SQL Server 默认动态端口会变化,如果没手动固定 1433,重启服务后端口可能漂移,防火墙规则对着旧端口就失效了。

解决:先确认 SQL Server 是否在监听 1433。命令行执行 netstat -ano | findstr 1433,没有任何输出说明端口没监听,回到配置管理器把 IPAll 的 TCP 端口设成 1433 并重启服务;有监听但连不上,就是防火墙拦截,回到 netsh 那条命令。

5.5 报错“数据库 'DemoDB' 不存在”

现象:连接成功但提示数据库不存在。

原因:连接字符串里 Initial Catalog 写的库名和实际库名不一致,或者 SQL 脚本只在某台机器执行过,目标机器上根本没有这个库。

解决:在 SSMS 里执行 SELECT Name FROM sys.databases 查看当前实例下所有数据库名,把 App.config 里 Initial Catalog 改成正确的名字。这套资源的 SQL 脚本要在目标数据库上重新执行一遍,别想当然认为库会跟着程序自动过去。

6. 再接再厉:把 DataGridView 改成翻页查询,顺便验证连接方式的切换

翻页机制其实不复杂,核心是三句话:只查当前页需要的数据,而不是一次性把所有记录拖到内存里。由于 IDENTITY 自增主键的存在,倒序排列取指定范围,用 OFFSET FETCH 语法最顺手。这个改造做完,你会对整套增删改查的运行链路理解得更透。先封装一个分页查询方法,接收页码和每页条数,返回当前页数据加上总记录数,这样控件上能显示“第 1/5 页,共 96 条”。

private (DataTable, int) GetPagedData(int pageIndex, int pageSize, string connStr) { string countSql = "SELECT COUNT(*) FROM Users"; int total = Convert.ToInt32(DbHelper.ExecuteScalar(countSql, connStr)); string sql = @"SELECT Id, UserName, Age, Email, CreateTime FROM Users ORDER BY Id DESC OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY"; SqlParameter[] paras = { new SqlParameter("@Offset", SqlDbType.Int) { Value = (pageIndex - 1) * pageSize }, new SqlParameter("@PageSize", SqlDbType.Int) { Value = pageSize } }; DataTable dt = DbHelper.ExecuteQuery(sql, connStr, paras); return (dt, total); }

这个写法依赖 SQL Server 2012 以上的 OFFSET FETCH 语法,老版本数据库不支持,需要改成 ROW_NUMBER() 嵌套查询。参数 @Offset 直接算偏移量,不需要拼接字符串。翻页按钮和后端逻辑解耦,UI 只管传页码,数据层永远做同样的事。验证连接方式切换时,我一般把 App.config 里的 Data Source 从本地句点改成局域网 IP,启动后翻几页,能过说明网络连接全程通畅。这个改造虽然简单,但能把增删改查、连接字符串、配置修改这几个环节全部串起来,相当于一次完整的走查。从那以后我每次接触这类带数据库的 Winform 老项目,都会强制走一遍这个流程:先看配置、测连接、查账号、翻一页数据,全部通过才敢继续往下加功能。希望这套思路对你有帮助,少走一步冤枉路都是赚到的。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进