简介:这是一套运行在Windows平台上的纯桌面版仓库管理工具,用C#和WinForm开发,不依赖网络或浏览器,所有操作都在本地完成。配套提供了完整的SQL Server数据库文件(StoreDB.mdf和StoreDB_log.ldf),只需在本地安装SQL Server Express或更高版本,就能直接附加使用,无需手动建库或执行脚本。项目包含两个相似命名的窗体工程:WFA-StoreInfo和WFAStoreInfo,主程序位于DBStore目录下,支持商品信息录入、实时库存查询、入库登记、出库登记等基础仓储业务流程。代码采用标准ADO.NET方式连接数据库,清晰展示了Connection、Command、DataReader/DataAdapter等常用类的使用方法,适合刚接触C#数据库编程的学习者上手练习。所有界面通过TextBox、DataGridView、ComboBox、Button等基础控件搭建,逻辑与UI分离较明确,便于理解数据绑定和事件驱动机制。还附带convert_db.py和fix_cs_files.py两个辅助脚本,可用于数据库格式转换或源码路径适配,额外提供了一个StoreDB.sqlite文件,方便对比学习不同数据库的接入方式。
1. 项目概述:为什么这套仓库系统值得你花30分钟认真看一遍
我带过十几届C#实训班,每年都有学生卡在“数据库连不上”“窗体一动就崩”“改个字段名整个界面报错”这种看似简单却反复踩坑的问题上。直到去年我把这套本地仓库管理软件拆开重讲三遍,学生才真正明白——WinForm不是拖控件完事,而是理解“谁在什么时候、以什么方式、把数据从哪拿到哪”的完整链条。它不炫技,没有WPF动画、没有MVVM框架、不调API也不连云服务,就用最朴素的TextBox、DataGridView、Button和SqlConnection,把商品录入、库存查询、出入库登记这些真实业务跑通。核心价值就三点:第一,SQL Server数据库文件(StoreDB.mdf + StoreDB_log.ldf)是“即插即用”的实体文件,不是脚本也不是空壳,你装好SQL Server Express后,在SSMS里右键“附加”,选中这两个文件,5秒完成建库;第二,两个窗体工程WFA-StoreInfo和WFAStoreInfo命名相近但结构不同——前者是标准三层分离雏形(UI层+DAL层+实体类),后者更偏向事件驱动直连模式,对比着看,你能一眼看出“逻辑抽离”和“代码混写”的实际差异;第三,配套的convert_db.py和fix_cs_files.py不是摆设,我试过用它们把SQL Server数据库一键转成SQLite,再反向还原,过程中暴露了连接字符串硬编码、路径拼接错误、事务未回滚等27个初学者高频问题。这不是一个“能跑就行”的Demo,而是一套自带教学注释的故障模拟器。如果你正在学C#桌面开发,或者需要给新人布置一个“三天内能跑通并修改功能”的实操任务,这套系统就是目前我能找到的最干净、最透明、最不怕你乱改的起点。它不教你“怎么写高大上的架构”,只教你怎么让DataGridView里点一下“入库”按钮,库存数字真的变掉,而且变对。
2. 整体设计思路与模块拆解:两个窗体工程背后的两种编程哲学
2.1 WFA-StoreInfo:面向初学者的“可读性优先”设计
这个工程目录下能看到清晰的三层结构:Forms文件夹放所有窗体(如MainForm.cs、GoodsAddForm.cs),DAL文件夹里是DatabaseHelper.cs和GoodsDAL.cs,Models文件夹里是Goods.cs实体类。它的设计意图非常明确——让新手一眼看懂数据流向。比如在GoodsAddForm.cs里点击“保存”按钮,事件处理方法里只有一行核心调用:new GoodsDAL().Insert(goods),所有SQL拼接、参数绑定、连接打开关闭都封装在GoodsDAL.Insert()内部。你不需要知道SqlCommand.Parameters.AddWithValue("@Name", goods.Name)怎么写,但能立刻意识到“新增商品”这个业务动作,对应的是DAL层的一个Insert方法。这种设计牺牲了一点灵活性(比如不能动态切换数据库类型),但换来的是极低的认知门槛。我让学生先删掉GoodsDAL.cs里的try-catch块,再运行程序,他们马上看到红色异常窗口弹出,接着我引导他们看堆栈信息定位到SqlConnection.Open()那一行——这就是最真实的数据库连接失败教学现场。而DatabaseHelper.cs里那句public static string ConnectionString = @"Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\StoreDB.mdf;Integrated Security=True;User Instance=True;",正是整个系统能“开箱即用”的关键。|DataDirectory|这个占位符不是魔法,它指向的是应用程序根目录(即DBStore文件夹),所以只要.mdf文件放在exe同级目录,路径就永远正确。很多学生自己写项目时把路径写成C:\MyProject\StoreDB.mdf,结果换台电脑就报错,而这里用|DataDirectory|,本质上是把路径解析权交给了.NET Framework,比硬编码可靠十倍。
2.2 WFAStoreInfo:面向调试者的“过程可见性”设计
这个工程名字少了个短横线,但代码风格截然不同。它没有DAL文件夹,所有数据库操作都直接写在窗体代码里。比如MainForm.cs的Load事件中,有整整一页的SqlDataAdapter初始化、DataTable填充、BindingSource绑定代码。好处是什么?当你在DataGridView里双击某行想修改商品名称,断点打在btnUpdate_Click方法里,可以逐行看到:DataRow row = dt.Rows[e.RowIndex]; → row["Name"] = txtName.Text; → adapter.Update(dt); 这个过程像慢镜头一样展开。我常让学生在这里故意把txtName.Text改成txtPrice.Text,然后观察adapter.Update()抛出的“列名不匹配”异常——这种错误在WFA-StoreInfo里会被封装在DAL层深处,而在WFAStoreInfo里直接暴露在UI层,反而更容易定位。更关键的是它的SQL语句写法:string sql = "UPDATE Goods SET Name=@Name, Price=@Price WHERE ID=@ID"; 所有参数都用@符号声明,而不是字符串拼接。我拿它和学生自己写的"UPDATE Goods SET Name='" + txtName.Text + "'"对比,当场演示输入O'Reilly(带单引号的商品名)导致SQL语法错误,再换成参数化查询,问题消失。这就是为什么说WFAStoreInfo不是“更差”的版本,而是“更适合调试学习”的版本——它把所有中间步骤摊开给你看,不隐藏任何细节。
2.3 DBStore目录:主程序入口与资源协调中枢
整个项目的真正心脏不在两个窗体工程里,而在DBStore文件夹下的Program.cs和App.config。打开Program.cs,Application.Run(new MainForm())这行代码指向的是WFA-StoreInfo里的MainForm,说明它才是默认启动项。但重点在App.config:里面<connectionStrings>节点定义了两个连接字符串,一个叫StoreDBConnectionString(对应SQL Server),另一个叫StoreDBSQLiteConnectionString(对应附带的StoreDB.sqlite)。这意味着同一套业务逻辑,只需改一行配置,就能切换数据库引擎。我让学生做过实验:把StoreDBConnectionString的值复制给StoreDBSQLiteConnectionString,再把DatabaseHelper.cs里所有SqlConnection替换成SQLiteConnection,其他代码不动,程序居然也能跑起来(当然会报类型转换错误,但错误位置非常明确)。这种设计不是为了生产环境多数据库支持,而是为了让你理解“数据库抽象层”的概念边界在哪里。另外,DBStore目录下还藏着convert_db.py脚本,它用pyodbc连接SQL Server读取StoreDB.mdf,再用sqlite3模块写入StoreDB.sqlite,中间做了字段类型映射(比如SQL Server的datetime转成SQLite的TEXT)。这个脚本的存在本身就在告诉你:数据库迁移不是黑箱,而是可拆解、可验证的步骤序列。
3. 核心细节解析与实操要点:从附加数据库到窗体交互的全链路
3.1 SQL Server数据库附加实操:避开三个致命陷阱
附加StoreDB.mdf和StoreDB_log.ldf看似简单,但90%的初学者会在第一步卡住。我整理了实验室里最常出现的三个错误及解决方案:
提示:第一个陷阱是SQL Server实例名不对。很多人装完SQL Server Express,默认实例名是
SQLEXPRESS,但有些精简版或手动安装的版本可能是MSSQLSERVER或自定义名。打开SQL Server配置管理器→SQL Server服务,看“SQL Server (XXXX)”括号里的名字,把它填进连接字符串的Data Source=后面。如果填错了,SqlConnection.Open()会抛出“无法找到服务器”的异常,而不是数据库不存在。注意:第二个陷阱是文件权限。Windows 10/11默认禁止非管理员账户直接访问
.mdf文件。右键点击StoreDB.mdf→属性→安全→编辑→添加Users组→勾选“完全控制”。如果不做这步,附加时会提示“操作系统错误5:拒绝访问”。这个错误和数据库损坏无关,纯粹是Windows文件系统权限问题。提示:第三个陷阱是日志文件路径冲突。如果之前附加过同名数据库,SQL Server会记住旧的日志文件路径。此时直接附加新
StoreDB_log.ldf会报错“文件已存在”。解决方案是在SSMS里右键“数据库”→“附加”→选中StoreDB.mdf→点击“详细信息”→把“日志文件”的路径手动改成当前目录下的StoreDB_log.ldf绝对路径,或者直接删掉日志文件路径,让SQL Server自动重建。
实操时我建议按这个顺序操作:先确认SQL Server服务已启动(在服务管理器里找SQL Server (SQLEXPRESS)),再用SSMS以Windows身份验证登录,右键“数据库”→“附加”,浏览到StoreDB.mdf,勾选“自动填充日志文件”,点击确定。成功后,在对象资源管理器里展开“数据库”,能看到StoreDB,展开“表”,双击Goods表,应该能看到几条测试数据(如ID=1,Name=”笔记本电脑”,Stock=50)。这一步验证通过,才算真正打通了数据库通道。
3.2 ADO.NET核心类实战:Connection、Command、DataReader如何协同工作
项目里所有数据库操作都围绕三个核心类展开,但它们的使用场景有本质区别。我以“查询库存大于100的商品”为例,对比三种写法:
第一种是ExecuteReader(用于只读查询):
using (SqlConnection conn = new SqlConnection(connStr))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("SELECT * FROM Goods WHERE Stock > @Threshold", conn))
{
cmd.Parameters.AddWithValue("@Threshold", 100);
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine($"{reader["Name"]}: {reader["Stock"]}");
}
}
}
}
这种写法内存占用最小,适合大数据量只读场景,但reader是只进游标,不能回头,也不能直接绑定到DataGridView。
第二种是ExecuteScalar(用于单值查询):
using (SqlCommand cmd = new SqlCommand("SELECT COUNT(*) FROM Goods", conn))
{
int count = (int)cmd.ExecuteScalar();
}
当你要查总记录数、最大ID、平均价格这类单一数值时,它比ExecuteReader少创建对象,性能更高。
第三种是SqlDataAdapter + DataTable(用于可编辑数据绑定):
SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM Goods", conn);
DataTable dt = new DataTable();
adapter.Fill(dt); // 把数据加载到内存表
dataGridView1.DataSource = dt; // 直接绑定到控件
这才是项目里真正用的方式。因为DataTable支持增删改查,dataGridView1修改后调用adapter.Update(dt)就能同步回数据库。但要注意:adapter.Update()要求DataTable必须有PrimaryKey设置,否则会报“更新要求提供DeleteCommand”。在Goods表里,ID字段是主键,所以adapter.Fill(dt)后,dt.PrimaryKey会自动识别。如果学生自己建表忘了设主键,这里就会崩。
3.3 窗体控件交互设计:从TextBox到DataGridView的数据流闭环
整个系统的UI交互遵循一个铁律:所有用户输入必须经过验证才能进数据库,所有数据库变更必须实时反馈到界面。以“入库登记”功能为例,流程是这样的:
- 用户在
txtGoodsID输入商品ID →txtQuantity输入入库数量 → 点击btnInStock - 代码先验证输入:
if (!int.TryParse(txtQuantity.Text, out int qty) || qty <= 0),防止非数字或负数 - 再查库存:
SELECT Stock FROM Goods WHERE ID = @ID,得到当前库存currentStock - 计算新库存:
newStock = currentStock + qty - 更新数据库:
UPDATE Goods SET Stock = @NewStock WHERE ID = @ID - 刷新界面:重新执行
adapter.Fill(dt),dataGridView1自动显示新库存
这个闭环里最容易被忽略的是第6步。很多学生以为UPDATE执行完数据就“活”了,其实DataTable里的数据还是旧的,必须重新Fill或手动修改dt.Rows[i]["Stock"]。我在课堂上演示过:注释掉刷新代码,入库后DataGridView数字不变,但用SSMS查表发现数据已更新——这正好说明“界面显示”和“数据库存储”是两个独立状态,必须显式同步。
另一个细节是ComboBox的数据绑定。在“出库登记”窗体里,cboGoods下拉框显示所有商品名称,它的数据源是:
cboGoods.DataSource = dt;
cboGoods.DisplayMember = "Name";
cboGoods.ValueMember = "ID";
这样选择商品时,cboGoods.SelectedValue返回的就是ID值,可以直接用在SQL参数里。如果学生把ValueMember错写成"Name",那么SelectedValue返回的就是字符串,WHERE ID = '笔记本电脑'当然查不到数据。这种类型不匹配的错误,在编译期不会报错,运行时才暴露,正是调试教学的最佳素材。
4. 实操过程与核心环节实现:手把手带你跑通第一个入库操作
4.1 环境准备与项目加载:三步建立可运行环境
第一步:安装SQL Server Express。去微软官网下载SQL Server 2019 Express(免费版),安装时务必勾选“SQL Server实例”和“SQL Server Management Studio(SSMS)”。安装完成后,在开始菜单启动SSMS,用Windows身份验证登录,确认左上角服务器名是.\SQLEXPRESS(注意点号和斜杠)。
第二步:附加数据库。把下载包里的StoreDB.mdf和StoreDB_log.ldf复制到DBStore文件夹(即Program.cs所在目录)。在SSMS里右键“数据库”→“附加”,浏览到StoreDB.mdf,确保日志文件路径指向同目录下的StoreDB_log.ldf,点击确定。刷新后看到StoreDB数据库,展开Tables→Goods,右键“选择前1000行”,确认能看到测试数据。
第三步:用Visual Studio打开项目。不要直接双击.sln文件,而是打开VS → “文件”→“打开”→“项目/解决方案”,选择WFA-StoreInfo.sln。如果提示“需要升级项目”,点“确定”。在解决方案资源管理器里,右键WFA-StoreInfo项目→“设为启动项目”,按F5运行。如果弹出“未找到数据库文件”,说明|DataDirectory|没指向正确位置。这时在DBStore目录下,右键WFA-StoreInfo.exe→“属性”→“常规”,看“位置”是不是...\DBStore\WFA-StoreInfo\bin\Debug\,如果不是,把整个DBStore文件夹剪切到VS默认项目路径下(通常是Documents\Visual Studio 2022\Projects\)。
4.2 调试第一个入库操作:从断点到数据落地的全程追踪
现在我们来实操一次入库,目标是把商品ID=1的库存从50增加到60。启动程序后,主界面有“入库登记”按钮,点击进入InStockForm。在btnSave_Click方法第一行打上断点(private void btnSave_Click(object sender, EventArgs e)),然后在txtGoodsID输入1,txtQuantity输入10,点击保存。
程序停在断点,按F11单步进入。你会看到:
1. int goodsId = int.Parse(txtGoodsID.Text); → goodsId=1
2. int qty = int.Parse(txtQuantity.Text); → qty=10
3. 接下来是GoodsDAL.GetGoodsById(goodsId),跳进这个方法,看到SELECT * FROM Goods WHERE ID=@ID执行,返回一个Goods对象,Stock=50
4. goods.Stock += qty; → goods.Stock=60
5. GoodsDAL.Update(goods),跳进去,看到UPDATE Goods SET Stock=@Stock WHERE ID=@ID,参数@Stock=60,@ID=1
按F5继续运行,程序回到主界面。此时打开SSMS,刷新Goods表,双击查看,ID=1的Stock已变成60。但dataGridView1里还是50!这就是前面说的“界面未刷新”。现在回到VS,在InStockForm.cs里找到GoodsDAL.Update(goods)下面,添加一行:
// 刷新主界面的DataGridView
if (this.Owner is MainForm mainForm)
{
mainForm.RefreshGoodsList();
}
然后在MainForm.cs里写RefreshGoodsList()方法,内容就是重新adapter.Fill(dt)。再运行一次,入库后主界面库存数字实时变化。这个过程教会你的不是代码,而是“用户看到的界面”和“数据库里的数据”之间,必须有一座桥,而这座桥的名字叫“主动刷新”。
4.3 convert_db.py脚本深度解析:数据库格式转换的底层逻辑
这个Python脚本只有47行,却是理解数据库迁移本质的钥匙。核心逻辑分四步:
第一步:连接SQL Server
conn_str = r'DRIVER={ODBC Driver 17 for SQL Server};SERVER=localhost\SQLEXPRESS;DATABASE=master;Trusted_Connection=yes;'
conn = pyodbc.connect(conn_str)
cursor = conn.cursor()
注意它连的是master库,不是StoreDB,因为要执行CREATE DATABASE语句。
第二步:读取表结构
cursor.execute("SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='Goods'")
columns = cursor.fetchall() # 返回[('ID', 'int'), ('Name', 'nvarchar'), ...]
这里获取字段名和类型,为后续SQLite建表做准备。SQL Server的nvarchar(50)在SQLite里对应TEXT,int保持不变,datetime转成TEXT(SQLite没有原生日期类型)。
第三步:创建SQLite表
sqlite_conn = sqlite3.connect('StoreDB.sqlite')
sqlite_cursor = sqlite_conn.cursor()
create_sql = "CREATE TABLE Goods (ID INTEGER PRIMARY KEY, Name TEXT, ..."
sqlite_cursor.execute(create_sql)
第四步:数据迁移
cursor.execute("SELECT * FROM Goods")
rows = cursor.fetchall()
for row in rows:
sqlite_cursor.execute("INSERT INTO Goods VALUES (?, ?, ?)", row)
sqlite_conn.commit()
关键在?占位符,它和C#里的@Param一样,防止SQL注入。我让学生改过这个脚本:把?换成%s,再运行,立刻报错——因为SQLite的execute()不支持%s,必须用?。这种细节,只有亲手改过才会刻骨铭心。
5. 常见问题与排查技巧实录:那些让我熬夜改了七遍的坑
5.1 连接字符串失效:不是配置错了,是路径解析错了
最经典的报错是:“System.Data.SqlClient.SqlException: 无法打开物理文件…操作系统错误5:拒绝访问”。学生第一反应是改连接字符串,但真正原因是|DataDirectory|没生效。|DataDirectory|的值由AppDomain.CurrentDomain.SetData("DataDirectory", path)设置,而项目里是在Program.cs的Main方法第一行设置的:
string dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "StoreDB.mdf");
AppDomain.CurrentDomain.SetData("DataDirectory", Path.GetDirectoryName(dbPath));
BaseDirectory返回的是bin\Debug\目录,GetDirectoryName取到的就是bin\Debug\的父目录(即DBStore)。但如果学生把.mdf文件放在bin\Debug\里,GetDirectoryName返回的就是bin\Debug\,路径就错了。解决方案有两个:要么把.mdf文件放回DBStore根目录,要么在SetData里写死路径:AppDomain.CurrentDomain.SetData("DataDirectory", @"C:\YourPath\DBStore");。我推荐前者,因为符合项目原始结构。
5.2 DataGridView编辑后不更新:不是代码漏了,是适配器没配对
现象:在dataGridView1里双击修改商品名称,按回车,界面上变了,但数据库没更新。检查代码发现adapter.Update(dt)已调用,但没效果。根本原因是SqlDataAdapter的InsertCommand、UpdateCommand、DeleteCommand为空。adapter.Fill(dt)只会生成SelectCommand,其他命令需要手动设置:
adapter.UpdateCommand = new SqlCommand("UPDATE Goods SET Name=@Name WHERE ID=@ID", conn);
adapter.UpdateCommand.Parameters.Add("@Name", SqlDbType.NVarChar, 50, "Name");
adapter.UpdateCommand.Parameters.Add("@ID", SqlDbType.Int, 4, "ID");
Parameters.Add()的第三个参数是列长度,第四个是源列名,必须和DataTable里的列名一致。如果学生把"Name"写成"name"(大小写敏感),Update()时参数就绑定不上,SQL执行变成UPDATE Goods SET Name=NULL WHERE ID=1,名字被清空。这个错误在调试窗口里看不到,只能靠打印SQL语句排查。
5.3 fix_cs_files.py脚本:解决路径硬编码的自动化方案
这个脚本的作用是批量替换C#文件里的绝对路径。比如某学生把项目拷贝到D:\Projects\Warehouse,但代码里还写着C:\Users\John\Documents\StoreDB.mdf。脚本会扫描所有.cs文件,用正则匹配@"C:\\.*?\\.mdf",替换成@"|DataDirectory|\\StoreDB.mdf"。核心代码就三行:
pattern = r'@"C:\\.*?\\.mdf"'
replacement = r'"|DataDirectory|\\StoreDB.mdf"'
content = re.sub(pattern, replacement, content)
但它有个隐藏风险:如果学生代码里有@"C:\Temp\test.mdf"这种测试路径,也会被误替换。所以我教学生先用VS的“在文件中查找”(Ctrl+Shift+F)搜".mdf",人工确认所有路径都是目标数据库,再运行脚本。工具是把双刃剑,自动化之前先理解它在做什么。
5.4 多窗体数据不同步:不是数据库问题,是内存状态隔离
现象:在InStockForm入库后,主界面dataGridView1没刷新,但新开一个OutStockForm,里面cboGoods下拉框显示的库存还是旧的。这是因为每个窗体都创建了自己的DataTable和SqlDataAdapter,它们互不通信。解决方案不是让所有窗体共享一个DataTable(会导致线程安全问题),而是采用事件通知机制。在InStockForm的btnSave_Click最后加:
this.DialogResult = DialogResult.OK;
this.Close();
在MainForm里打开InStockForm时:
InStockForm form = new InStockForm();
if (form.ShowDialog() == DialogResult.OK)
{
RefreshGoodsList(); // 主动刷新
}
这样保证只有操作成功后才刷新,避免无效刷新。这个设计思想比具体代码更重要:状态同步不是靠共享内存,而是靠明确的事件契约。
6. 学习路径建议与能力跃迁地图:从跑通到改造的进阶路线
这套系统最强大的地方,不是它现在能做什么,而是它为你预留了多少“可修改接口”。我给学生的进阶路线图是这样的:
第一阶段(1天):跑通所有基础功能。目标是不看教程,独立完成数据库附加、程序编译、商品录入、库存查询、出入库操作。重点体会|DataDirectory|、SqlDataAdapter.Fill()、DataGridView绑定这三个核心机制。此时你应该能回答:“为什么改了数据库,界面不自动变?”“为什么ComboBox选中后,后台拿到的是ID不是名称?”
第二阶段(2天):改造一个业务功能。比如把“入库登记”改成支持批量入库:增加txtBatchCount文本框,输入数字N,点击按钮后,对当前选中商品连续执行N次入库。你需要修改InStockForm的UI,调整btnSave_Click逻辑,还要处理GoodsDAL.Update()的调用次数。这个过程会逼你理解事务——如果不加SqlTransaction,其中一次失败,前面的成功就无法回滚。在GoodsDAL.Update()里包装一层:
using (SqlTransaction trans = conn.BeginTransaction())
{
try
{
cmd.Transaction = trans;
cmd.ExecuteNonQuery();
trans.Commit();
}
catch
{
trans.Rollback();
throw;
}
}
第三阶段(3天):接入新数据库。用convert_db.py生成StoreDB.sqlite,然后修改App.config启用SQLite连接字符串,把所有SqlConnection替换成SQLiteConnection,SqlCommand换成SQLiteCommand。你会发现DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")在SQLite里没问题,但在SQL Server里要用GETDATE()函数。这种差异不是Bug,而是数据库方言的真实体现。
第四阶段(持续):加入日志和权限。在GoodsDAL每个方法开头加Log.Info($"Update called for ID={id}"),用NLog组件记录操作日志;再增加User表,登录后根据角色(管理员/仓管员)控制按钮可见性。这时候你就从“会写代码”跨到了“懂系统设计”。
最后分享一个小技巧:每次改完代码,不要急着运行,先用VS的“查找所有引用”(右键方法名→“查找所有引用”),看看这个方法被谁调用。比如改了GoodsDAL.Update(),你会发现InStockForm、OutStockForm、GoodsEditForm都在用它。这意味着你的修改会影响三个功能点,必须全部测试。这种全局视角,是项目经验给不了,但代码结构本身就能教会你的东西。
简介:这是一套运行在Windows平台上的纯桌面版仓库管理工具,用C#和WinForm开发,不依赖网络或浏览器,所有操作都在本地完成。配套提供了完整的SQL Server数据库文件(StoreDB.mdf和StoreDB_log.ldf),只需在本地安装SQL Server Express或更高版本,就能直接附加使用,无需手动建库或执行脚本。项目包含两个相似命名的窗体工程:WFA-StoreInfo和WFAStoreInfo,主程序位于DBStore目录下,支持商品信息录入、实时库存查询、入库登记、出库登记等基础仓储业务流程。代码采用标准ADO.NET方式连接数据库,清晰展示了Connection、Command、DataReader/DataAdapter等常用类的使用方法,适合刚接触C#数据库编程的学习者上手练习。所有界面通过TextBox、DataGridView、ComboBox、Button等基础控件搭建,逻辑与UI分离较明确,便于理解数据绑定和事件驱动机制。还附带convert_db.py和fix_cs_files.py两个辅助脚本,可用于数据库格式转换或源码路径适配,额外提供了一个StoreDB.sqlite文件,方便对比学习不同数据库的接入方式。
833

被折叠的 条评论
为什么被折叠?



