简介这份资源是《Visual Studio 2015(C#) Windows数据库项目开发》的配套资料包面向具备C#基础、希望系统掌握Windows平台数据库应用开发的初学者与进阶开发者。内容围绕Visual Studio 2015与.NET Framework展开涵盖环境配置、ADO.NET数据访问、CRUD操作、DataSet与DataAdapter离线数据管理以及多表查询、存储过程、触发器、视图、索引和数据库安全等进阶主题并配有实训项目、课件与教材案例帮助读者从环境搭建逐步过渡到完整项目实战。压缩包共3423个文件约95.9MB以cs源码、resx与resources资源、dll类库、config配置、rdlc报表、csproj与sln工程文件为主另含mdf与ldf数据库文件、aspx页面及ppt课件结构完整便于按模块学习。目前已有1081人学习下载适合需要对照源码理解数据库交互流程、积累项目经验的读者参考。1. 从一份 Visual Studio 2015 的 C# Windows 数据库项目压缩包说起你手上如果有一个叫「Visual Studio 2015(C#) Windows数据库项目开发.zip」的压缩包大概率是某门课、某个培训或者某位前辈留下的完整工程。它不是一个空壳模板而是一套能在 Windows 上跑起来的、带数据库增删改查的桌面程序。这类项目通常用 WinForms 或 WPF 做界面用 ADO.NET 或 Entity Framework 连 SQL Server核心诉求就一个把数据从界面存进库、再从库里读回界面。适合谁适合刚学完 C# 语法、想找一个能跑通的完整项目来练手的人也适合需要快速交付一个内部管理工具的一线开发者。但现实是很多人拿到压缩包后卡在第一步——环境对不上、数据库连不上、控件一多界面就卡。这篇笔记就按「拆包 → 跑通 → 改功能 → 避坑」的顺序把这条路走一遍。2. 拆开压缩包先别急着 F5环境与依赖的核对清单拿到一个 Visual Studio 2015 时代的 C# 项目最忌讳的就是双击 .sln 然后直接按 F5。那个年代的工程对运行环境有比较明确的绑定尤其是数据库和 .NET Framework 版本。先做核对比事后报错再回头查要省时间。2.1 确认 .NET Framework 版本与目标平台Visual Studio 2015 默认支持到 .NET Framework 4.6但很多教学项目用的是 4.5 或 4.5.1。打开项目后在解决方案资源管理器里右键项目 → 属性 → 应用程序 → 目标框架看它锁在哪个版本。如果本机没装对应版本的 .NET Framework编译能过但运行会直接弹窗报错。常见做法是去微软官网下载对应的 Developer Pack而不是只装 Runtime。另一个容易忽略的是目标平台x86 还是 x64。如果项目里引用了第三方的 32 位数据库驱动或 COM 组件平台不匹配会在运行时抛BadImageFormatException。在项目属性的「生成」页里把目标平台显式设成与依赖一致别用 Any CPU 糊弄。2.2 数据库引擎选型SQL Server 还是 LocalDB这类项目九成以上用 SQL Server。但你要区分它连的是完整版 SQL Server 还是 SQL Server LocalDB。打开配置文件App.config看connectionStrings节点里的Data Source。如果是(LocalDB)\v11.0或(LocalDB)\MSSQLLocalDB说明用的是 LocalDB它随 Visual Studio 一起安装不需要单独装数据库服务。如果是.\SQLEXPRESS或某个具体实例名那就得确认本机有没有装对应的 SQL Server Express 实例。我一般会先在 SSMS 或 Azure Data Studio 里手动连一次确认实例活着、库存在、账号能进再回 Visual Studio 跑程序。这一步能排掉后面一半的「登录失败」类报错。2.3 还原 NuGet 包与检查引用路径老项目里的packages文件夹经常被压缩包排除掉或者引用路径写的是原作者的绝对路径。打开项目后先看「引用」节点有没有黄色感叹号。如果有右键解决方案 → 还原 NuGet 包。如果还原失败检查packages.config里列的包名和版本手动在 NuGet 包管理器里按版本装。还有一种情况是引用了本机 COM 组件或第三方 DLL路径写死成C:\Users\某某\...这种必须改成相对路径或重新添加引用。别小看这一步引用没理顺后面写多少代码都编译不过。2.4 用一段最小代码验证数据库连通性在正式跑主程序之前我习惯先写一个控制台入口或者一个临时按钮用最少的代码验证连接字符串能不能打开。这样能把「环境问题」和「业务代码问题」分开。using System; using System.Data.SqlClient; namespace DbConnectCheck { class Program { static void Main(string[] args) { // 连接字符串从 App.config 读取这里先硬编码做验证 string connStr Data Source(LocalDB)\MSSQLLocalDB;Initial CatalogMyTestDb;Integrated SecurityTrue; using (SqlConnection conn new SqlConnection(connStr)) { try { conn.Open(); Console.WriteLine(连接成功数据库版本 conn.ServerVersion); } catch (SqlException ex) { // 按错误号定位18456 登录失败4060 无权打开库53 实例名不对 Console.WriteLine(错误号 ex.Number 消息 ex.Message); } } Console.ReadLine(); } } }这段代码的逻辑很直接用SqlConnection打开连接成功就打印服务器版本失败就按SqlException.Number分类。参数说明Data Source填实例名LocalDB 用(LocalDB)\MSSQLLocalDBExpress 用.\SQLEXPRESSInitial Catalog填目标数据库名Integrated SecurityTrue表示用 Windows 身份验证如果数据库用的是 SQL 账号改成User IDxxx;Passwordxxx。跑通这段再回去跑主程序心里就有底了。3. 把数据库增删改查接进 WinForms从建表到绑定的完整链路环境通了之后核心工作就是把界面和数据库接起来。这类项目的典型结构是一个Form上放DataGridView显示数据几个TextBox做输入几个Button做增删改查。下面按建表、写数据访问层、绑定界面的顺序走一遍。3.1 建表与主键设计别用自增 ID 当业务键先在数据库里建一张业务表。以常见的「学生信息管理」为例CREATE TABLE Students ( Id INT IDENTITY(1,1) PRIMARY KEY, -- 自增主键仅用于内部关联 StudentNo NVARCHAR(20) NOT NULL UNIQUE, -- 业务学号唯一约束 Name NVARCHAR(50) NOT NULL, Age INT NULL, CreateTime DATETIME NOT NULL DEFAULT GETDATE() );这里的关键点是自增Id只做内部主键业务上用来查询和展示的是StudentNo。很多教学项目直接拿自增 ID 当学号用一旦删除记录再新增ID 就断了业务上很难看。加UNIQUE约束能防止重复录入。CreateTime给默认值插入时不用手动传。表建好后在 Visual Studio 里用「服务器资源管理器」连上数据库确认表结构可见。3.2 数据访问层把 SqlConnection 关进 using 里不要在按钮的 Click 事件里直接写 SQL 和连接代码那样界面一复杂就乱。抽一个简单的数据访问类using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; namespace StudentApp.DAL { public class StudentDal { private readonly string _connStr; public StudentDal(string connStr) { _connStr connStr; } // 查询全部返回 DataTable 直接绑 DataGridView public DataTable GetAll() { DataTable dt new DataTable(); using (SqlConnection conn new SqlConnection(_connStr)) using (SqlCommand cmd new SqlCommand(SELECT Id, StudentNo, Name, Age, CreateTime FROM Students ORDER BY Id DESC, conn)) { conn.Open(); using (SqlDataAdapter adapter new SqlDataAdapter(cmd)) { adapter.Fill(dt); } } return dt; } // 新增返回受影响行数 public int Add(string studentNo, string name, int age) { using (SqlConnection conn new SqlConnection(_connStr)) using (SqlCommand cmd new SqlCommand(INSERT INTO Students(StudentNo, Name, Age) VALUES(No, Name, Age), conn)) { cmd.Parameters.AddWithValue(No, studentNo); cmd.Parameters.AddWithValue(Name, name); cmd.Parameters.AddWithValue(Age, age); conn.Open(); return cmd.ExecuteNonQuery(); } } // 按学号删除 public int DeleteByNo(string studentNo) { using (SqlConnection conn new SqlConnection(_connStr)) using (SqlCommand cmd new SqlCommand(DELETE FROM Students WHERE StudentNo No, conn)) { cmd.Parameters.AddWithValue(No, studentNo); conn.Open(); return cmd.ExecuteNonQuery(); } } } }逻辑说明每个方法内部自己开连接、自己关用using保证即使抛异常也能释放。参数说明AddWithValue会自动推断类型但生产环境建议显式指定SqlDbType和长度避免nvarchar被隐式转成varchar导致索引失效。GetAll返回DataTable是为了直接绑DataGridView省去手动拼列的麻烦。注意ORDER BY Id DESC让最新录入的排在最上面符合操作习惯。3.3 界面绑定与刷新DataGridView 的 DataSource 用法在 Form 的代码里声明一个StudentDal实例在Load事件里调GetAll并赋给DataGridView.DataSourceprivate StudentDal _dal; private void Form1_Load(object sender, EventArgs e) { string connStr System.Configuration.ConfigurationManager.ConnectionStrings[MyDb].ConnectionString; _dal new StudentDal(connStr); RefreshGrid(); } private void RefreshGrid() { dgvStudents.DataSource _dal.GetAll(); // 隐藏自增 Id 列只展示业务字段 if (dgvStudents.Columns[Id] ! null) dgvStudents.Columns[Id].Visible false; } private void btnAdd_Click(object sender, EventArgs e) { if (string.IsNullOrWhiteSpace(txtStudentNo.Text) || string.IsNullOrWhiteSpace(txtName.Text)) { MessageBox.Show(学号和姓名不能为空); return; } int age 0; int.TryParse(txtAge.Text, out age); _dal.Add(txtStudentNo.Text.Trim(), txtName.Text.Trim(), age); RefreshGrid(); }参数说明连接字符串从App.config的connectionStrings节点读键名要和配置文件里一致。RefreshGrid每次操作后重新查一遍保证界面和数据一致。DataGridView的AutoSizeColumnsMode建议设成Fill否则列宽会很别扭。如果数据量大别每次全查加分页或按条件过滤。3.4 用参数化查询堵住拼接 SQL 的漏洞上面代码里全部用了参数这是必须的。我见过太多教学项目直接写SELECT * FROM Students WHERE Name txtName.Text 这种写法遇到单引号就报错遇到恶意输入就是注入。参数化查询不仅安全还能让 SQL Server 缓存执行计划重复查询更快。改法很简单把拼接的变量换成xxx然后用cmd.Parameters.AddWithValue或Add传值。注意AddWithValue对nvarchar会按实际字符串长度推断如果列定义是NVARCHAR(50)而传入 200 个字符会截断或报错所以关键字段建议用Add(Name, SqlDbType.NVarChar, 50).Value name这种显式写法。4. 避坑与排查数据库项目最容易翻车的五个地方这一章全是血泪经验。每个问题都按「现象 → 原因 → 解决」写遇到对应症状直接对号入座。4.1 现象程序启动报「无法打开登录所请求的数据库」原因连接字符串里的Initial Catalog指向的库不存在或者当前 Windows 账号没有权限访问。LocalDB 实例下如果库文件被删了也会报这个。解决先用 SSMS 连上实例执行SELECT name FROM sys.databases看库在不在。不在就手动建库或附加 MDF 文件。在的话检查当前登录账号有没有被授予db_datareader和db_datawriter角色。LocalDB 的库文件默认在用户目录下换电脑后路径变了也会连不上需要重新附加。4.2 现象DataGridView 数据不刷新改了库里的数据界面没反应原因DataSource绑的是旧的DataTable没有重新查询。或者DataGridView的DataSource被设成null后又赋了同一个引用。解决每次增删改后调RefreshGrid()重新查。如果用了BindingSource要调bindingSource.ResetBindings(false)。另外注意DataGridView的AllowUserToAddRows如果为true最后一行是空白新增行绑定时会多出一行设成false更干净。4.3 现象控件一多WinForms 界面拖动卡顿、重绘闪烁原因窗体上控件数量多且没有开双缓冲。DataGridView在数据量大时逐行绘制也会卡。解决给自定义控件或窗体设DoubleBuffered true或者在构造函数里SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true)。DataGridView可以设DoubleBuffered属性需要反射或继承更直接的是减少一次性加载的行数用分页。另外把AutoSizeColumnsMode从AllCells改成Fill能明显减少布局计算。4.4 现象插入中文变成问号或者查询条件带中文查不到原因数据库列的排序规则不是中文排序规则或者参数化查询时用了varchar而不是nvarchar。解决建表时字符串列统一用NVARCHAR连接字符串不用改。C# 里string默认是 Unicode传给SqlParameter时如果显式指定SqlDbType.VarChar就会按当前代码页转换中文可能丢。改成SqlDbType.NVarChar并指定长度。数据库排序规则建议用Chinese_PRC_CI_AS建库时选对后期改排序规则很麻烦。4.5 现象程序在自己电脑上跑得好拷到别人电脑就报错原因目标机器没装对应版本的 .NET Framework或者没装 SQL Server LocalDB或者缺少某个第三方 DLL。解决发布时用 Visual Studio 的「发布」功能勾选「应用程序文件」里的依赖项。如果对方没有数据库环境考虑把数据库换成 SQLite用System.Data.SQLite或Microsoft.Data.Sqlite这样整个程序就是一个 exe 加一个 db 文件部署成本最低。但要注意 SQLite 的 SQL 语法和 SQL Server 有差异比如自增主键用INTEGER PRIMARY KEY AUTOINCREMENT日期函数也不同迁移时逐条改。5. 从能跑到好用给老项目加一层配置化与异常兜底项目跑通之后真正拉开差距的是「好不好维护」。我一般会做两件事把连接字符串和 SQL 语句从代码里抽出来以及给所有数据库操作加统一的异常处理。5.1 把连接字符串和 SQL 放进配置文件App.config里加connectionStrings add nameMyDb connectionStringData Source(LocalDB)\MSSQLLocalDB;Initial CatalogStudentDb;Integrated SecurityTrue providerNameSystem.Data.SqlClient/ /connectionStrings appSettings add keyPageSize value20/ /appSettings代码里用ConfigurationManager.ConnectionStrings[MyDb].ConnectionString读。这样换数据库、换实例只改配置文件不用重新编译。SQL 语句如果复杂可以放到单独的.sql文件里用File.ReadAllText读进来或者用存储过程。注意App.config在编译后会变成StudentApp.exe.config发布时要一起带上。5.2 用 try-catch 包住数据库操作并记录日志数据库操作失败是常态关键是失败后程序别崩并且留下线索。我习惯在数据访问层外面包一层public DataTable SafeGetAll() { try { return _dal.GetAll(); } catch (SqlException ex) { // 记录到文件同时给用户友好提示 System.IO.File.AppendAllText(error.log, DateTime.Now SQL错误 ex.Number : ex.Message Environment.NewLine); MessageBox.Show(读取数据失败请检查数据库连接。错误号 ex.Number); return new DataTable(); } catch (Exception ex) { System.IO.File.AppendAllText(error.log, DateTime.Now 未知错误: ex.Message Environment.NewLine); MessageBox.Show(发生未知错误已记录日志。); return new DataTable(); } }参数说明SqlException.Number是排查的关键常见的有 18456登录失败、4060无法打开库、53实例找不到、2627唯一约束冲突。日志文件放在程序目录下方便用户反馈时直接发过来。注意别把异常吞掉后返回null返回空DataTable能让界面继续显示空表格比崩溃好。5.3 用事务保证多条 SQL 要么全成要么全败如果一次操作要写多张表比如「新增学生同时写一条操作日志」必须用事务using (SqlConnection conn new SqlConnection(_connStr)) { conn.Open(); using (SqlTransaction tran conn.BeginTransaction()) { try { using (SqlCommand cmd1 new SqlCommand(INSERT INTO Students(...) VALUES(...), conn, tran)) { /* 加参数 */ cmd1.ExecuteNonQuery(); } using (SqlCommand cmd2 new SqlCommand(INSERT INTO Logs(...) VALUES(...), conn, tran)) { /* 加参数 */ cmd2.ExecuteNonQuery(); } tran.Commit(); } catch { tran.Rollback(); throw; } } }注意SqlCommand的构造函数要传transaction否则命令不在事务里执行。Rollback放在catch里throw把异常继续往上抛让上层记录日志。别在catch里只写Rollback不throw那样上层不知道失败了。5.4 一个验证技巧用 SQL Profiler 或 Extended Events 看真实执行的 SQL有时候代码里写的 SQL 和实际发到数据库的不一样尤其是用了 ORM 或拼接字符串。开一个 SQL Server Profiler 跟踪或者用 Extended Events 抓sql_statement_completed事件能看到真实执行的语句、参数值和耗时。我一般会在调试增删改查时开着确认参数化生效、没有隐式转换、没有全表扫描。如果发现某条查询耗时超过 100ms加索引或改写法。这个习惯能帮你提前发现性能问题而不是等用户抱怨卡了才回头查。5.5 老项目的升级取舍什么时候该重写什么时候只打补丁Visual Studio 2015 和 .NET Framework 4.x 已经停止主流支持但很多内部工具还在跑。我的判断标准是如果只是加字段、改查询、修 bug就在原项目上打补丁成本最低。如果要加 Web API、要跨平台、要接新数据库那就把数据访问层抽出来用 .NET 6 重写界面可以保留 WinForms 也可以换 WPF。重写时先把数据库结构用 EF Core 的Scaffold-DbContext反向生成模型再逐步替换。别一次性全推倒老项目能跑就是最大的价值。希望帮到你。本文还有配套的精品资源点击获取