【工控上位机】C#编写扫码产品数据入库程序

生产流程

产品被组织成三个层级:大箱码、小箱码和产品码,其中大箱码下关联有多个小箱码,每个小箱码下又关联有多个产品码。在扫码入库的流程中,机器读码器会一直扫描产品的二维码,而箱码需要人工手动干预进行添加,添加时机器不会停止运作。
难点在于产品的二维码是按照倒叙添加的,因为数据库中每个层级一张表格,表格之间相互关联,所以在插入数据时如果没有上级表格的副键会很难建立联系。解决方法有两个,一是建立临时表,等到大箱子的二维码存入到临时文件中时,再将临时文件插入到数据库中。二是每扫描到产品码就插入到数据库中,表格的副键设置为空,等到上一级二维码更新后再将表格中为空的数据更新。(下面的流程是以第一个方法为主,结果到客户那提了新需求后又只能用第二个方法了....

程序实现思路

整体分为三个步骤,提取扫码枪数据、建立临时文件存储数据、存入数据库。首先第一个步骤扫码枪的连接方式是USB所以读取数据非常方便,读码器会模拟键盘输入,只要用C#读取键盘输入数据就可以。第二步骤建立临时文件,临时文件使用.CSV文件或者.JSON文件,CSV更简洁JSON文件有层级类似于一个数组的样子,所以这里用的JSON文件因为数据量也不是很大。最后存入数据库就使用循环,每个产品都使用查询指令存入,不过好像批量存入可以用SqlBulkCopy。

数据库建表

三张表,每个表都有自增ID,而且大箱子会和小箱子的表中ID关联更新与删除

CREATE TABLE BigBoxes (  
    BigID INT IDENTITY(1,1) PRIMARY KEY,  --自增ID--
    BigCode NVARCHAR(255) NOT NULL UNIQUE, -- 大箱子吧,设为唯一  
);
CREATE TABLE Boxes (  
    BoxID INT IDENTITY(1,1) PRIMARY KEY,  
    BoxCode NVARCHAR(255) NOT NULL UNIQUE, -- 箱码,设为唯一  
    BigID INT, -- 外键,关联大箱子表  
    CONSTRAINT FK_Boxes_BigBoxes FOREIGN KEY (BigID)  
        REFERENCES BigBoxes(BigID)  
        ON DELETE CASCADE -- 如果大的被删除,关联的箱也会被删除  
        ON UPDATE CASCADE, -- 如果大的ID更新,关联的箱ID也会更新  
);
CREATE TABLE Products (  
    ProductID INT IDENTITY(1,1) PRIMARY KEY,  
    ProductCode NVARCHAR(255) NOT NULL UNIQUE, -- 产品码,设为唯一  
    BoxID INT, -- 外键,关联箱表  
    CONSTRAINT FK_Products_Boxes FOREIGN KEY (BoxID)  
        REFERENCES Boxes(BoxID)  
        ON DELETE CASCADE -- 如果箱被删除,关联的产品也会被删除  
        ON UPDATE CASCADE -- 如果箱ID更新,关联的产品ID也会更新  
);

关于自增ID的问题,假如0自增ID到100而之前的数据被删除的话,下一次自增还会从100开始,于是使用下面的代码重置自增ID

DBCC CHECKIDENT ('boxes', RESEED, 0);
#重置boxes箱子中的自增ID为0

提取扫码枪数据

因为扫码枪读取数据就是模拟键盘输入,所以就用了一个简单的textbox控件来获取扫码枪的输入,其中要注意的就是焦点的变化,可以使用按钮来触发启动锁定焦点在文本框上。还有在第一次扫码输入到文本框后,下一次扫码要覆盖当前文本框的内容,这里因为扫码枪会在扫码后添加回车,所以事件用回车做判断,而且有一个记录器state来记录是否上次输入了回车,如果上次输入过回车就在输入前会把文本框清空再输入
技术变化的好快啊,搜扫码枪的时候还看到了通过串行接口来获取数据的方法好麻烦,用USB来获取数据的方式好方便

这一段是对一次扫码完成后的操作,当一次扫码后回车触发事件
        private void Box1_KeyDown(object sender, KeyEventArgs e)//输入回车字符时触发事件
        {
            if (state == 1)//这个状态好像可以用布尔值来表示,因为只有0和1...
            {
                Box1.Clear();
                state = 0;
            }
            if (e.KeyCode == Keys.Enter)
            {
                state = 1;//输入过回车就会记录下来,等到下次有按键触发时就会先清空
                inputBuffer = Box1.Text;//inputBuffer变量来记录文本中的数据
                richTextBox1.AppendText(inputBuffer + "\n");
                richTextBox1.ScrollToCaret();
                judge(); //这个是判断方法,后面用来根据输入的次数来判断是大小箱码
            }
        }

建立临时文件存储数据库

创建的JSON文件存储着产品码与小箱码的数据,最后读码获取的大箱码并没有存储,而是存储在一个变量中,到时候插入数据库的时候直接使用存储的变量作为插入语句。其实也可以存储在文件中,感觉这样好像比较规范,不过用变量存储的方法代码写起来比较方便就是了。

这一段是将数据添加到JSON文件中

using Newtonsoft.Json;

 public void insertJson(string code, string now)//向json文件中加入数据,如果没有文件就新建
        {
            if (File.Exists(jsonFilePath))
            {
            //json文件的读取与反序列化
                string json = File.ReadAllText(jsonFilePath);
                ProductData productData = JsonConvert.DeserializeObject<ProductData>(json);

                if (now == "product")  //判断加入的是箱码还是产品码
                {
                    productData.产品.Add(code);
                }
                else if (now == "box")
                {
                    productData.箱码 = code;
                }
                // 保存修改后的数据
                string output = JsonConvert.SerializeObject(productData, Formatting.Indented);
                File.WriteAllText(jsonFilePath, output);
				//数据序列化后写入文本
                MessageBox.Show("文件写入成功");
            }
            else
            {
                MessageBox.Show("文件不存在!");
                creatJson();
                insertJson(code, now);
            }
        }

最初我也想过用.txt格式或者excel的文件来作为临时文件,就是txt文件中存储着产品码,文件名是箱码,存储在大箱子的文件夹中,因为只是起到一个临时存储防止断电或者重启数据消失的作用,而且对文件夹的操作也是自己熟悉的。。。

存入数据库部分

读取JSON文件并且使用查询语句插入到数据库表中,数据库语句感觉也挺麻烦的,存入大箱码后会自动生成ID,然后在存入箱码时声明一个变量存储大码的ID,再将ID与箱码对应存入表中,产品码也是,都是两层的循环。

using (SqlConnection sqlConnect = new SqlConnection(connectionString))
            {
                try
                {
                    sqlConnect.Open();
                    
                    MessageBox.Show(BigCode);
                    string query = $"INSERT INTO BigBoxes (BigCode) " +
                                    $"VALUES('{BigCode}'); ";

                    SqlCommand sql1 = new SqlCommand(query, sqlConnect);

                    int result = sql1.ExecuteNonQuery(); // 执行 SQL 命令  
                    
                    foreach (var filepath in filesPath)
                    {
                        #region 箱码插入
                        // 读取文件内容  
                        string jsonContent = File.ReadAllText(filepath);
                        // 反序列化JSON到ProductData对象  
                        ProductData productData = JsonConvert.DeserializeObject<ProductData>(jsonContent);
                        string box = productData.箱码;
                        
                        query = $"DECLARE @BigID INT " +
                            $"select @BigID = BigID from BigBoxes " +
                            $"where BigCode = '{BigCode}'; " +
                            $"INSERT INTO Boxes(BoxCode , BigID) " +
                            $"VALUES('{box}', @BigID); ";

                        SqlCommand sql2 = new SqlCommand(query, sqlConnect);

                        int result1 = sql2.ExecuteNonQuery(); // 执行 SQL 命令  
                        if (result1 > 0)
                        {
                            MessageBox.Show("箱码插入成功!");
                        }
                        else
                        {
                            MessageBox.Show("数据插入失败或未插入任何行。");
                        }
                        #endregion
                        #region 产品码插入
                        if (productData?.产品 != null)
                        {
                            foreach (var product in productData.产品)
                            {
                                query = $"DECLARE @BoxID INT " +
                             $"select @BoxID = BoxID from Boxes " +
                             $"where BoxCode = '{box}'; " +
                             $"INSERT INTO Products(ProductCode , BoxID) " +
                             $"VALUES('{product}', @BoxID); ";

                                SqlCommand sql3 = new SqlCommand(query, sqlConnect);

                                int result3 = sql3.ExecuteNonQuery(); // 执行 SQL 命令  
                                if (result3 > 0)
                                {
                                    MessageBox.Show("产品数据插入成功!");
                                }
                                else
                                {
                                    MessageBox.Show("数据插入失败或未插入任何行。");
                                }
                            }
                        }
                    }
                    #endregion
                    sqlConnect.Close();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.ToString());
                }
            }

第二种方法不建临时文件直接存入数据库

提示:在用这种方法前要把数据库的副键ID设置为可以为空

insert into Boxes (BoxCode) values("要关联的箱码"); 
DECLARE @Box1ID INT 
select @Box1ID = BoxID from Boxes 
where BoxCode = "要关联的箱码"; 
#下面更新代码来建立产品码与箱码之间的连接
update pr 
set BoxID = @Box1ID 
from Products pr 
where pr.BoxID is null;

小结

这只是第一版的程序,最终到客户现场又添加了很多需求,改了好多功能。另外并没有添加与PLC通讯并读取读码器扫取的产品码的部分,还有数据查重的部分。最后程序到客户手中,还会被客户发现操作不规范导致的各种各样的BUG,最后需要在程序中添加防呆功能来防止扫描二维码混乱。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值