You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在opdnew<0时退出foreach与while循环并取消所有数据更新

批量扣除OPD余额并确保全量验证后更新的实现方案

需求梳理

  • 从tb_ManageExpenseSheetOne筛选符合条件的记录,提取每个记录的PreparedBy(人员姓名)和TotalAmount(扣除金额)
  • 对每个人员,查询其tb_Person表中的RemainingOPD余额,计算扣除后的余额opdnew = RemainingOPD - TotalAmount
  • 若任意人员的opdnew < 0,立即显示警告信息,终止所有流程,不更新任何人员的OPD余额
  • 只有所有人员的扣除后余额都≥0时,才批量更新对应人员的RemainingOPD字段

原代码核心问题

  • 边检查边更新,一旦后续出现余额不足,已更新的记录无法回滚,不符合需求
  • 直接拼接SQL字符串,存在严重SQL注入风险
  • 重复创建/关闭数据库连接,资源利用效率低
  • 依赖UI控件临时存储计算值,容易引发异常

优化后的实现代码

protected void btnOPDDetect_Click(object sender, EventArgs e)
{
    // 存储待更新的人员及对应扣除金额(同一人多条记录自动累加)
    Dictionary<string, double> pendingUpdates = new Dictionary<string, double>();
    bool validationFailed = false;
    string errorMessage = string.Empty;
    string batchNo = lblIdforReport.Text;

    // 第一步:全量验证所有人员的OPD余额是否充足
    using (SqlConnection dbConn = GetConnection())
    {
        string expenseQuery = @"
            SELECT PreparedBy, TotalAmount 
            FROM tb_ManageExpenseSheetOne 
            WHERE IsCheckedAdvance='true' 
              AND FinanceOfficer=@FinanceUser 
              AND SubmittedStatus='Submitted' 
              AND VoucherStatus='Voucher Created' 
              AND RecommendedStatus='Recommended' 
              AND BatchNo=@BatchNumber";

        using (SqlCommand cmd = new SqlCommand(expenseQuery, dbConn))
        {
            // 参数化查询避免SQL注入
            cmd.Parameters.AddWithValue("@FinanceUser", User.Identity.Name);
            cmd.Parameters.AddWithValue("@BatchNumber", Convert.ToInt64(batchNo));

            dbConn.Open();
            using (SqlDataReader reader = cmd.ExecuteReader())
            {
                while (reader.Read() && !validationFailed)
                {
                    string personName = reader["PreparedBy"].ToString();
                    double deductAmount = Convert.ToDouble(reader["TotalAmount"]);

                    // 获取当前人员的剩余OPD
                    double currentOPD = GetPersonOPD(personName, dbConn);
                    double newOPD = currentOPD - deductAmount;

                    if (newOPD < 0)
                    {
                        validationFailed = true;
                        errorMessage = $"'{personName}' OPD is not enough";
                        break;
                    }

                    // 累加同一人员的扣除金额
                    if (pendingUpdates.ContainsKey(personName))
                    {
                        pendingUpdates[personName] += deductAmount;
                    }
                    else
                    {
                        pendingUpdates.Add(personName, deductAmount);
                    }
                }
            }
            dbConn.Close();
        }
    }

    // 验证不通过,显示警告并终止
    if (validationFailed)
    {
        lblWarning.Text = errorMessage;
        return;
    }

    // 第二步:所有验证通过,批量更新OPD余额(事务确保原子性)
    using (SqlConnection dbConn = GetConnection())
    {
        dbConn.Open();
        using (SqlTransaction transaction = dbConn.BeginTransaction())
        {
            try
            {
                string updateQuery = @"
                    UPDATE tb_Person 
                    SET RemainingOPD = RemainingOPD - @DeductValue 
                    WHERE Name=@PersonName";

                using (SqlCommand cmd = new SqlCommand(updateQuery, dbConn, transaction))
                {
                    // 复用参数对象提升效率
                    cmd.Parameters.Add("@DeductValue", SqlDbType.Float);
                    cmd.Parameters.Add("@PersonName", SqlDbType.VarChar);

                    foreach (var item in pendingUpdates)
                    {
                        cmd.Parameters["@DeductValue"].Value = item.Value;
                        cmd.Parameters["@PersonName"].Value = item.Key;
                        cmd.ExecuteNonQuery();
                    }
                }

                // 提交事务,确认所有更新生效
                transaction.Commit();
                btnArchive.Visible = true;
                lblWarning.Text = string.Empty;
            }
            catch (Exception ex)
            {
                // 出错回滚,确保数据一致性
                transaction.Rollback();
                lblWarning.Text = $"Update failed: {ex.Message}";
            }
            finally
            {
                dbConn.Close();
            }
        }
    }
}

// 辅助方法:查询指定人员的剩余OPD
private double GetPersonOPD(string personName, SqlConnection conn)
{
    string query = "SELECT RemainingOPD FROM tb_Person WHERE Name=@Name";
    using (SqlCommand cmd = new SqlCommand(query, conn))
    {
        cmd.Parameters.AddWithValue("@Name", personName);
        object result = cmd.ExecuteScalar();
        return result != DBNull.Value ? Convert.ToDouble(result) : 0;
    }
}

关键改进说明

  • 两步式流程:先完成全量验证,再执行更新,彻底避免部分更新的问题
  • 参数化查询:完全消除SQL注入风险
  • 事务控制:所有更新操作原子化,要么全部成功,要么全部回滚
  • 资源复用:复用数据库连接和命令对象,提升操作效率
  • 字典累加:自动处理同一人员多条记录的扣除金额累加
  • 解耦UI依赖:移除对TextBox的依赖,避免UI状态干扰业务逻辑

内容的提问来源于stack exchange,提问作者Shahid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 23:34:53