
简介这是一套基于C#与SQL Server开发的合同管理系统完整源码包面向初中级.NET开发学习者、软件工程课程设计或毕业设计使用者帮助理解WinForms界面设计、三层架构及数据库交互也可作为企业合同管理模块的原型参考。资源共123个文件主要以.cs源码文件、.resx/.resources资源文件、.config配置文件及数据库文件.mdf/.ldf组成压缩包整体2.82MB其中数据库文件可直接附加至SQL Server便于快速还原测试数据。系统覆盖客户、项目、合同信息及执行控制等模块管理员可对上述数据进行增删改查普通用户则支持按编号、名称、日期等条件查询合同及项目明细权限划分明确。目前已有153人学习下载适合用作C#项目实战参考、数据库应用练习或合同管理原型基础。附带数据库附加说明修改连接串后即可调试运行能直观看到各模块运行效果便于对照代码加深理解。1. 为什么做这套合同管理系统先解决“到期了才发现没续签”的窘境做合同管理的朋友应该都有同感合同躺在文件夹里靠 Excel 登记到期日全靠脑子记真到了续签节点往往是人情提醒才想起来。去年我帮一家做工程项目的小公司整理信息化方案他们手上三四百份合同付款节点、质保期、续签日全在业务员各自的 Excel 里财务月底对账才发现有两笔应收款逾期了半个月。问题不是他们没有管理意识而是没有一个能把“合同主信息、客户、项目、执行进度”串起来的工具。这套基于 C# WinForms SQL Server 的合同管理系统源码包解决的就是这个问题管理员维护客户、项目、合同和执行控制普通用户查明细界面直白数据关系清楚拿到源码改一改就能落地成内部系统。适合刚学 C# 数据库开发的初学者拿来拆解也适合小团队拿来二次开发成自己的合同台账工具。2. 系统功能与数据库设计从合同台账到执行控制的数据链路2.1 功能模块梳理管理员和普通用户各管什么这套系统的功能划分很典型管理员负责“数据维护”普通用户负责“数据查看”。从源码里的窗体命名就能看出一二FrmMain是主窗体Frmhetongmingxi管合同明细FrmzhixingD管执行控制。这种按“主界面—业务单据—执行记录”拆窗体的做法在中小型管理软件里非常常见每个窗体对应一个业务操作入口开发时思路清晰后期加功能也不会牵一发动全身。功能上可以拆成四个块客户管理客户基本信息维护包括名称、联系人、电话、地址作为合同的关联主体。项目管理项目立项信息项目编号、名称、负责人合同挂在项目下面。合同明细合同编号、名称、签订日期、到期日期、合同金额、客户关联、项目关联。执行控制合同执行进度比如预付款、进度款、验收、质保金支付等节点状态。这套设计的好处是数据有“归属”合同不会凭空存在它必须挂在一个客户和一个项目下查合同的时候顺着客户或项目钻取业务脉络是通的。权限上管理员能增删改普通用户只能查询对应源码里的登录角色字段这正好满足小公司“管的人少、看的人多”的实际需求。2.2 SQL Server 数据库结构核心表与字段设计数据库设计是这套系统最值得看的部分。典型的合同管理系统核心表不会太多我拆解一下数据关系表名核心字段作用客户表客户ID、客户名称、联系人、电话合同的主体一个客户可有多份合同项目表项目ID、项目名称、负责人、项目状态业务载体合同挂在项目下合同表合同ID、合同编号、合同名称、客户ID、项目ID、签订日期、到期日期、金额核心业务表与客户、项目关联执行控制表执行ID、合同ID、节点名称、计划日期、实际日期、状态合同执行进度的节点跟踪关键字段的选型上有几个细节值得说。合同编号用varchar因为在真实业务里合同编号会带字母和横杠比如HT-2025-001用int会自增不连续且没法表达业务含义。金额字段用decimal(18, 2)两位小数足够但如果合同会涉及多币种数据库层面最好加一个币种字段这是原系统没做但实际很容易碰到的情况。日期字段用datetime这里有个隐藏坑SQL Server 2008 之后的datetime精度到毫秒而 2016 推荐用datetime2如果你在公司新部署的 SQL Server 2019 上还原这个库字段类型兼容没问题但在C#端读取日期时要注意格式化否则 GridView 里会显示一堆毫秒尾巴。2.3 数据库附加与连接配置先把数据层跑通拿到源码包后第一步不是打开 Visual Studio 编译而是先把数据库挂上。.bak文件的话用还原如果是.mdf文件就用附加。这套系统用的是 SQL Server数据库文件的附加方式如下。-- 以 Windows 身份登录 SSMS执行附加 USE [master] GO CREATE DATABASE [hetongManage] ON (FILENAME NC:\Data\hetongManage.mdf), (FILENAME NC:\Data\hetongManage_log.ldf) FOR ATTACH GO参数说明FILENAME路径必须是 SQL Server 服务账户有权限访问的目录我习惯放在 SQL Server 的Data目录下而不是 D 盘任意文件夹否则很容易出现“附加数据库失败操作系统错误 5拒绝访问”。数据文件路径不要放在桌面、C 盘根目录这类权限尴尬的位置。附加完成后改连接字符串。项目里的App.config是 WinForms 读取连接配置的地方。典型的配置如下connectionStrings add namehetongManage.Properties.Settings.hetongManageConnectionString connectionStringData Source.;Initial CataloghetongManage;User IDsa;Password123456; providerNameSystem.Data.SqlClient / /connectionStrings说明Data Source.表示本机默认实例如果你装的是命名实例要写成localhost\SQLEXPRESS这样的格式。User IDsa 密码方式适合开发期快速调试部署到公司内部使用时最好换成 Windows 身份验证或者建一个权限受限的专用账号避免给 sa 一个弱口令满天飞。改完连接字符串在FrmMain派生的窗口里测试登录能进主界面说明数据层通了。3. 源码结构与 C# 实现细节窗体、数据访问与业务逻辑3.1 项目结构拆解sln 解决方案里的各个工程这套源码的工程结构不复杂核心是一个 WinForms 项目。hetongManage.csproj是主工程文件双击hetongManage.sln就能在 Visual Studio 里打开。DesignTimeResolveAssemblyReferences.cache这类文件是 VS 自动生成的临时缓存不需要提交到代码库但因为这个资源是编译过的项目打包所以一并保留在压缩包里不影响使用。首次打开解决方案时如果本机已经装过 NuGet 包会自动还原。但这个项目用的是传统System.Data.SqlClient理论上不依赖外部包。编译前检查一下目标框架老的项目可能是.NET Framework 4.5或4.7.2如果本机装了 VS2022默认可能只装了.NET 6/8的 SDK需要去 Visual Studio Installer 里补装“.NET Framework 4.7.2 开发工具”这个组件否则打开项目会提示“不受支持”。3.2 数据访问层设计SqlConnection 与 SqlCommand 的典型用法源码里的数据访问方式是比较老派的SqlConnectionSqlCommandSqlDataAdapter直接写 SQL 的模式这在老项目中很常见也最适合新手读。比如加载合同明细分页列表典型的代码块如下private DataTable LoadContractList(string keyword, int pageIndex, int pageSize) { string connStr ConfigurationManager.ConnectionStrings[hetongManageConnectionString].ConnectionString; string sql SELECT c.ContractID, c.ContractNo, c.ContractName, c.SignDate, c.DueDate, c.Amount, cus.CustomerName, p.ProjectName FROM dbo.Contract c INNER JOIN dbo.Customer cus ON c.CustomerID cus.CustomerID INNER JOIN dbo.Project p ON c.ProjectID p.ProjectID WHERE c.ContractName LIKE kw OR c.ContractNo LIKE kw ORDER BY c.SignDate DESC OFFSET offset ROWS FETCH NEXT pageSize ROWS ONLY; using (SqlConnection conn new SqlConnection(connStr)) using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(kw, % keyword %); cmd.Parameters.AddWithValue(offset, (pageIndex - 1) * pageSize); cmd.Parameters.AddWithValue(pageSize, pageSize); SqlDataAdapter adapter new SqlDataAdapter(cmd); DataTable dt new DataTable(); adapter.Fill(dt); return dt; } }几点逻辑说明INNER JOIN把合同表跟客户、项目关联起来一条 SQL 查出列表所需的全部展示字段避免 UI 层再做二次查询。OFFSET...FETCH NEXT是 SQL Server 2012 的分页语法比老式的ROW_NUMBER()写法简洁但要注意如果数据库兼容级别被设为 2008100这个语法会直接报错需要把数据库兼容级别改成 2012110以上。using确保连接和命令释放这是老代码里比较规范的做法值得保留。AddWithValue在少量参数时没问题但如果你接手后要继续加功能建议逐步换成cmd.Parameters.Add(kw, SqlDbType.NVarChar, 50).Value ...因为AddWithValue有时会让 SQL Server 对参数类型推断不准导致索引失效。这是一个典型的“能跑和跑得好的差别”。3.3 主窗体与合同明细分窗体UI 交互的套路FrmMain是 MDI 主窗体左侧菜单或顶部菜单挂各功能入口子窗体通过new Frmhetongmingxi().Show()这种方式在新窗口打开。Frmhetongmingxi是合同明细界面上面是查询条件客户、项目、日期区间、关键字下面是DataGridView展示列表双击行打开合同详情或编辑窗体。这种“上查下显”的布局在内部管理系统里是标准做法改造成本低用户上手快。执行控制窗体FrmzhixingD则是一个主从表结构上方显示合同基本信息下方显示该合同的执行节点列表节点状态通过下拉框切换未开始/进行中/已完成。这里涉及一个 WinForms 常见操作——主从表的联动加载private void contractGrid_SelectionChanged(object sender, EventArgs e) { if (contractGrid.CurrentRow null) return; int contractId Convert.ToInt32(contractGrid.CurrentRow.Cells[ContractID].Value); LoadExecuteItems(contractId); }这段代码的解释当用户在合同列表选中一行时触发SelectionChanged事件取当前行的ContractID再异步或同步加载该合同对应的执行节点列表。这里有一个细节坑SelectionChanged事件在列表数据刷新时会触发多次如果加载方法里有耗时操作UI 会卡顿。常见做法是在LoadExecuteItems里加一个防重入标志或用BackgroundWorker异步加载。4. 把系统跑起来从 sln 到数据联调的完整复现步骤4.1 环境准备与编译顺序这套系统的运行环境是 Windows Visual Studio SQL Server。版本搭配上我建议 VS2019 或 VS2022SQL Server 2016 到 2019 都可以。装 VS 时记得勾选“.NET 桌面开发”工作负载因为 WinForms 依赖它。数据库建议装 Developer 版功能跟企业版一样且免费适合开发调试。环境就绪后按这个顺序操作先附加数据库再改连接字符串然后编译。如果你一上来就 F5多半会报“数据库不存在”或“用户登录失败”。顺序反了系统在一个错误状态里你会分不清是代码问题还是环境问题。编译时如果遇到CS0246找不到类型或命名空间多半是目标框架不对右键项目 → 属性 → 目标框架改成机器上已安装的 .NET Framework 版本。4.2 登录与菜单权限两种角色的验证路径系统带了登录功能用户名密码存在数据库用户表里。第一次登录用管理员账号进系统后先建立客户和项目数据再录合同主信息最后添加执行节点。普通用户账号登录后只看到查询界面看不到新增、编辑、删除按钮。这种权限控制的实现简单的方式是在FrmMain里根据登录角色控制菜单或按钮的Visible/Enabled属性。private void ApplyRolePermissions(string roleName) { bool isAdmin roleName 管理员; btnAddContract.Visible isAdmin; btnEditContract.Visible isAdmin; btnDeleteContract.Visible isAdmin; // 普通用户的可操作按钮收起只保留查询 }这段代码里Visible控制按钮是否显示Enabled控制按钮是否可点。实际项目里这两种属性经常被混用隐藏是界面层面禁用是交互层面。如果按钮承载重要操作建议两者结合——管理员看得到且可点普通用户看得到但置灰这样比直接隐藏更直观用户不会疑惑“这个功能去哪了”。权限相关的改动属于高频需求你可以在这个基础上扩展成菜单级权限表但初期这套“角色判断控制按钮”的方式足够用。4.3 数据库连接失败时的排查路径联调时最常见的问题就是数据库连不上。出现SqlException时按这几步排查首先确认 SQL Server 服务是否启动。在服务管理器里看MSSQLSERVER服务状态没启动就右键启动。其次确认 SQL Server 是否允许 TCP/IP 连接SSMS 里右键实例 → 属性 → 连接 → 勾选“允许远程连接到此服务器”并在 SQL Server 配置管理器里启用 TCP/IP 协议。再次确认连接字符串里的Data Source是否写对了端口默认 1433命名实例写法是服务器名\实例名。最后确认登录账号权限SQL Server 身份验证模式下sa账号能否登录取决于实例的验证模式是不是“SQL Server 和 Windows 身份验证模式”混合模式没开启的话sa永远登不进。如果这些查完还连不上打开 SQL Server 错误日志看具体报错。一条血泪经验SQL Server 的“允许远程连接”默认是关闭的很多人本机调试没问题换台电脑就连不上的根源就在这里——这不是代码问题是配置问题。5. 避坑与常见问题这套源码最容易翻车的五个位置5.1 编译环境不匹配VS 打不开 .sln现象双击hetongManage.sln提示“此版本的应用程序不支持该项目类型”或“需要安装 .NET Framework X.X”。原因项目基于老版本 .NET Framework 创建本机只有 .NET Core / .NET 5 的 SDK缺少对应的 Framework 开发包。解决Visual Studio Installer → 修改 → 单个组件 → 搜索“.NET Framework 4.7.2 开发工具”并勾选安装然后重新打开解决方案。如果还提示版本过低可以右键解决方案 → 重定向到当前版本但要注意重定向后部分 NuGet 包可能需要更新。5.2 WinForms 设计器报错但编译通过现象打开Form1.cs的设计器视图界面不渲染或直接抛异常但 Build 能通过。原因设计器加载时会实例化窗体并触发连接逻辑如果数据库没附加或连接字符串指向无效设计器在初始化组件时就炸了。解决先附加数据库并确认连接字符串有效再重新打开设计器。如果还不行把窗体构造函数里会执行数据库访问的代码用if (DesignMode) return;包一层这是 WinForms 开发绕不开的判断。5.3 日期格式显示带毫秒尾巴现象DataGridView里显示签订日期是2025-01-15 14:23:11.123用户嫌难看。原因数据库字段是datetimeC# 默认ToString()会输出完整精度。解决单元格格式化或 SQL 层做转换两选一-- SQL 层处理 SELECT CONVERT(varchar(10), SignDate, 120) AS SignDateStr FROM dbo.Contract// C# 层处理 e.Value Convert.ToDateTime(e.Value).ToString(yyyy-MM-dd);我习惯在 SQL 层就处理成纯yyyy-MM-dd这样 UI 绑定简单C# 端不用再关心显示格式。要注意CONVERT的style参数120是yyyy-MM-dd HH:mm:ss23是yyyy-MM-dd。新手容易把120和23弄混记住120带时间23只要日期。5.4 GridView 分页与排序的“假分页”现象数据量到了几千条界面操作明显卡顿。原因老写法经常把整表SELECT * FROM Contract直接绑到DataGridView所谓的分页只是 UI 层翻页数据全在内存里。解决改数据库层分页用前面代码块里的OFFSET...FETCH或ROW_NUMBER()方式只取当前页数据同时记录总行数用于显示页码。这套源码里如果已经是这种写法说明作者考虑过数据量问题如果还是全表加载建议你按第 3.2 节的方式重构。5.5 密码明文存储与 SqlBulkCopy 的触发器陷阱现象数据安全审查不过或者用 SqlBulkCopy 批量导入合同数据时某些字段没写进去。原因老系统的用户表密码常用明文或简单 MD5而 SqlBulkCopy 默认不触发INSERT触发器如果业务上有“插入合同自动生成执行任务”之类的触发器逻辑批量导入会绕过它。解决密码至少升级成SHA256 Salt再入库批量导入场景如果依赖触发器做业务联动要么改用普通INSERT逐条执行要么把触发器逻辑改成存储过程在导入后显式调用。这是一个高级点的坑但等你真做到批量导入时一定会遇到。6. 进阶给这套系统加一个合同到期自动提醒的轮询任务跑通源码只是起点真正用起来还得加业务价值。合同管理系统最刚需的增值功能是到期提醒。方案很多登录时检查、定时器轮询、Windows 服务后台跑。最简单实用的是在FrmMain里挂一个System.Windows.Forms.Timer登录后每 30 分钟查一次把未来 30 天内到期的合同弹出来。private void InitDueDateAlert() { Timer timer new Timer(); timer.Interval 30 * 60 * 1000; // 30 分钟一轮 timer.Tick (sender, e) { DataTable dueContracts GetDueContracts(DateTime.Today, DateTime.Today.AddDays(30)); if (dueContracts.Rows.Count 0) { string msg string.Join(\n, dueContracts.Rows.CastDataRow() .Select(r $【{r[ContractNo]}】{r[ContractName]} 到期日{Convert.ToDateTime(r[DueDate]):yyyy-MM-dd})); MessageBox.Show(msg, 合同到期提醒, MessageBoxButtons.OK, MessageBoxIcon.Warning); } }; timer.Start(); }这个功能的关键Timer.Interval的单位是毫秒30 分钟要写成30 * 60 * 1000不能写30。查询逻辑里用DueDate BETWEEN from AND to和状态过滤排除已终止、已续签的合同避免弹一堆无效提醒。弹窗方式适合内部系统但如果怕用户不在电脑前漏看可以在数据库层把提醒记录存成任务表下次登录时统一展示未读提醒这样就不会被MessageBox卡住主线程。从那以后我每次给这类合同系统做交接都会强制走一遍全流程附加数据库 → 改连接串 → 编译 → 登录 → 录入一条真实合同 → 改状态 → 查列表 → 删数据。一套走完这系统的脾气也就摸清了。源码里的每个窗体、每条 SQL 都是作者的真实业务场景沉淀读一遍比看十篇教程都有用。希望你也能从这套代码里拆出适合自己的东西少走我当年走过的弯路希望帮到你。本文还有配套的精品资源点击获取