- 前言: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
#異質資料介接