2013年7月29日 星期一

Agile Contract Type (II) - Incremental and Target Cost

接續著上一篇的主題,這一篇則整理另外 2種Agile合約,在國外的討論或是書上會看到的,這兩種在台灣就不是這麼常聽到了:
  • 漸進式交付合約(Incremental Delivery) (1)(2):這種合約會設置好幾個檢查點,每個檢查點可以讓甲方決定還要不要繼續下去,不繼續的話,已經完成的功能也是能運作的軟體,而不是一堆做到一半的程式碼。這種方式是比較適合Agile Project Management,因為每個 Iteration 交出的成品都是可運作的軟體。譬如一年的專案,可以在每一季設一個檢查點,如果乙方的Agile Team每個 Iteration 是 2 週,每個 Iteration 都交付一些重要性高的、可以用的軟體功能,6個 Iteration 時(也就是3個月),甲方就可以決定還要不要繼續下去,如果想要更經常的檢視完成品,也可以雙方討論後,修改檢查點的時間。
  • 目標成本合約(Target Cost Contract) (3 對細節有興趣的朋友,可以花點時間看):這種合約是 Toyota 跟供應商長期簽訂的合約類型,上課的時候也是講說 Agile Project Management 建議簽這樣的合約類型。這種合約先由甲乙雙方決定一個目標金額,專案開始後,則依照比例甲方付款給乙方,而如果乙方提前完成目標,則兩方共享節省的金額,而如果超過目標金額,則兩方共同承擔懲罰。而獎賞或懲罰的比例則事先訂定下來,如果想拿大比例的獎賞,那就要承擔大比例的懲罰。這種合約類型用圖解釋比較清楚:


    • 專案的基本數字:
      • 專案總金額:500 萬,額外準備100萬,總專案的最高費用(Cap)是600萬
      • 簽約時的付款:不先付任何款
      • 結案的付款:結案時給50萬
      • 每月工資:50萬
      • 預計工時:10個月
    • 專案 3種狀況(正常完成、提前、延誤)的付款進度如圖:



    • 正常進度:紅線,每月固定50萬,最後第10個月後到500萬,沒有獎勵也沒有懲罰。
    • 提前:綠色的線,假設提前在第8個月做完的話,可以領到
      • 8個月的400萬
      • 結案時可以領的50萬
      • 激勵金額 (目標的500萬-完工400萬)*50%=50萬,全部加起來也是500萬。在這個情況下,甲方可以提早開始使用系統帶來的效益,而乙方可以早領錢、早結案,專案人員可以慶祝(在台灣軟體開發專案能夠提前,真是難得啊...)並準備投入下一個專案。
    • 延後:在到 10th個月後,11th個月起,甲乙方各付一半(因為獎金也是甲乙各一半),所以11th個月起,每月只能算25萬,一直到550萬為止,所以到12th月後,累計就已經550萬,加上結案可以收到的50萬,就已經到頂(600萬)了,這時候乙方就必須不領錢直到做完為止,雖然看起來只有乙方在suffer,但是實際上甲方一直沒系統可用,這樣造成的損失也是甲方要承擔的懲罰。
    • 因為Target Cost Contract 比較特殊,可能有一些實行上的細節要考慮,除了看 (3) 以外,也可以看這篇 Agile Journal 2005年的 Selling agile: target-cost contracts ,講到非常實務上的細節。
小弟在整理這些合約類型的過程中,如果是乙方,可能需要先取得甲方的信任,在第2次、第3次的專案中,再開始用新的合約類型,比較不會引起甲方的過度懷疑,小弟認為做專案就是信任大考驗,在雙方互信的狀況下,怎麼樣都好談,要是雙方無信任,那就很難再繼續下去....
下一篇則要來個整理表了,比較各專案類型的適用對象,與陰謀論的想一下甲乙方會承擔的風險。

參考

2013年7月27日 星期六

Agile Contract Type (I) - Fixed Price and T&M


這個圖是你的合約關係嗎?

在上Agile Project Management的課程時,一直想跟實際工作情形連起來,連到採購的時候,心中有的大問號:現在一般常見的系統開發合約(開發一個系統,分3或4期付款:簽約、系統設計完成、開發完成、結案) ,跟以 Iteration 為進行方式的Agile Project,要怎麼對應的起來? 用Agile 開發的話,不把全部的SA/SD文件都寫完啊,那要怎麼付款跟請款 ?
上完課後,為了一解心中的疑惑,開始蒐集 Agile Contract 的類型,這一系列分3篇,算是對整理後的筆記,先整理目前常見的合約類型共4種,最後再加上一個比較表,說明適用的時機與缺點。

這一篇先講兩種常見的固定價格合約(Fixed Price and Fixed Scope)與工時與材料(Time and Material)類型的合約:
  • 固定價格合約(Fixed price):是目前最常見的軟體專案發包方式,這種合約通常有固定的總金額與專案範圍,通常付款是分為:
    • (1)合約簽約付一筆
    • (2)中間階段0~N次付款:通常可以分 "系統分析文件交付"、"系統設計文件交付"、"系統開發完成交付"、"系統通過使用者驗收測試(UAT)" 等等不同階段
    • (3)專案結案付一筆
    • 譬如一個中型專案,乙方為了公司營運金流的順暢,希望分 4 次付款,所以請款的時候會是 "合約簽約"、"系統分析、系統設計文件交付與確認"、"通過UAT"、"專案結案",這樣的合約配合瀑布式開發方式是很合適的,各階段都能搭配上。
    • 在Agile Project Management時,並不會一開始完整的分析整個系統,用 固定價格的合約 就會覺得有點卡卡,譬如甲方要求 "SA, SD文件確認" 付款時,乙方不知道要怎麼產出 超大本的SA/SD文件 去給甲方確認,所以出現其他比較符合Agile的合約類型。但是,如果甲方說,只能發包這樣的合約(組織要求或...),乙方是可以應用一些觀念,讓合約比較能符合Agile開發方式,譬如偷偷引入漸進式交付合約的想法(Incremental Delivery),把Release當作階段 而不把SA/SD文件完成當作階段 (完成子系統#1,2當作第一階段、完成子系統#3,4,5 當作第二階段)。
    • Scrum 大師Jeff Sutherland 提出的 2個重要的合約的觀念 (1) ,如果必須要承接/發包固定價格的合約,可以加入到合約中,對甲乙雙方都會有幫助的 (小弟不喜歡用"雙贏", 講出雙贏的人通常都不會說自己贏比較多...)
    • Money for nothing 金錢無用 :甲方可以在任一個 Iteration 後結束專案。評量的標準是, 如果甲方認為後續要做的價值不如已經完成的價值(i.e 已經完成的功能已經能達到所需要,因為Agile 是從高優先順序開始開發) 甲方將會付給乙方剩下合約的20%的金額。( 金錢無用 -> 不想付錢買不會用到的功能..) 
    • Change for free 改變免費:雖然講免費,但實際是,當有改變的時候,Product Owner 在 Iteration Planning 前或中提出新需求,Agile Team 評估 Story Point,確定要做的話,Product Owner把不重要的功能或相等Story Point的功能的移出 Product Backlog,做一些 TradeOff,甲方的高優先需求得以滿足,乙方一樣做那麼多Story Point 的工作,以這個方式來歡迎改變!

  • 時間與材料合約/有上限的時間與材料合約( T&M / T&M with cap):跟台灣俗稱的的"買人力"的想法很接近,可以想成"買整組人"在甲方駐點(通常還是要駐一下, out of sight out of mind, XD )開發所需要的系統,這種方式就是做多久就付多少金額。因為時間都是乙方估計,所以風險都在甲方,也因此有變種的,是加上上限的T&M(Capped T&M)合約,這個上限是雙方同意後的金額,通常甲方應該要加上鼓勵條款,鼓勵乙方早日完成。(不知為何,在台灣的甲方,能夠準時驗收與準時付款,對乙方就是一種激勵...) (2)

下一篇將整理的是漸進式的與目標成本的合約。

參考

2013年7月13日 星期六

[Engineering Practice] 軟體開發的測試-單元測試 / Software Development Testing - Unit Testing

以前只要講到系統功能要測試,通常就會想到找 PM、SA或直接給客戶進行人體測試,結果就會發生一使用功能就 Error 的悲劇... 。今年去上 Agile Project Management 時, 講到強調軟體開發的Extreme Programming時,特別強調測試,在 "XP 規則" ( The Rules of Extreme Programming原文 )中,有一組規則是講測試的,貼上來給大家震撼一下:
  • All code must have unit tests. 所有程式碼都要有單元測試 !!!!!
  • All code must pass all unit tests before it  can be released. 所有程式碼通過單元測試後才能release !
  • When a bug is found tests are created. 如果有發現 bug 時,就要回去寫測試 !!!
  • Acceptance tests are run often and the score is published.  經常執行驗收測試,並且必須公布量化指標!!
對XP Unit Test 有興趣的朋友,可以再看這一篇: Unit Test ..

既然單元測試是這麼重要,在上完Agile的課後,小弟開始學習在.Net Framework 要怎麼做單元測試,這一篇算是介紹,也是對自己學習的筆記,我使用的工具是 C# 與 Visual Studio 2012,使用 Java 的朋友,你一定能找到對應的工具與平台,這年頭, M$ 會推出某個功能,都是被 Java Community 逼出來的..ㄎㄎ... 廢話不多說,我用問題與回答來說明單元測試:

1. 單元測試到底是指甚麼?  =>預期結果等於測試結果
  • 簡單說,單元測試是對程式的最基本的單位進行測試 ( 最基本單位是要看你自己認定,小弟是非常程序導向的開發人員 i.e. old ,我的通常是 Function ),確認程式能夠如預期的執行。小弟的心得是,因為要單元測試,會強迫自己開始規劃自己的程式,讓 function 真正能比較像一個 function 該做的事,不會通通塞到一起才來拼命 debug line by line (還記得以前老師教的高聚合低耦合嗎?... )
  • 在單元測試會做的三件事:這跟人體測試時的邏輯是滿相同的,假設你要呼叫一個兩數相除的 function,會有3 步驟
    • Step (1) 先準備呼叫的資料,如 100除以5,所以你會把100跟5先準備好,這個步驟就是 "安排測試資料(Arrange)"
    • Step (2) 開始呼叫Function,並取得結果,譬如這個function可正常呼叫並回傳20,這個步驟就是 "執行測試(Act)"
    • Step (3) "判斷結果(Assert)其實在提供資料去測試時,你會有預期的結果,所以從第2步取得結果後,就會跟你預期的結果進行比對,以判斷兩數相除的Function是否如果預期執行,譬如 100/5=20,function回傳結果也是20,所以你就可以判斷function是否運作正常了!
    • 下圖是我寫測試一個上傳檔案的 Function 是否正確的單元測試程式,其實跟真正要呼叫該 Function 的程式會很類似,所以其實單元測試寫完,就可以copy/paste到正是用的程式,一魚兩吃阿!


2. 要如何開始做單元測試 ? =>打開你的好朋友(?!) Visual Studio
  • 這個問題跟使用的程式語言與開發工具非常相關了,我用Visual Studio 2012來當範例,基本操作在網路上已經很多教學怎麼做了 (如果跟我一樣從未接觸過,可以參考這一篇 MSDN,照著做一次會有感覺...) ,步驟大致是:
    • (1)在你目前開發的"方案"中,加入一個 "單元測試專案"
    • (2)在新的單元測試專案中,增加"參考" 開發的專案
    • (3)在新的單元測試專案中,開始寫Arrange, Act, Assert的程式。 請注意的是,在一個單元測試中,可以放多筆測試資料,預設是只要一個測試未通過,就會當作不成功,可以多放幾筆確定 function 真的跟預期的結果相同 (該有回傳就回傳,該有Exception 就會有 Exception...)
  • 寫完後,可以直接按 "測試總管" 的 "全部執行",就會知道是否通過單元測試了! 在測試的過程中,再回去改程式,我覺得這個過程是最大的收穫 !

3. 要如何確認自己做夠多的單元測試? => 計算程式碼涵蓋範圍
  • 還記得剛才的XP Test Rule "所有程式碼都要有單元測試 !",所以要如何判斷自己有涵蓋夠多的程式? 你可以使用Visual Studio 2012的  "分析程式碼涵蓋範圍"功能 ,馬上就可以看到涵蓋結果。至於你的目標要設為多少,可能要由你的 developer team 自行在definition of done 中定義。 
  • 如果你有引用Web Service 或 WCF,預設在計算程式碼涵蓋範圍時,會把這些外部參考也會當作範圍,這樣分母就會很大,涵蓋率就會變低,看了就不爽! 這時你可以自己加上一個設定檔(參考這一篇 Customizing Code Coverage Analysis ,最下方有範例檔案),並且在裡面增加排除Web Service/WCF,這樣就不會把外部的算進去了! 想要直接下載的可以到這邊下載,加到你的測試專案中,並在 "測試"->"測試設定"->"選取測試執行檔"使用這設定。
    • 加入後設定檔案後,要自己指定


    • 設定檔案中,如果不想納入Web Service/WCF,這邊要加上排外的設定。設定完再執行一次 "分析程式碼涵蓋範圍" 就會看到變化囉 !



4. 要如何準備單元測試的多筆測試資料? => 資料驅動的測試方式
  • 如果你的developer team有專職的測試人員或是你的Product Owner/Customer 願意準備測試資料,這時你就可以使用 資料驅動的測試方式 (Data-driven test) 。這方式可以用 CSV, EXCEL, Database 當作資料來源,而在單元測試時,就可以取得這些資料,進行多次的測試,太棒啦,測試可也切成資料與邏輯 !  
  • 使用 data-driven 測試有幾個眉角需要注意的
    • (1)資料上,準備時要有輸入的參數,還有預期的結果,譬如大家都熟悉的 Excel ,我有一個function要驗證帳號密碼是否正確,所以有3個欄位:帳號,密碼,預期結果,類似下圖

    • (2)資料上,檔案要自己加入倒測試專案中,並且在 "複製到輸出目錄" 要選 "永遠複製"
    • (3)程式上,要自己加上 using System.Data 
    • (4)程式上,要加上 private TestContext testContextInstance; 的一段
    • (5)程式上,要加上 Data Source,Connection String 可以參考這篇 Data Driven Testing with Visual Studio 2012 – Coded UI Test ,已經整理好了,改改檔名就可以。
    • 這段的程式是這樣的,其實很單純,但是  Visual Studio 2012 就是不會自己做.


5. 要如何把自己的 Function 跟別的 Interface/Library 隔離?  => Fake 機制
  • 有時候測試的時候會希望跟別的 Interface/Library 切割的比較清楚,譬如拿到輸入資料後不寫入資料庫,只要判斷程式邏輯正確,這時要怎麼切割呢? 在 Visual Studio 2012 時加上了 Fake 的機制,請一定要參考這一篇 VS 2012 的 Unit Test與測試總管 的"建立 Fake 組件"  與這一篇 Isolating Code Under Test with Microsoft Fakes ,Fake機制裡面還可以分為 Stub 與 Shim ,各有各的適用對象。以下小弟舉一個簡單的範例,使用的是Shim給大家參考:我要做的是,不去資料庫檢查帳密,我只要確定帳號密碼不一致就可以,所以我用Shim 隔離我的測試程式與實際的程式。


6. 要如何自動Build 自動單元測試?  => TFS 現在5人內免費!
  • 如果你是 one person team (獵人的磊扎,就是下圖那一位,1個人可以當5個人),那你可以不用考慮這個了 "持續整合/持續集成"這個議題,你自己應該會持續的 build,並且你的開發環境上就是 releasable build...

  • 如果你不是 one person team,一定會遇到版本控制、自動整合Build、自動測試的議題。在這部分,我是使用免費的tfs,現在是5人以內免費,適合small team (M$可能不知道台灣軟體廠商 >5個工程師的專案是很大的專案 哈...)。這一段的內容我是參考這一本書 軟體測試實戰 Visual Studio & Team Foundation Server (推! 雖然是visual studio 2010 ,但是還是很有參考價值,不知道會不會改版..) 書中的 "10-03 自動化組件與測試" 。使用方式大致是這樣:
    • (1) Check in 最新的程式版本到 tfs,Solution 應該也包含測試的專案!
    • (2)建立新的 組建定義,設定一下組建方式等等,譬如指定星期一到五中午12:30 tfs自己build自己測試...
    • (3)每天跑完後回去看結果,因為組建設定中,已經包含執行自動測試,所以在build完成後,tfs也會測試,測試的結果也在報告中,下圖是我前幾天的測試結果,可以看到單元測試錯誤! 



後記
  前前後後約花了約 1個月在Survey、熟悉這些工具的操作,並且在目前開發的系統加入單元測試。在開始加入單元測試後,發現了一些自己的盲點 (function執行的結果與預期不一樣),修改後也增加了程式的 robustness, Unit Test 好物!


參考資料

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

#異質資料介接