ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VS2015 C# WinForms数据库项目实战:连接、CRUD与部署全攻略

VS2015 C# WinForms数据库项目实战:连接、CRUD与部署全攻略 简介面向C#初学者的Visual Studio 2015 Windows数据库项目开发配套资源包聚焦C#与SQL Server、SQLite、MySQL等数据库交互的桌面应用开发适用于高校课程实训或个人自学从环境搭建到项目实战均有覆盖。包内共3423个文件压缩后约95.9MB类型包括1126个cs源代码、428个resx与430个resources资源文件、325个dll动态库、77个rdlc报表、111个exe可执行程序、12个PPT课件及配置文件完整覆盖从开发环境配置说明到实训项目、课件、附录项目等模块。目前已有1081人学习下载可按章节逐步实操。资源包含多层次实例既有基础数据查询、ADO.NET连接与CRUD操作、DataSet离线数据管理也有存储过程、触发器、视图等进阶内容还提供多表查询、数据库安全性等扩展案例帮助开发者系统掌握C#数据库开发全流程并积累实战经验。1. VS2015 与 C# Windows 数据库项目别被“老版本”劝退的落地路线图Visual Studio 2015(C#) 做 Windows 数据库项目开发这套技术栈放到今天看确实不算新但它的可用场景依然很扎实单机进销存、设备台账、C# 上位机的采集数据回放以及学校里大量的课程设计都还是这套东西在扛。前阵子一个做设备维护的朋友找我说他工控机上用了五年的 Access 台账想加个查询界面又不想动 Access 原生窗体——我给他指的路就是 VS2015 加 C# 加 WinForms配一个本地数据库文件。这个组合的优点是成熟、稳定、教程铺天盖地生成的程序在没装新运行库的旧 Windows 上也能跑。标题里那个 zip 只是载体真正值钱的是整套能两三天跑通的路线环境怎么装、连接层怎么写、界面怎么绑、数据怎么入库。这篇笔记把这条路完整走一遍新手能照抄熟手可以直接挑避坑章节看。2. 环境搭建VS2015 with Update 3 安装细节与 WinForms 项目骨架先说一个很多人问过的问题Visual Studio 2015 都过去这么多年了还有必要装吗做数据库项目开发尤其目标是 Win7/Win10 工控机、客户机器配置参差不齐的场景VS2015 反而是最不容易翻车的环境。VS2019 起步就需要 Win10 1809 以上的系统很多工业现场还停在 Win7 SP1项目一拷过去就跑不起来。VS2015 生成的 .NET Framework 4.x 程序在 Win7 SP1 上直接能跑这是它至今没退场的关键原因。但装 VS2015 有个硬前提必须装 with Update 3俗称 sp3的版本。RTM 老盘有不少编译器 bugC# 6 支持不完整更重要的是它的安装器在 Win10/Win11 上兼容性极差装到一半卡死是常态。Update 3 是 VS2015 这个版本号的最后一个更新相当于官方把已知问题统一收口了。今天在任何新系统上装都要优先找这个版本的完整离线镜像不要用在线安装器——在线安装器本质是个下载器网络一抖就装一半失败纯属给自己找麻烦。2.1 安装 Visual Studio 2015 with Update 3版本选择与装完先做的两件事安装步骤并不复杂但每一步都得踩稳。我一般按这个顺序走下载 vs2015 with update 3 的完整离线镜像注意镜像语言和系统位数下载后先校验一下文件哈希损坏的 iso 装到一半会报各种莫名其妙的错。安装前把杀毒软件和 Windows Defender 的实时保护临时关掉右键安装程序选择“以管理员身份运行”。磁盘 C 盘至少留 20GB太满会导致安装器中途报错。安装类型选“自定义”功能里勾选 Visual C#、.NET Framework 4.6 SDK再把 Microsoft SQL Server 2016 Express LocalDB 这一项勾上——这就是后面要用的本地数据库运行环境。Windows 10 SDK 不是必须可以去掉省下不少空间。装完后打开 Help About确认版本信息里带 Update 3 字样版本号一般是 14.0.25431.01 这个序列。没看到就说明装的是旧盘最好重来。装完先做两件事。第一是进 Tools Extensions and Updates把 NuGet Package Manager 更新到最新。VS2015 自带的 NuGet 版本太老不更新的后果是后面装 SQLite 驱动、操作 DataSet 时会提示找不到包源或版本冲突。第二是确认 LocalDB 能启动打开命令提示符输入sqllocaldb info能看到实例列表就正常后面连库全靠它。提示安装卡在某个进度条反复回滚时别急着格式化重装。到%temp%目录翻dd_vs_*开头的安装日志多数能定位到是 VC 运行库缺失还是某个组件冲突对症处理比强装十次有效。2.2 新建项目的抉择WinForms 还是 WPF目标框架怎么设系统装好后进入正题建项目。这一步有个绕不开的选择——WinForms 还是 WPF。我的判断标准非常简单目标是内部工具、课程设计、上位机界面就选 WinForms客户明确要求现代皮肤、数据大屏、复杂动画才考虑 WPF。别拿 WPF 的 XAML 学习曲线去换一个用不上的界面效果。对比项WinFormsWPF界面搭建速度拖控件即用最快要写 XAML初期明显慢表格控件DataGridView 文档最全DataGrid 功能强但行为偏玄学数据绑定DataSource 直接赋值需要 INotifyPropertyChanged老系统兼容.NET Framework 4.0 起能跑至少 4.0资源占用高适用场景CRUD 工具、上位机、课程设计大屏展示、皮肤定制、动画新建项目的路径是 File New Project左侧模板选 Visual C# Windows Windows Forms Application按自己的规则填名称和路径建议勾上 Create directory for solution。项目生成后第一件事是右键项目进 Properties把 Application 页的 Target framework 确认在 .NET Framework 4.6——这是 VS2015 的默认值。如果项目要拿到更老的机器上跑降到 4.5 甚至 4.0 也可以但别超过 4.6.2Win7 无 SP1 的系统装不了。同时我会顺手在解决方案里建好四个目录Forms 放窗体Data 放数据访问类Models 放实体类Utils 放日志和公共方法。原因很朴素数据库项目开发到后期换数据库、加导出、改报表是必然的连接串和 SQL 全写在按钮事件里改一次要翻所有窗体谁写谁哭。分层之后再接新需求基本只在 Data 和 Utils 里动代码这是给两个月后的自己留后悔药。还要补一个引用在解决方案资源管理器里右键 References Add Reference把 System.Configuration 勾上。后面要用 ConfigurationManager 读连接串少了这个引用编译直接报错很多人第一次写配置文件就栽在这里。平台目标也顺手确认一下如果后面打算用 Access就把平台目标设为 x86Access 的 OLEDB 驱动多数是 32 位的AnyCPU 在 64 位系统上会加载失败这点到避坑清单里还会再提。3. 数据库选型与连接层LocalDB、Access、SQLite 怎么选连接串怎么写数据库项目开发的第一个岔路口不是界面而是选数据库。Windows 桌面程序最常见的三个方向是 SQL Server LocalDB、Access 和 SQLite它们的部署形态完全不同没有谁比谁高级只有合不合适。选型选错了后面所有代码都要推倒重来。3.1 三种数据库的适用边界一张表和两条判断红线数据库部署形态驱动适合场景SQL Server LocalDB随装的服务进程VS 可自带System.Data.SqlClient课程设计、小系统、要用 T-SQLAccess.accdb单文件即用System.Data.OleDb ACE.OLEDB办公数据迁移、Excel 联动、C# 与 Access 老项目SQLite单文件零配置System.Data.SQLite上位机高频采集、单机工具、绿色发布两条判断红线第一需不需要复杂查询、视图、存储过程或者以后想平滑迁到正式 SQL Server——那就用 LocalDBT-SQL 能力最全第二数据库文件要被拷来拷去、随 U 盘带走——那就选 Access 或 SQLiteLocalDB 的 mdf 附加到另一台机器上经常因为实例版本不匹配而失败。另外补充一个实际场景很多公司老系统的数据都在 Access 里用 C# 与 Access 配合做一个查询与录入界面比逼着用户迁移到 SQL Server 现实得多。而 C# 上位机这类高频采集场景SQLite 零配置、单文件、不占服务资源基本是默认选项。简单说LocalDB 胜在能力SQLite 胜在省事Access 胜在存量数据兼容。3.2 连接串与 DbHelperADO.NET 连接层最小实现选型定了之后连接层是第一个写代码的地方。我强烈建议把连接串放到 app.config而不是写死在代码里。这样发布之后客户改服务器名、改数据库路径直接编辑 exe 旁边的 config 文件就行不用重新编译程序。app.config 的最小配置长这样?xml version1.0 encodingutf-8 ? configuration connectionStrings add nameMainDb connectionStringData Source(localdb)\MSSQLLocalDB;Initial CatalogWinDbDemo;Integrated SecurityTrue; providerNameSystem.Data.SqlClient / /connectionStrings startup supportedRuntime versionv4.0 sku.NETFramework,Versionv4.6 / /startup /configuration注意 connectionStrings 是专门放连接串的节点别塞进 appSettings 里当普通键值用。连接串里最关键的是 Data Source 的实例名(localdb)\MSSQLLocalDB是 SQL Server 2014 及以上 LocalDB 的默认实例VS2015 自带的是 2016 版 LocalDB用的就是这个名字。如果目标机器装过 SQL Server 2012那可能是(localdb)\v11.0两个名字混用会导致后面报错。新建数据库最简单的方式是打开 VS 的 View Server Explorer右键 Data Connections 选择 Add Connection服务器名填(localdb)\MSSQLLocalDB在数据库下拉里选或新建一个然后右键数据库执行 New Query 写建表脚本。表建好之后数据访问类我习惯写成静态 DbHelper只暴露两个方法覆盖 90% 的需求using System; using System.Configuration; using System.Data; using System.Data.SqlClient; namespace WinDbDemo.Data { public static class DbHelper { private static readonly string ConnStr ConfigurationManager.ConnectionStrings[MainDb].ConnectionString; public static DataTable ExecuteQuery(string sql, SqlParameter[] parameters null) { using (var conn new SqlConnection(ConnStr)) using (var cmd new SqlCommand(sql, conn)) { if (parameters ! null) { cmd.Parameters.AddRange(parameters); } var adapter new SqlDataAdapter(cmd); var table new DataTable(); adapter.Fill(table); return table; } } public static int ExecuteNonQuery(string sql, SqlParameter[] parameters null) { using (var conn new SqlConnection(ConnStr)) using (var cmd new SqlCommand(sql, conn)) { if (parameters ! null) { cmd.Parameters.AddRange(parameters); } conn.Open(); return cmd.ExecuteNonQuery(); } } } }这个类里所有 SqlConnection 和 SqlCommand 都用 using 包住连接用完即关。对 Access 和 SQLite 来说连接释放是头等大事对 SQL Server 来说虽然连接池不会立刻释放底层连接但 SqlCommand 和 DataReader 不释放照样会导致下一次查询卡住。ExecuteQuery 用 SqlDataAdapter.Fill 是故意不去手动 Open 连接——Fill 内部会自动打开再关闭连接少写一行是一行。所有 SQL 一律走 SqlParameter 参数数组这是底线。第一层收益是防 SQL 注入第二层收益很多人没意识到如果用字符串拼接日期值会受操作系统区域设置影响中文系统可能拼出稀奇古怪的格式字符串里带单引号更是直接语法错误。参数化之后这些问题全部消失这是血泪经验。3.3 Access 和 SQLite 的连接串差异别把 SqlClient 用到别的库上换数据库时最常犯的错是把 System.Data.SqlClient 拿来连 Access 和 SQLite。Access 要用 System.Data.OleDb连接串长这样ProviderMicrosoft.ACE.OLEDB.12.0;Data Source|DataDirectory|inventory.accdb;Persist Security InfoFalse;SQLite 用 System.Data.SQLite 时连接串长这样Data Sourced:\data\sensor.db;Version3;PoolingTrue;Journal ModeWal;两个细节要注意|DataDirectory|是运行时动态解析的默认指向 exe 所在目录测试阶段指向 bin\Debug。把数据库文件放在子目录时要确认该目录存在并且在安装时正确拷到目标位置否则打开连接直接报找不到文件。SQLite 连接串里的 Journal ModeWal 建议显式写上对并发读有本质改善后面避坑章节会详细说。4. 界面与 CRUDDataGridView BindingSource 的数据绑定与增删改实现界面部分最核心的是表格。WinForms 里的 DataGridView 是效率之王拖一个出来把查询结果 DataTable 直接赋值给它列就自动出来了。很多教程一上来就教建实体类、转 List、再绑定我建议小项目省掉这一步——DataTable 自带行状态和结构信息DataGridView 能直接消费代码量最少调试也直观。等数据量到几万行再考虑换 BindingList 走自定义实体。4.1 查询绑定DataTable 直接赋给 grid.DataSource 为什么够用查询绑定的代码量小到不能再小。我在 Form 的 Load 事件里直接调一个加载方法把查询结果塞给表格private void LoadProductList(string categoryFilter null) { string sql SELECT Id, Name, Category, Price, Stock, UpdatedAt FROM Product WHERE 1 1; var parameters new ListSqlParameter(); if (!string.IsNullOrEmpty(categoryFilter)) { sql AND Category Category; parameters.Add(new SqlParameter(Category, SqlDbType.NVarChar, 20) { Value categoryFilter }); } sql ORDER BY Id DESC; DataTable dt DbHelper.ExecuteQuery(sql, parameters.ToArray()); gridProduct.AutoGenerateColumns false; gridProduct.DataSource dt; }WHERE 1 1是拼动态条件的惯用写法省去判断前面有没有 WHERE 的麻烦后面拼AND Category Category时不用关心语法顺序。参数从 List 转数组空条件时不加参数命令文本也不会出错。AutoGenerateColumns false配合设计器里预先定义的列使用在设计器里编辑列把每列的 DataPropertyName 设置为 SELECT 出来的字段名Id 列设为 Visiblefalse再调整宽度和对齐。这样查出来的数据永远按你预设的格式展示不会每次重新自动生成列。如果界面上除了表格还有一组编辑框要同步显示当前行就给 DataGridView 套一层 BindingSource。它会维护当前行指针把编辑框的 Text 通过 DataBindings 绑到同一数据源的字段上点表格不同行时编辑框自动切换内容不用写一大堆 Click 事件去取值赋值BindingSource bs new BindingSource(); bs.DataSource dt; gridProduct.DataSource bs; txtName.DataBindings.Clear(); txtName.DataBindings.Add(Text, bs, Name); txtPrice.DataBindings.Clear(); txtPrice.DataBindings.Add(Text, bs, Price);注意给同一个控件重复绑定前先 Clear否则第二次加载时会叠出好几个绑定改一个框触发多次值变更界面会莫名跳动。这一点很多人踩过属于 WinForms 绑定的经典暗坑。4.2 增删改的写法与事务边界参数数组、返回行数、批量提交增删改的思路是同一个写 SQL 语句用 SqlParameter 传值调 ExecuteNonQuery根据返回行数判断成功还是失败。以新增为例private void btnAdd_Click(object sender, EventArgs e) { if (!decimal.TryParse(txtPrice.Text, out decimal price)) { MessageBox.Show(价格必须是数字); return; } if (!int.TryParse(txtStock.Text, out int stock)) { MessageBox.Show(库存必须是整数); return; } string sql INSERT INTO Product (Name, Category, Price, Stock, UpdatedAt) VALUES (Name, Category, Price, Stock, GETDATE()); SqlParameter[] ps { new SqlParameter(Name, SqlDbType.NVarChar, 50) { Value txtName.Text.Trim() }, new SqlParameter(Category, SqlDbType.NVarChar, 20) { Value txtCategory.Text.Trim() }, new SqlParameter(Price, SqlDbType.Decimal) { Value price }, new SqlParameter(Stock, SqlDbType.Int) { Value stock } }; int rows DbHelper.ExecuteNonQuery(sql, ps); if (rows 0) { LoadProductList(); MessageBox.Show(保存成功); } }进入 SQL 之前先用 TryParse 挡非法输入避免 decimal.Parse 直接在界面线程上崩掉。参数类型和长度要显式标注特别是 NVarChar 的长度要和表结构一致否则遇到超长输入时 SQL Server 的报错很绕。ExecuteNonQuery 返回受影响行数返回 0 时多半是更新操作的 WHERE 条件没匹配到记录不是 SQL 语句有错排查方向别搞反。当一次操作要写多个表时必须用事务。比如删除一条主记录同时清掉它的明细任一条失败都要回滚否则明细还挂着主表的外键主键删不掉数据处于半删状态。WinForms 里的简单事务写法是这样public static bool ExecuteInTransaction( ListKeyValuePairstring, SqlParameter[] commands) { using (var conn new SqlConnection(ConnStr)) { conn.Open(); using (var tx conn.BeginTransaction()) { try { foreach (var pair in commands) { using (var cmd new SqlCommand(pair.Key, conn, tx)) { if (pair.Value ! null) { cmd.Parameters.AddRange(pair.Value); } cmd.ExecuteNonQuery(); } } tx.Commit(); return true; } catch { tx.Rollback(); throw; } } } }事务里执行的每条命令都必须带上同一个 tx 对象漏掉一个就会变成“这个连接上有多个活动事务”的怪错。提交之前不要做任何界面操作事务越长锁越久SQL Server 默认锁超时一到用户那边的表现就是查询卡了十几秒然后报错。再补充一个 C# 上位机常见的性能场景每秒几十条采集数据要入库。不要一条 INSERT 提交一次把写入放到一个后台队列里攒到 500 条或 1 秒触发一次用一个事务批量执行速度能提升一个数量级。界面线程不要直接碰 SqlConnection用 async/await 调用 ExecuteNonQueryAsync或者用 BackgroundWorker否则 UI 一卡操作员就会怀疑程序死了——这是线程和连接池的配合问题不是数据库的问题。5. 避坑清单VS2015 数据库项目最常见的 5 个翻车现场与排查路径这一章是整篇笔记里最值钱的部分。下面五条坑我都在真实项目里见过一遍以上每一条都按“现象 → 原因 → 解决”的套路写清楚。5.1 VS2015 安装卡死、装完闪退现象安装向导走到“正在配置 Windows 组件”或某个单独组件时长时间停在 0%最终整个安装回滚装完后双击 VS2015 闪退或者弹出 0xc000007b 的错误码。原因多半是没装 with Update 3 的离线版本、镜像文件损坏、系统缺少 VC 运行时库或者安装时杀毒软件锁定了安装器写文件的权限。0xc000007b 这个错误码基本可以断定是系统里 VC 运行库版本混乱。解决先装齐 VC 2008 到 2015 的 x64/x86 运行时库集合再以管理员身份运行 Update 3 离线安装程序并临时关闭实时防护。闪退的话打开 Windows 事件查看器看 Application 日志里异常模块的路径拿到具体是哪个 dll 加载失败再对症处理别盲目重装三遍。5.2 LocalDB 连不上error 26、实例不存在、mdf 附加失败现象调用conn.Open()时抛“A network-related or instance-specific error occurred while establishing a connection to SQL Server”错误码 26或者提示“Cannot open database requested by the login”。原因(localdb)\MSSQLLocalDB这个实例从来没有启动过或者机器上只有旧版 LocalDB实例名是(localdb)\v11.0再或者首次附加 mdf 文件时目录权限不足路径里的文件夹不存在。解决先打开命令提示符执行sqllocaldb info看看机器上到底有哪些实例再执行sqllocaldb start MSSQLLocalDB把它拉起来。连接串保持Integrated SecurityTrue别画蛇添足加用户名密码。用 AttachDbFilename 时确认 mdf 文件所在目录可读写而且 Initial Catalog 的逻辑库名要和 mdf 里的逻辑名一致。这块向来有点玄学最省事的办法是别附加直接在服务实例里建一个库然后连过去。5.3 Access 报错未在本地计算机上注册 Microsoft.ACE.OLEDB.12.0现象用 OleDbConnection 连 .accdb 文件时运行时抛“Microsoft.ACE.OLEDB.12.0 未在本地计算机上注册”。原因64 位 Windows 上项目平台目标是 AnyCPUCLR 按 64 位进程加载但常见的 Microsoft Access Database Engine 驱动只有 32 位版本64 位进程加载不到 32 位驱动。解决最稳的做法是右键项目进 Properties Build把 Platform target 改成 x86重新编译。不要试图在装了 32 位 Office 的机器上再装 64 位 ACE 驱动两套驱动混装会互相覆盖越修越乱。判断方法也简单同一个 exe 在只装了 32 位 Office 的老机器上正常在纯 64 位系统上报这个错基本就是位数问题改平台目标即可。5.4 SQLite 高频插入报 database is locked 或卡顿现象批量写入时报“SQLite error: database is locked”或者程序运行几天后查询明显变慢数据库文件越来越大。原因SQLite 同一时间只允许一个写者代码里连接没有及时关闭多个进程或线程同时写同一个库还有一种情况是 WAL 日志文件不断膨胀没有定期 checkpoint。解决所有写操作强制用 using 短连接写完立刻释放显式开启 WAL 模式执行PRAGMA journal_modeWAL;高频插入时用事务批量提交1000 条 INSERT 放在一个事务里比 1000 次独立提交快一个数量级。定期执行PRAGMA wal_checkpoint(TRUNCATE);控制文件大小。另外 System.Data.SQLite 的 SQLite.Interop.dll 位数必须和项目平台一致否则程序加载时直接崩连报错都省了。5.5 DataGridView 绑定后不刷新、列头错乱现象重新查询并赋了新的 DataTable 后表格还是显示旧数据或者第一次正常第二次列宽全变、列头重复。原因BindingSource 缓存了旧值没有重置DataGridView 的 AutoGenerateColumns 开着每次赋新表都重新生成列和设计器里预定义的列混在一起。解决重新赋值前先gridProduct.DataSource null;再赋新表把AutoGenerateColumns false固定列全部在设计器里预定义并设置 DataPropertyName用 BindingSource 时刷新后调用bs.ResetBindings(false)。这三步做齐基本不会再出现“表格带记忆”的怪事。6. 部署收尾把连接串交给用户、三种分发方式与上线前验证清单6.1 连接串外置与全局日志收尾前最值得做的两件事程序做到最后一步是发布。我这几年养成的习惯是连接串永远放在 exe.config 里并且把配置做成用户可见的“数据库设置”窗口——服务器实例、数据库名、账号密码用户改了存回 config程序下次启动生效。这样客户换服务器、换数据库文件位置不需要找开发改代码重新编译。对连接串做 DPAPI 加密在单机项目里意义有限解密密钥就在同一台机器上徒增运维复杂度。如果配置项特别多有人会用 Newtonsoft.Json 把配置挪到 JSON 文件里但连接串这类系统级配置留在 app.config 更合适因为 .NET 原生就能读不引入额外依赖。上线之后最怕客户描述不清错误。我在 Main 方法里挂两个全局异常钩子把堆栈写到 exe 同目录的 log 文件再弹一个友好提示[STAThread] static void Main() { Application.ThreadException (s, e) LogHelper.Write(e.Exception.ToString()); AppDomain.CurrentDomain.UnhandledException (s, e) LogHelper.Write(e.ExceptionObject.ToString()); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); }ThreadException 管界面线程的未捕获异常UnhandledException 管后台线程的未捕获异常两者都写上拿到的日志至少包含异常类型和堆栈行号排查效率能提高一倍。6.2 分发方式对比表与上线验证清单Windows 项目分发无外乎三种各有各的坑方式适用主要坑ClickOnce内网多客户端需要频繁更新发布要签名证书过期要重新配置安装项目InstallShield/VS Installer需要注册服务、安装数据库引擎LocalDB runtime 必须先装包体积大xcopy 绿色目录单机、上位机、U 盘拷贝OLEDB/SQLite 依赖位数必须一致config 跟着走如果数据库选 SQLite绿色目录是最舒服的exe、dll、db 三个文件一个文件夹拷走就是备份。选 LocalDB 就得在目标机器上装 LocalDB runtime安装项目里要把这个前置条件写清楚否则客户双击 exe 直接报连接失败。交付前我用一张清单过一遍免得在现场翻车检查项做法首次安装在全新虚拟机按客户步骤完整走一遍首次连库确认 config 路径和数据库文件位置增删改每个操作后去数据库里查一遍落库异常输入空字符串、超长文本、非法数字中途断电事务测试要回滚干净备份恢复把数据库文件拷走再放回能正常打开最后说一个我吃过亏的教训早期做小项目图省事把连接串写死在代码里客户换了一台服务器我在电话里指挥他改了半天才找到文件在哪。后来所有项目强制连接层独立成类、配置外置、日志留痕看起来多花半小时省掉的全是现场运维的时间。如果你的项目也卡在同样的十字路口不妨先把这里的连接层和事务写法抄下来跑一遍再决定要不要继续投入。希望帮到你。本文还有配套的精品资源点击获取
返回列表