整理 UOF WKF 外掛欄位整合 ERP 多筆明細資料至 Grid 列表的完整實作流程與開發規範,適用於 C# 5.0 (.NET Framework 4.5 包含) 開發環境

  • 8
  • 0

整理 UOF WKF 外掛欄位整合 ERP 多筆明細資料至 Grid 列表的完整實作流程與開發規範,適用於 C# 5.0 (.NET Framework 4.5 包含) 開發環境

一、 架構與開發規範說明

在 UOF WKF 外掛欄位(VersionFieldUserControl_VersionFieldUC)開發中,自訂單身明細(Grid)的核心運作機制如下:

  1. 資料持久化機制:頁面透過隱藏的 txtFieldValue 容器存取 XML 結構。表單送出與儲存時,UOF 框架會讀取 FieldValue 屬性回傳的 XML 字串。
  2. ERP 明細匯入流程
    • 透過 SQL 查詢取得 ERP PURTA / PURTB 資料集(DataTable)。
    • 解析目前 txtFieldValue 中的 XML。
    • 將 ERP 多筆 DataRow 轉化為 <Item> 節點批次寫入 XML。
    • 呼叫 BindGrid() 解析 XML 並綁定至前端 Ede:Grid
  3. C# 5.0 相容限制
    • 不可使用 Null 條件運算子 (?.):須改用傳統三元運算子 (attr != null) ? attr.Value : string.Empty
    • 不可使用字串插值 ($"..."):須改用 string.Format(...)
    • DataRow 參數比對dt.Rows.Add() 傳入的參數數量必須與 DataTable.Columns 欄位數量完全相等。

二、 前端頁面設計 (OptionField_NEW_PURTAB.ascx)

HTML

 

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="OptionField_NEW_PURTAB.ascx.cs" Inherits="WKF_OptionalFields_OptionField_NEW_PURTAB" %>
<%@ Reference Control="~/WKF/FormManagement/VersionFieldUserControl/VersionFieldUC.ascx" %>
<%@ Register Assembly="Ede.Uof.Utility.Component.Grid" Namespace="Ede.Uof.Utility.Component" TagPrefix="Ede" %>

<style type="text/css">
    .tb-box { width: 100%; border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; border-radius: 4px; }
    .tb-header { background-color: #f5f5f5; font-weight: bold; padding: 5px; margin-bottom: 8px; }
    .tb-table { width: 100%; border-collapse: collapse; }
    .tb-table td { padding: 4px 8px; }
</style>

<!-- ERP 查詢與手動新增區 -->
<div class="tb-box">
    <div class="tb-header">明細資料操作</div>
    <table class="tb-table">
        <tr>
            <td style="width: 50%;">
                <strong>ERP 單號查詢:</strong><br />
                <asp:TextBox ID="TextBox1" runat="server" Width="200px"></asp:TextBox>
                <asp:Button ID="Button1" runat="server" Text="載入 ERP 明細" OnClick="Button1_Click" />
                <asp:Label ID="Label2" runat="server" ForeColor="Green"></asp:Label>
            </td>
            <td style="width: 50%;">
                <strong>手動新增 TB005:</strong><br />
                <asp:TextBox ID="txtTB005Input" runat="server" Width="200px"></asp:TextBox>
                <asp:Button ID="btnAddDetail" runat="server" Text="新增" OnClick="btnAddDetail_Click" />
                <asp:Button ID="btnDeleteDetail" runat="server" Text="刪除勾選列" OnClick="btnDeleteDetail_Click" />
            </td>
        </tr>
    </table>
</div>

<!-- Grid 明細展示區 -->
<div class="tb-box">
    <Ede:Grid ID="Grid1" runat="server" DataKeyNames="ID" AutoGenerateColumns="false" Width="100%">
        <Columns>
            <asp:BoundField HeaderText="ID" DataField="ID" Visible="false" />
            <asp:BoundField HeaderText="單別 (TA001)" DataField="TA001" HeaderStyle-Width="15%" />
            <asp:BoundField HeaderText="單號 (TA002)" DataField="TA002" HeaderStyle-Width="20%" />
            <asp:BoundField HeaderText="序號 (TB003)" DataField="TB003" HeaderStyle-Width="15%" />
            <asp:BoundField HeaderText="品號 (TB004)" DataField="TB004" HeaderStyle-Width="25%" />
            <asp:BoundField HeaderText="品名/規格 (TB005)" DataField="TB005" HeaderStyle-Width="25%" />
        </Columns>
    </Ede:Grid>
</div>

<!-- 訊息提示與 XML 持久化儲存控制項 -->
<div id="MESSAGES">
    <asp:Label ID="lblMessage" runat="server" ForeColor="Red"></asp:Label>
</div>

<div style="display: none;">
    <asp:TextBox ID="txtFieldValue" runat="server" TextMode="MultiLine"></asp:TextBox>
</div>

<!-- UOF 框架標準控制項 -->
<asp:Label ID="lblHasNoAuthority" runat="server" Text="無填寫權限" ForeColor="Red" Visible="False"></asp:Label>
<asp:Label ID="lblToolTipMsg" runat="server" Text="不允許修改(唯讀)" Visible="False"></asp:Label>
<asp:Label ID="lblModifier" runat="server" Visible="False"></asp:Label>

三、 後端邏輯實作 (OptionField_NEW_PURTAB.ascx.cs)

C#

 

using System;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml.Linq;
using Ede.Uof.WKF.Design;
using Ede.Uof.WKF.VersionFields;

public partial class WKF_OptionalFields_OptionField_NEW_PURTAB : WKF_FormManagement_VersionFieldUserControl_VersionFieldUC
{
    #region ============== 公開屬性 ==============
    private bool m_ShowGetValueButton = true;
    public bool ShowGetValueButton
    {
        get { return this.m_ShowGetValueButton; }
        set { this.m_ShowGetValueButton = value; }
    }
    #endregion

    protected void Page_Load(object sender, EventArgs e)
    {
        SetField(m_versionField);
    }

    #region ============== UOF 框架標準覆寫方法 ==============

    public override string ConditionValue => string.Empty;
    public override bool IsModified => false;
    public override string DisplayTitle => string.Empty;
    public override string Message => string.Empty;

    public override string RealValue
    {
        get => string.Empty;
        set => base.m_fieldValue = value;
    }

    public override string FieldValue
    {
        get
        {
            if (string.IsNullOrEmpty(txtFieldValue.Text) || txtFieldValue.Text == "<FieldValue/>")
            {
                return "<FieldValue/>";
            }
            return txtFieldValue.Text;
        }
        set => base.m_fieldValue = value;
    }

    public override bool IsFirstTimeWrite
    {
        get => false;
        set => base.IsFirstTimeWrite = value;
    }

    /// <summary>
    /// 控制控制項啟用/停用(檢視/編輯/簽核模式)
    /// </summary>
    public void EnabledControl(bool enabled)
    {
        TextBox1.Enabled = enabled;
        Button1.Enabled = enabled;
        txtTB005Input.Enabled = enabled;
        btnAddDetail.Enabled = enabled;
        btnDeleteDetail.Enabled = enabled;
        Grid1.Enabled = enabled;
    }

    /// <summary>
    /// 初始化欄位狀態與綁定資料
    /// </summary>
    public override void SetField(VersionField versionField)
    {
        FieldOptional fieldOptional = versionField as FieldOptional;

        if (fieldOptional != null)
        {
            if (string.IsNullOrEmpty(fieldOptional.FieldValue))
            {
                txtFieldValue.Text = "<FieldValue/>";
            }
            else
            {
                txtFieldValue.Text = fieldOptional.FieldValue;
                BindGrid();
            }

            // 權限控制判斷
            if (!fieldOptional.IsAudit)
            {
                EnabledControl(fieldOptional.HasAuthority);
            }
            else
            {
                if (fieldOptional.Filler != null)
                {
                    bool isCurrentSiteFiller = base.taskObj != null &&
                                               base.taskObj.CurrentSite != null &&
                                               base.taskObj.CurrentSite.SiteId == fieldOptional.FillSiteId &&
                                               fieldOptional.Filler.UserGUID == Ede.Uof.EIP.SystemInfo.Current.UserGUID;

                    EnabledControl(isCurrentSiteFiller ? fieldOptional.HasAuthority : fieldOptional.AllowModify);
                }
                else
                {
                    EnabledControl(fieldOptional.HasAuthority);
                }
            }

            if (fieldOptional.FieldMode == FieldMode.Print || fieldOptional.FieldMode == FieldMode.View)
            {
                EnabledControl(false);
            }

            if (fieldOptional.Modifier != null)
            {
                lblModifier.Visible = true;
                lblModifier.ForeColor = System.Drawing.Color.FromArgb(0x52, 0x52, 0x52);
                lblModifier.Text = System.Web.Security.AntiXss.AntiXssEncoder.HtmlEncode(fieldOptional.Modifier.Name, true);
            }
        }
    }

    #endregion

    #region ============== ERP 資料查詢與 XML 操作邏輯 ==============

    /// <summary>
    /// 查詢 ERP PURTA / PURTB 表單
    /// </summary>
    public DataTable FIND_ERP_PURTA_PURTB(string TA002)
    {
        string connectionString = ConfigurationManager.ConnectionStrings["ERPconnectionstring"].ConnectionString;
        Ede.Uof.Utility.Data.DatabaseHelper m_db = new Ede.Uof.Utility.Data.DatabaseHelper(connectionString);

        string cmdTxt = @"
            SELECT PURTA.TA001, PURTA.TA002, PURTB.TB003, PURTB.TB004, PURTB.TB005
            FROM [TK].dbo.PURTA
            INNER JOIN [TK].dbo.PURTB ON TA001 = TB001 AND TA002 = TB002
            WHERE TA002 = @TA002";

        m_db.AddParameter("@TA002", TA002);

        DataTable dt = new DataTable();
        dt.Load(m_db.ExecuteReader(cmdTxt));

        return (dt != null && dt.Rows.Count > 0) ? dt : null;
    }

    /// <summary>
    /// 解析 XML 並重新繫結 Grid (C# 5.0 相容)
    /// </summary>
    private void BindGrid()
    {
        if (string.IsNullOrEmpty(txtFieldValue.Text)) return;

        XElement xe = XElement.Parse(txtFieldValue.Text);

        DataTable dt = new DataTable();
        dt.Columns.Add("ID");
        dt.Columns.Add("TA001");
        dt.Columns.Add("TA002");
        dt.Columns.Add("TB003");
        dt.Columns.Add("TB004");
        dt.Columns.Add("TB005");

        var items = xe.Elements("Item");
        foreach (var item in items)
        {
            XAttribute attrId = item.Attribute("id");
            XAttribute attrTA001 = item.Attribute("TA001");
            XAttribute attrTA002 = item.Attribute("TA002");
            XAttribute attrTB003 = item.Attribute("TB003");
            XAttribute attrTB004 = item.Attribute("TB004");
            XAttribute attrTB005 = item.Attribute("TB005");

            string id = (attrId != null) ? attrId.Value : string.Empty;
            string ta001 = (attrTA001 != null) ? attrTA001.Value : string.Empty;
            string ta002 = (attrTA002 != null) ? attrTA002.Value : string.Empty;
            string tb003 = (attrTB003 != null) ? attrTB003.Value : string.Empty;
            string tb004 = (attrTB004 != null) ? attrTB004.Value : string.Empty;
            string tb005 = (attrTB005 != null) ? attrTB005.Value : string.Empty;

            // 參數個數必須與 DataTable 欄位數量一致 (6個)
            dt.Rows.Add(id, ta001, ta002, tb003, tb004, tb005);
        }

        Grid1.DataSource = dt;
        Grid1.DataBind();
    }

    /// <summary>
    /// 按鈕事件:載入 ERP 多筆明細至 XML
    /// </summary>
    protected void Button1_Click(object sender, EventArgs e)
    {
        lblMessage.Text = string.Empty;
        Label2.Text = string.Empty;
        string TA002 = TextBox1.Text.Trim();

        if (string.IsNullOrEmpty(TA002))
        {
            lblMessage.Text = "請輸入單號!";
            return;
        }

        DataTable dt = FIND_ERP_PURTA_PURTB(TA002);

        if (dt == null || dt.Rows.Count == 0)
        {
            lblMessage.Text = "查無對應的 ERP 單據資料!";
            return;
        }

        XElement xe = (string.IsNullOrEmpty(txtFieldValue.Text) || txtFieldValue.Text == "<FieldValue/>")
            ? new XElement("FieldValue")
            : XElement.Parse(txtFieldValue.Text);

        int addedCount = 0;
        foreach (DataRow row in dt.Rows)
        {
            // C# 5.0 安全轉型 (防範 DBNull 與 null)
            string TA001Value = (row["TA001"] != DBNull.Value && row["TA001"] != null) ? row["TA001"].ToString().Trim() : string.Empty;
            string TA002Value = (row["TA002"] != DBNull.Value && row["TA002"] != null) ? row["TA002"].ToString().Trim() : string.Empty;
            string TB003Value = (row["TB003"] != DBNull.Value && row["TB003"] != null) ? row["TB003"].ToString().Trim() : string.Empty;
            string TB004Value = (row["TB004"] != DBNull.Value && row["TB004"] != null) ? row["TB004"].ToString().Trim() : string.Empty;
            string TB005Value = (row["TB005"] != DBNull.Value && row["TB005"] != null) ? row["TB005"].ToString().Trim() : string.Empty;

            if (!string.IsNullOrEmpty(TB005Value))
            {
                XElement newItem = new XElement("Item",
                    new XAttribute("id", Guid.NewGuid().ToString()),
                    new XAttribute("TA001", TA001Value),
                    new XAttribute("TA002", TA002Value),
                    new XAttribute("TB003", TB003Value),
                    new XAttribute("TB004", TB004Value),
                    new XAttribute("TB005", TB005Value)
                );

                xe.Add(newItem);
                addedCount++;
            }
        }

        if (addedCount > 0)
        {
            txtFieldValue.Text = xe.ToString();
            BindGrid();
            Label2.Text = string.Format("已成功匯入 {0} 筆明細", addedCount);
        }
        else
        {
            lblMessage.Text = "查詢結果中的 TB005 欄位皆為空值!";
        }
    }

    /// <summary>
    /// 手動新增單筆資料
    /// </summary>
    protected void btnAddDetail_Click(object sender, EventArgs e)
    {
        lblMessage.Text = string.Empty;
        string tb005Value = txtTB005Input.Text.Trim();

        if (string.IsNullOrEmpty(tb005Value))
        {
            lblMessage.Text = "請輸入 TB005 資料!";
            return;
        }

        XElement xe = (string.IsNullOrEmpty(txtFieldValue.Text) || txtFieldValue.Text == "<FieldValue/>")
            ? new XElement("FieldValue")
            : XElement.Parse(txtFieldValue.Text);

        XElement newItem = new XElement("Item",
            new XAttribute("id", Guid.NewGuid().ToString()),
            new XAttribute("TA001", "MANUAL"),
            new XAttribute("TA002", "MANUAL"),
            new XAttribute("TB003", "0000"),
            new XAttribute("TB004", "MANUAL"),
            new XAttribute("TB005", tb005Value)
        );

        xe.Add(newItem);
        txtFieldValue.Text = xe.ToString();
        txtTB005Input.Text = string.Empty;
        BindGrid();
    }

    /// <summary>
    /// 刪除 Grid 中被勾選的資料列
    /// </summary>
    protected void btnDeleteDetail_Click(object sender, EventArgs e)
    {
        string[] selectedIds = Grid1.GetSelectedRowGUIDs();
        if (selectedIds == null || selectedIds.Length == 0) return;

        XElement xe = XElement.Parse(txtFieldValue.Text);

        foreach (string id in selectedIds)
        {
            XElement targetItem = xe.Elements("Item")
                .FirstOrDefault(x => x.Attribute("id") != null && x.Attribute("id").Value == id);

            if (targetItem != null)
            {
                targetItem.Remove();
            }
        }

        txtFieldValue.Text = xe.ToString();
        BindGrid();
    }

    #endregion
}

四、 常見開發錯誤與 Debug 排查清單

錯誤現象常見原因排查與修復方式
System.ArgumentExceptiondt.Rows.Add() 的引數數量與 DataTable.Columns 數量不吻合。確認 dt.Columns.Add() 建立的欄位總數,必須與 dt.Rows.Add(...) 傳入的變數數量完全一致。
CS1061: 'XAttribute' does not contain a definition for '?'在舊版 Visual Studio / C# 5.0 環境使用了 C# 6.0 的 ?. 語法。改用 (attr != null) ? attr.Value : string.Empty 判斷。
System.NullReferenceException 讀取資料庫資料庫欄位回傳 NULLDBNull.Value),直接使用 .ToString() 引發例外。加入判斷式:(row["Col"] != DBNull.Value && row["Col"] != null)
表單送出後 Grid 明細遺失未將 XML 結構回填至 txtFieldValue.Text,或 FieldValue 屬性沒有回傳 txtFieldValue.Text確保每次對 XML 新增/刪除節點後都有執行 txtFieldValue.Text = xe.ToString()

 

自我LV~