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