2013年2月7日 星期四

.Net and SAP Integration: shared function RFC_READ_TABLE

  • 前言:RFC_READ_TABLE 是 SAP 提供的 Read SAP Table 的公用程式,對於需要取得SAP資料的.Net Programmer而言,經常會使用到這個程式,因此適合開發成共用層。這一篇則是將RFC_READ_TABLE 包裝成 WCF 提供服務,SAP端則是以 ECC6 Unicode 版本為目的地。對於本篇的內容,歡迎建議與討論!
  • 使用工具:
    • VS 2010、.Net Framework 4.0
    • SAP NCO 3.0 for .Net Framework 4.0:請自行到SAP網站下載,建議取得最新版,SAP更新的滿快的,舊版曾經發生一些奇怪的問題,安裝好的目錄通常在:C:\Program Files (x86)\SAP 底下,會需要 sapnco.dll 與 sapnco_utils.dll。另外,位元版本也需要注意:
      • Winform: 與專案版本相同,如果專案是64bit,則使用64bit
      • Web application、Web Service、WCF: 都使用32bit
    • 黃昭仁大大的"IRfcTable、DataTable、DataSet 資料轉換模組"(http://vsqa.blogspot.tw/2011/08/irfctabledatatabledataset.html):轉換 NCO 回傳的Table資料結構轉換成.Net Programmer適合使用的 Data Table, DataSet,非常好用。
    • SAP RFC_READ_TABLE:要用的好,必須先對這個function 的用法,詳細可以開 SAP GUI 先試試看。建議在開發前,先在SAP確認好資料都正確,SAP RFC_READ_TABLE 正確,在外部呼叫一定都會正確。
  • WCF 運作方式:WCF主要就是提供 RFC_READ_TABLE去設計,服務的input跟SAP相同,只是我沒有放RowCount,個人認為是不需要,各位看官如果覺得有需要,請再加上去就好。
    • 服務的宣告:
      [OperationContract]
      SAPMessageType SAP_RFC_READ_TABLE_UNICODE(string strDest, string strTable, string[] strOptions, string[] strFields);

      參數說明如下:
      > strDest:SAP目的地
      > strTable: 要查詢的SAP Table
      > strOptions: SAP Options 也就是 Where 的條件,每一行有長度限制是72
      > strFields: SAP Fileds 要查詢SAP Table哪一些欄位
      其中,回傳的資料結構(合約)我自行定義的,因為通常Export會回傳執行的狀態,而 Table 則存放實際的資料,所以把這些合成一種資料結構
      [DataContract]
      public class SAPMessageType
      {
          [DataMember]
          public DataTable SAPTable { get; set; }
          [DataMember]
          public string strMessage { get; set; }
          [DataMember]
          public string strMessageCode { get; set; }
      }
      
    • WCF 執行過程:邏輯上與資料庫連結是一樣的,主要分5區,
      • 第一區 宣告區:主要是宣告會使用到的變數等等,小弟比較老一點,所以會先宣告要用到的變數
      • 第二區 連線區:從連線的class取得連線參數,並開始連線
      • 第三區 傳入參數區:指定SAP Function(RFC_READ_TABLE),傳入Fileds, Options
      • 第四區 回傳參數與處理:取得回傳的SAP 資料,並根據Fields所指定的開始切割
      • 第五區 結束區
        [PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
        public SAPMessageType SAP_RFC_READ_TABLE_UNICODE(string strDest, string strTable, string strOptions, string[] strFields)
        {
            //
            //第一區 宣告區: logger 是使用nlog
            //
            logger.Info("SAP_RFC_READ_TABLE START UNICODE Version Start");
            SAPMessageType aData = new SAPMessageType();
            string strMessage = "";
            string strMessageCode = "";

            DataTable SAPDataTable = null;
            RfcDestination destination = null;

            try
            {
                //
                //第二區 連線區,連線內容在 SAPSystemConnect.cs 中取出
                //
                SAPSystemConnect aSapCfg = new SAPSystemConnect();
                RfcConfigParameters parameters = aSapCfg.GetParameters(strDest);

                if (parameters == null)
                {
                    //指定的SAP Client不存在
                    strMessageCode = "E";
                    strMessage = "指定的SAP Client不存在";
                }
                else
                {
                    destination = RfcDestinationManager.GetDestination(parameters);
                    RfcSessionManager.BeginContext(destination);

                    destination.Ping();
                    IRfcFunction function = null;

                    //
                    //第三區 傳入參數區
                    //

                    function = destination.Repository.CreateFunction("RFC_READ_TABLE");
                    function.SetValue("QUERY_TABLE", strTable);

                    //OPTIONS
                    if (strOptions != "")
                    {
                        IRfcTable tableOPTIONS = function["OPTIONS"].GetTable();
                        tableOPTIONS.Append();
                        tableOPTIONS.SetValue(0, strOptions);
                    }

                    IRfcTable tableFIELDS = function["FIELDS"].GetTable();
                    if (strFields.Length != 0)
                    {
                        foreach (string fd in strFields)
                        {
                            tableFIELDS.Append();
                            tableFIELDS.SetValue("FIELDNAME", fd);
                        }
                    }

                    function.Invoke(destination);

                    //
                    //第四區 回傳參數與處理
                    //

                    IRfcTable tableRead = function.GetTable("DATA");
                    DataTable dtRealTable = new DataTable(strTable);
                    ArrayList listFields = new ArrayList();
                    foreach (IRfcStructure row in tableFIELDS)
                    {
                        RFC_Table_Schema aRowSchema = new RFC_Table_Schema();
                        aRowSchema.fdOffset = Convert.ToInt16(row.GetString("OFFSET"));
                        aRowSchema.fdLength = Convert.ToInt16(row.GetString("LENGTH"));
                        aRowSchema.fdName = row.GetString("FIELDNAME");
                        aRowSchema.fdType = row.GetString("Type");
                        listFields.Add(aRowSchema);

                        //準備存放整理後資料的DataTable
                        dtRealTable.Columns.Add(row.GetString("FIELDNAME"), typeof(String));
                    }

                    foreach (IRfcStructure row in tableRead)
                    {
                        DataRow realRow = dtRealTable.NewRow();
                        //根據實際資料切Table內容, WA是為分割前的資料
                        String strWA = row.GetString("WA");

                        //unicode
                        #region Unicode切割方式

                        string anewString;

                        foreach (RFC_Table_Schema aRowSchema in listFields)
                        {
                            anewString = "";
                            //根據長度切割
                            //如果自SAP取得資訊,發現後面都是空白,SAP就會自己Trim字串,資料會與Fields提供的資訊不同
                            if ((aRowSchema.fdOffset + aRowSchema.fdLength) > strWA.Length)
                            {
                                if ((strWA.Length - aRowSchema.fdOffset) > 0)
                                {
                                    anewString = strWA.Substring(aRowSchema.fdOffset, (strWA.Length - aRowSchema.fdOffset));
                                }
                            }
                            else
                            {
                                anewString = strWA.Substring(aRowSchema.fdOffset, aRowSchema.fdLength);
                            }

                            realRow[aRowSchema.fdName] = anewString.Replace("\0", "").Trim();
                        }
                        #endregion

                        dtRealTable.Rows.Add(realRow);

                    }

                    //
                    //第五區 結束區
                    //

                    SAPDataTable = dtRealTable;
                    strMessageCode = "S";
                    strMessage = "成功讀取";

                    RfcSessionManager.EndContext(destination);
                    destination = null;
                }

            }
            catch (Exception ex)
            {
                logger.Error(ex.ToString());

                strMessageCode = "E";
                strMessage = ex.ToString();
                if (destination != null)
                {
                    RfcSessionManager.EndContext(destination);
                    destination = null;
                }
            }

            aData.SAPTable = SAPDataTable;
            aData.strMessage = strMessage;
            aData.strMessageCode = strMessageCode;
            logger.Info("SAP_RFC_READ_TABLE START UNICODE Version END");
            return aData;
        }



  • Service 使用端:呼叫時,先引用WCF,依照這樣呼叫:
  • //使用WCF
    SAPServiceReference.SAPServiceClient aClient = new SAPServiceReference.SAPServiceClient();
    SAPServiceReference.SAPMessageType aClientData = null;
    
    //需要的欄位
    string[] strFds2 = new string[] { "BUKRS", "BUTXT", "LAND1" };
    //Where條件
    string[] strOps = new string[] { "LAND1 <> 'TW'", "AND MANDT = '500' " };
    //取得 T001 公司主檔
    aClientData = aClient.SAP_RFC_READ_TABLE_UNICODE("DEV500", "T001", strOps, strFds2);
    aClient.Close();
    
    
    //因為回傳 Datatable,所以可以直接用
    GridView4.DataSource = aClientData.SAPTable;
    GridView4.DataBind();
    //可以有一個Label說明執行的結果
    lbNewErr.Text = aClientData.strMessageCode + ":" + aClientData.strMessage;
    

如果有需要程式參考的大大,請到這邊:

  • https://dl.dropbox.com/u/3330791/Sharecode/SAPWcfService.rar 
    • 程式目前會無法執行,因為要請自行下載SAP NCO 3.0,並加入 sapnco.dll、sapnco_utils.dll

參考資料:

  • SAP NCO 手冊: http://help.sap.com/saphelp_crm700_ehp02/helpdata/EN/4a/097b0543f4088ce10000000a421937/content.htm 
  • 黃昭仁大大的 Blog: http://vsqa.blogspot.tw/ 
  • How-To Use SAP Nco 3 Connector | .Net 4 | Visual Studio 2010: http://klanguedoc.hubpages.com/hub/How-To-Use-SAP-Nco-3-Net-4-Visual-Studio-2010

#異質資料介接