ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

.Net调用SAP RFC接口全流程:环境搭建、代理类生成与错误排查

.Net调用SAP RFC接口全流程:环境搭建、代理类生成与错误排查 简介一份记录 .NET 框架调用 SAP RFC 接口读取数据完整过程的实战文档面向需打通 SAP 与 .NET 系统的软件开发人员特别适合负责企业级 ERP 集成、正在踩坑排错的工程师参考。文档系统梳理了从环境准备到编码实现的全链路先讲清 VS2003、SAP.Net Connector 2.0、Java Runtime、SAP Logon 等关键依赖为何缺一不可再给出 Winform 项目中添加 SAP 引用、生成 Proxy、配置连接并调用 RFC 的完整代码示例。针对实际操作中最常见的 saplogon.ini 配置问题文档明确提示清空 SysName 与 SrvPort 下的内容避免连接时反复查找 Messaging Server同时补充了 Web App 场景下的差异实现便于读者在不同项目形态间迁移。资源为单个 PDF 文件大小 3.06MB携带与离线查阅都很方便。该文档记录了作者反复研究、重装系统后总结出的完整调试经验现有 285 人学习下载对刚接触 SAP RFC 的 .NET 开发者是一份高性价比的入门排错参考资料。1. .Net 调用 SAP RFC 接口一条最容易劝退人的集成链路在软件开发里.Net 调用 SAP RFC 接口算得上是最容易劝退新人的集成场景之一。我为了把这条链路跑通前后重装了三次系统翻遍了德文、日文、英文和中文资料最后还是师傅加几位高手一起才把问题定位清楚。卡点根本不在 C# 语法而在版本匹配SAP.Net Connector 2.0 只认 .Net Framework 1.1开发环境必须固定在 VS2003。很多人一上来用新版本 IDE 建项目连 SAP Connector Proxy 类都生成不了后面全是空谈。这篇文章把完整过程拆开讲环境安装、SAP 侧创建 RFC、Winform 调用、Web 场景差异、常见错误码排查再到写入数据时 TABLES 参数转换的边界。适合正准备接 SAP 接口、身边又没有前人指路的 .Net 开发者按章节一步步复现即可。2. 环境准备为什么必须 VS2003 和 SAP.Net Connector 2.0一上来就直奔代码大概率会卡死在代理类生成这一步。我先把环境讲透因为这是整条链路里最容易出现“玄学”问题的地方。很多人装完 SAP.Net Connector 2.0 以后发现 VS2010 里根本找不到相关模板于是怀疑安装包坏了。其实不是坏了是版本约束太死。2.1 版本矩阵SAP.Net Connector 2.0 只认 .NET Framework 1.1SAP.Net Connector 2.0 是 SAP 官方最早提供的 .NET 桥接组件它把 RFC SDK 的底层调用封装成 SAP.Connector.dll 和 SAP.Connector.Rfc.dll 两个托管程序集同时提供了一套从 WSDL 生成强类型代理类的工具。这套工具生成的代理代码以 .NET Framework 1.1 为编译目标IDE 集成深度绑定 VS2003。虽然在更高版本的 CLR 上1.1 程序集通常也能被加载但代理类生成向导只在 VS2003 的项目模板里存在所以实际开发环境被锁死是硬约束。这里有一个常见误用很多人搜到 NCo 3.0以为和 SAP.Net Connector 2.0 是一回事。NCo 3.0 的目标框架是 .NET Framework 4.x命名空间和 API 风格与 2.0 差别很大。如果你们项目没有历史代码限制直接上 NCo 3.0 更省事但本文所有示例都基于 2.0。因此先确认项目背景再选型。版本匹配关系如下组件版本要求用途IDEVS2003 (7.1)唯一能创建 SAP Connector Proxy 类的环境.NET Framework1.1代理类代码的编译目标SAP.Net Connector2.0提供 SAP.Connector.dll 与 SAP.Connector.Rfc.dllJava Runtime任意可用 JRE导入 RFC Function 元数据时解析 WSDLSAP GUI任意版本验证账号、服务器、系统号等连接参数2.2 安装四件套与验证方法按下面顺序装可以绕开大部分问题安装 VS2003确保安装时勾选 .NET Framework 1.1 SDK安装 SAP.Net Connector 2.0安装后确认安装目录里同时存在 SAP.Connector.dll 和 SAP.Connector.Rfc.dll安装 Java Runtime Environment这一步容易被忽略。导入 RFC Function 时工具要调用 Java 解析服务端返回的元数据没有 JRE 会一直停留在 Loading 画面安装 SAP GUISAP Logon。它不只是日常登录工具关键功能是帮你验证 SAP 服务器地址、系统号、客户端号这些参数是否真实有效。装完以后做三个验证动作。第一个是在 VS2003 里新建 Windows 应用程序右键添加引用如果能找到 SAP.Connector 相关程序集说明安装成功。第二个是在“添加新项”对话框里能看到 SAP Connector Proxy 模板。第三个是在命令行执行 java -version 能正常输出版本号。如果引用时找不到 DLL可以直接浏览安装目录手动添加。我遇到过 GAC 注册失败的情况手动引用后一切正常。这个细节很多人不知道卡住时值得先试。2.3 saplogon.ini 清坑绕开 Messaging Server 的隐形开关还有一个不起眼但能卡住你好几个小时的点C:\WINDOWS\saplogon.ini。这个文件同时被 SAP GUI 和 SAP.Net Connector 读取。打开后你会看到类似下面的结构[MSSysName] Item1某个SAP系统名 [MSSrvPort] Item13600如果这两个小节下面不是空的SAP.Net Connector 建立连接时会被引导去访问 SAP 的 Messaging Server然后四处寻找 sapmsg.ini。最常见的报错是“无法打开或者无法找到 sapmsg.ini”以及 (102) RFC_ERROR_COMMUNICATION: Connect to message server failed。解决方式很简单把 Item1 后面的值清空保存后再试。[MSSysName] Item1 [MSSrvPort] Item1实际操作时我一般先备份原文件因为 SAP GUI 如果依赖 Message Server 做负载均衡登录清空后需要按原样恢复。这个文件也可能同时出现在用户 Profile 目录下最好两个位置都检查一遍。另外某些机器上该文件是只读的需要用管理员权限改。改完不用重启直接重新运行程序即可。为什么这个坑这么隐蔽因为报错信息里只提 sapmsg.ini你第一反应是去搜这个文件存不存在而不是回头检查正在读取的 saplogon.ini。等你发现是残留配置在引导路径时几个小时已经过去了。3. Winform 调用 RFC从创建代理类到 DataGrid 显示数据环境就绪后走一遍最典型的读取流程。这一章用 Winform 做载体把后面 Web 场景里也会用到的核心对象全部串起来。整体链路是SAP 侧准备 RFC 函数VS2003 里导入生成代理类写代码调用并转换结果。3.1 SAP 侧创建 RFC FunctionSE37 与参数定义先要确保 SAP 那边存在可被外部调用的 RFC 函数。登录 SAP GUI 后用事务码 SE37 打开 Function Builder新建函数命名按 SAP 惯例建议使用 Z 开头比如 Z_GET_CUSTOMER在 Import 页签定义输入参数比如客户号、查询关键字在 Tables 页签定义输出表比如客户列表结构行结构可以直接使用 SE11 里的数据字典结构激活函数并确认函数属性的“处理类型”为远程启用的模块也就是 RFC 类型。这里有个容易踩的点函数所属的功能组必须对 RFC 调用开放调用账号要有对应执行权限。权限问题在创建时不会暴露外部调用时直接表现为 (103) 登录失败让人误判成密码错误。3.2 生成 SAPProxy1.sapwsdl代理类模板回到 VS2003 项目右键添加新项会看到多了一个 SAP Connector Proxy 模板。选择后生成一个 .sapwsdl 文件默认叫 SAPProxy1.sapwsdl。它本质上是一个 WSDL 描述后续把 SAP 上的 RFC Function 导入到这个文件工具会调 JRE 解析元数据并生成强类型类。导入完成后你会看到两类东西。一类是代理类 SAPProxy1里面包含所有已导入 RFC 的调用方法。另一类是一组强类型 Table 类比如 BRFCKNA1Table。这个类名不是随便来的它由 RFC 的 Tables 参数结构决定。原文里特别提示过“BRFCKNA1Table 可不是随便写的”在导入时工具会根据 RFC 元数据自动映射。如果看不到模板说明 SAP.Net Connector 2.0 没装好或者项目根本不是 VS2003 项目。这个阶段不要硬写代码先把代理类导出来。3.3 按钮事件完整代码连接、调用、转换Form 上放一个 TextBox 输入客户关键字一个 Button 触发调用一个 DataGrid 显示结果。按钮事件代码如下private void button1_Click(object sender, System.EventArgs e) { SAP.Connector.SAPLogonDestination myDest; SAP.Connector.SAPConnection myConn; SAPProxy1 myProx; BRFCKNA1Table xMara; System.Data.DataTable dMara; try { // 设置 SAP 连接的 Destination 信息 myDest new SAP.Connector.SAPLogonDestination(); myDest.DestinationName ECC6; // SAP 系统名称 myDest.Client 800; // 客户端号 myDest.Username RFC_USER; // 访问用户名 myDest.Password ******; // 密码 // 创建连接并打开 myConn new SAP.Connector.SAPConnection(myDest); myConn.Open(); // 创建代理实例并关联连接 myProx new SAPProxy1(); myProx.Connection myConn; // 调用 RFC 函数把返回数据放入强类型表 xMara new BRFCKNA1Table(); myProx.Rfc_Customer_Get(, this.textBox1.Text, ref xMara); // 转成 ADO.NET DataTable 并绑定界面 dMara new System.Data.DataTable(); dMara xMara.ToADODataTable(); dataGrid1.DataSource dMara; dataGrid1.Refresh(); myConn.Close(); } catch(Exception ex) { textBox1.Text ex.ToString(); } }这段代码的逻辑分四段。第一段是目标描述。SAPLogonDestination 负责描述“连到哪”包括系统名、客户端、账号、密码。这里的 DestinationName 不是随便填的字符串它需要与 SAP 服务器上的系统名或 saplogon.ini 里的系统条目对应。Client 是三位数字比如 800、300。第二段是连接建立。真正持有 RFC 会话的是 SAPConnection 对象Open() 会触发 RFC 握手。如果只设了 DestinationName 而没设 AppServerHost 和 SystemNumberOpen() 就会报 Missing R3NAME 或 ASHOST 类错误这一点在第 5 章详细展开。第三段是核心调用。SAPProxy1 是刚才生成代理类Rfc_Customer_Get 就是 RFC 函数对应的方法。第一个参数传空字符串表示不限制字段第二个参数是查询关键字第三个参数 ref xMara 是输出表。方法签名里的 ref 很关键说明该参数既可能是输入也可能是输出你必须在外面先 new 出来否则传引用时会报空对象错误。第四段是数据转换。ToADODataTable() 是 SAP.Net Connector 2.0 提供的方法把 RFC 返回的强类型表一次性转成 DataTableDataGrid 直接绑定即可。这里不需要逐行拷贝省掉了很多样板代码。3.4 空表与异常显示的两个细节这段有两个细节值得单独说。第一RFC 返回的表可能一行都没有。这时 ToADODataTable() 返回的是一个空 DataTable绑定到 DataGrid 后界面就是空的。千万不要直接访问强类型表的行索引否则会抛 JCO_ERROR_RESOURCETrying to access row values in a table which does not have any rows yet。这个异常在第 5 章会再次出现它是读取场景里最高频的误报错之一。第二示例代码里 textBox1 同时承担输入和异常显示这是原始代码里的写法实战里不建议照抄。我一般会拆成 TextBox 和 Label 两个控件异常信息用 ex.ToString() 完整显示因为 SAP 异常往往带错误码 (101)、(102)、(103) 和内部函数上下文截断后很难定位。4. Web App 与 Web Service连接管理是两个世界同样的 RFC 调用在 Web 场景里有两个明显变化连接不再手动 Open/Close而是交给 SAPLoginProvider 管理WebForms 还要求显式调用 DataBind。这一章把两种场景的差异和写法都过一遍。4.1 Web AppSAPLoginProvider 与 DataBindWeb App 的后台代码里代理类和 RFC 调用部分与 Winform 几乎一样区别在于连接来源和数据绑定时机。直接看代码private void Button1_Click(object sender, System.EventArgs e) { SAPProxy1 proxy new SAPProxy1(); BRFCKNA1Table brfcknA1Table1 new BRFCKNA1Table(); try { // 从当前页面登录状态中获取 SAP 连接 proxy.Connection SAP.Connector.SAPLoginProvider.GetSAPConnection(this); // 调用 RFC 函数 proxy.Rfc_Customer_Get(, this.TextBox1.Text, ref brfcknA1Table1); // WebForms 需要显式绑定数据 this.DataBind(); } catch(Exception ex) { // 获取连接失败时通常忽略后续会自动触发重新登录 } }这段代码的核心是 SAPLoginProvider.GetSAPConnection(this)。它会检查当前页面上下文的登录状态如果没有有效连接会跳转到登录页。这意味着 Web App 必须有一个登录页先执行过否则 GetSAPConnection 永远拿不到连接。异常分支可以写得很轻业务方法里不需要手动连接 SAP 服务器。还有一个明显的差异Winform 里绑定数据源后 DataGrid 会自动刷新WebForms 里必须手动调用 this.DataBind()。如果忘了这一句数据不会渲染到页面上但也不报错看起来就像“没查到数据”。这个坑我栽过一次后来就习惯在每次调用后强制调用 DataBind。4.2 SAPLogin1.aspx登录页与连接持久化SAP.Net Connector 2.0 安装后VS2003 项目模板里会多出 SAPLogin1.aspx 这个页面。它负责把用户的 SAP 账号、密码、客户端、语言等信息保存到登录上下文。登录按钮的代码逻辑如下string systemNumber 00; // SAP 系统号一般与服务器实例一致 private void Login_Click(object sender, System.EventArgs e) { this.destination1.Username this.user.Text; this.destination1.Password this.password.Text; // 设置 SAP 服务器地址与系统号 this.destination1.AppServerHost 192.168.1.10; this.destination1.SystemNumber short.Parse(systemNumber); // 客户端号可能为空做个保护 try { this.destination1.Client short.Parse(this.client.Text); } catch(Exception) { this.destination1.Client 0; } // 语言可选常用 EN 或 ZH if (this.language.Text.Length 0) { this.destination1.Language this.language.Text; } try { SAP.Connector.SAPLoginProvider.OpenSAPConnection(this, destination1.ConnectionString, this.persist.Checked); } catch (System.Exception exception) { this.message.Text exception.ToString(); } }这里的 destination1 是页面上放置的一个 SAP Destination 控件。Username、Password、AppServerHost、SystemNumber、Client、Language 组合起来就是一条完整的连接描述。ConnectionString 属性会把这些值拼成类似下面的连接串格式ASHOST192.168.1.10 SYSNR00 CLIENT800 USERRFC_USER PASSWD******这个连接串格式后面会反复用到包括 Web Service 的连接池也一样依赖它。注意 SYSNR 是字符串形式的两位系统号Client 是 short 类型所以代码里用 Parse 做了转换。第三个参数 persist.Checked 控制登录状态是否持久化。为 false 时连接信息只保存在 Session 里会话结束就失效为 true 时会写入持久化 Cookie下次访问免登录。从安全角度说把 SAP 密码写入持久化 Cookie 有泄露风险我一般建议保持 false特别是面向公网的 Web 应用。4.3 Web ServiceGetConnectionFromPool 与 Destination 配置Web Service 与 Web App 的差别更大。先看完整示例这个例子调用的 RFC 是 Rfc_Get_Local_Destinations返回本机可用的 Destination 列表输出类型是 RFCHOSTSTableprivate void InitializeComponent() { this.components new System.ComponentModel.Container(); this.destination1 new SAP.Connector.Destination(this.components); // // destination1 // this.destination1.AppServerHost 10.1.1.100; this.destination1.Client ((short)(800)); this.destination1.Language EN; this.destination1.Password ******; this.destination1.SystemNumber ((short)(00)); this.destination1.Username RFC_USER; } [WebMethod] public DataSet Exploding_BOM(string Language) { try { SAPProxy1 mProxy new SAPProxy1(); // 从连接池获取连接 mProxy.Connection SAP.Connector.Connection.GetConnectionFromPool(this.destination1.ConnectionString); RFCHOSTSTable mStpoxtable new RFCHOSTSTable(); // 取当前日期并转为字符串 DateTime mNow DateTime.Now; string str_now mNow.ToString(); mProxy.Rfc_Get_Local_Destinations(Language, ref mStpoxtable); mProxy.Connection.Close(); mProxy.Connection null; // 把返回表放入 DataSet 返回给客户端 DataSet mReturn new DataSet(); mReturn.Tables.Add(mStpoxtable.ToADODataTable()); return mReturn; } catch (Exception ex) { return DataError(ex); } } public DataSet DataError(Exception ex) { DataSet errDS new DataSet(Errors); DataTable errTable errDS.Tables.Add(Error); errTable.Columns.Add(Message); errTable.Rows.Add(new Object[] {ex.Message}); return errDS; }这个写法有三个要点。第一Destination 在 InitializeComponent 里集中配置不要在 WebMethod 里每次 new 一个。Web Service 可能同时被多个客户端调用连接参数集中配置一是便于修改二是避免多次创建相同对象。第二GetConnectionFromPool 从连接池取连接比每次都 Open 新建高效很多。但调用完成后必须 Close并且把 Proxy 的 Connection 置为 null否则连接池里的连接一直被占用最终把 SAP 服务器的可用连接数打满。第三返回值用 DataSet 而不是强类型表是为了让调用方可以用标准 XML 反序列化。DataError 方法把异常统一包装成 DataSet调用方拿到后先检查表名是不是 Error就能统一处理业务数据和异常。我后来在这个基础上又加了错误码字段排查效率更高。这里还要强调一个 Web Service 特有的坑如果 WebMethod 直接抛异常ASP.NET 会把它包装成 SOAP Fault客户端解析很痛苦。把错误信息放进 DataSet 返回是一件“虽然丑但双方都好处理”的做法。4.4 参数顺序与 RFC 方法签名从源码里可以注意到代理类方法名与 RFC 函数名的对应有规律比如 Rfc_Customer_Get、Rfc_Get_Local_Destinations。方法签名的参数顺序严格遵循 SE37 里 Import、Export、Tables 的定义顺序。所以当你拿到代理类后一定要先看 IDE 自动生成的签名不要凭 SE37 里的排序记。我见过同事把 Export 参数当成 Import 传编译能过但运行时报参数类型或数值异常排查半天才发现是顺序问题。5. 避坑RFC 调用最常见的 5 个错误码与排查顺序前面流程跑通了这一章写我调试时贴在墙上的错误码。SAP 的 RFC 异常信息里都带数字编号比如 (103) 或 (102)这个编号比错误文本更可信。下面按排查优先级排序每个都是实际遇到过的坑。5.1 (103) RFC_ERROR_LOGON_FAILURE账号密码正确仍失败现象调用 myConn.Open() 时报 Name or password is incorrect (repeat logon)有些场景还会显示 Timeout。原因不一定是密码错。我当初新建了一个用户并授予 SAP_ALL 权限依然报 103。SAP 的 RFC 外部调用要求账号具有 S_RFC 授权对象并且能访问对应功能组。DDIC 初始用户通常不允许作为外部 RFC 调用账号。密码策略也会导致 103例如密码过期或首次登录强制修改。解决先用 SAP GUI 以同一账号登录同一个客户端排除密码问题。然后在 SU01 里检查用户的授权至少包含 S_RFC。最后确认所调函数的功能组已被授权。如果功能组没开放错误表现和密码错误完全一样这是最容易误判的一条。5.2 (101) RFC_ERROR_PROGRAMMissing R3NAME 或 ASHOST 连接参数现象Open() 时报 Missing R3NAME... or ASHOST... in connect_param in RfcOpenEx。原因SAPLogonDestination 里只填了 DestinationName没有提供 AppServerHost 和 SystemNumber。Connector 想用 DestinationName 去解析服务器配置结果落空。解决把连接参数补全。我一般会同时设置 DestinationName、AppServerHost、SystemNumber、Client。这个错误基本是配置缺失不需要去查 SAP 服务器本身。注意 SystemNumber 是两位数字比如 00对应 SAP 实例的服务端口。5.3 (102) RFC_ERROR_COMMUNICATION连接 gateway 或 message server 失败现象报 Connect to SAP gateway failed或者 Connect to message server failed有时伴随无法找到 sapmsg.ini。原因如果报 message server大概率是第 2 章提到的 saplogon.ini 里残留了 [MSSysName] 和 [MSSrvPort] 配置Connector 被引导到 Messaging Server。如果报 gateway failed通常是 SAP 的 gateway 服务没启动或防火墙挡了对应端口。解决先清空 saplogon.ini 中两个小节的内容。然后用 telnet 测试应用服务器的 33xx 端口系统号是 00 就测 3300。端口不通时优先查网络和防火墙SAP 侧配置再正确也无济于事。5.4 (106) JCO_ERROR_RESOURCE返回表没有行却去取数据现象连接和调用都正常但我访问强类型表的某一行的值时崩溃提示 Trying to access row values in a table which does not have any rows yet。原因RFC 返回的表是空表代码紧接着取了第一行。空表不是异常而是正常业务情况比如查询条件没有匹配数据。解决取值前先判断行数。通过 ToADODataTable 得到 DataTable 后检查 Rows.Count 再绑定如果直接操作强类型表先看 RowCount 属性。我习惯在调试时把行数打到标题栏或日志里空表一眼就能看出来。5.5 (122) JCO_ERROR_CONVERSION数字入参超过字段长度现象调用带数字参数的 RFC 时报 Integer 4234243 has to many digits at field PO_ITEM。原因入参数字长度超过 SAP 数据字典里字段定义的域长度。比如 PO_ITEM 在 SE11 里是 NUMC 5 类型你传了 7 位数SAP 转换层直接拒绝。解决先在 SE11 查看字段定义再对入参做长度校验。这个错误提醒我RFC 调用前要按 SAP 的数据字典约束数据而不是按 C# 的 int 类型判断。C# 侧 10 位数是合法 intSAP 侧只有 5 位容纳空间差异就在这里。6. 从读数据到写数据TABLES 参数转换与一个调试习惯前面全部是读数据场景。评论里被问得最多的问题与通过 TABLES 参数向 RFC 写回数据有关。根因在于 SAP.Net Connector 2.0 生成代理方法时TABLES 参数是强类型表对象不是 ADO.NET 的 DataTable。直接拿 DataTable 作为入参编译都过不去如果拿强类型表但没有逐行填充传进去的只是一个空表RFC 内部自然什么也写不进去。常见做法是先把业务数据逐行写入强类型表的实例再把这个实例作为 ref 参数传给代理方法。强类型表内部对应的是 RFC 的 Table 句柄逐行填充时要用表对象提供的 Insert 或 Append 一类方法不同版本生成的方法名略有差异以你 IDE 里实际生成的接口为准。我一般会在调用前打印一下表的行数确认数据真的装进去了而不是默默传了一个空表。这个检查花不了十秒钟能省掉大量的“怎么不生效”排查时间。还有一个调试习惯是我后来所有 SAP 接口项目都强制走的。第一步在 SAP GUI 里用 SE37 以相同参数执行一次函数确认函数本身没有问题。第二步在 C# 里把连接串参数逐个打印出来注意不要打印密码确认 AppServerHost、SystemNumber、Client 三个值与 SAP GUI 一致。第三步调用前后各打印一次入参表行数和返回表行数与 SE37 的执行结果比对。这三步走完问题基本能定位到 SAP 侧还是 .Net 侧。另外一个小技巧RFC 调用出错时不要只看 ex.Message把 ex.ToString() 完整留在日志里因为内部异常才包含 RFC 错误码和函数上下文。我们曾经靠一段完整的异常堆栈直接定位到 SAP 侧短转储 ST22 里的 ABAP 错误省掉了两边来回扯皮的过程。从那以后我每次接 SAP 接口都强制先走一遍 SE37 单测再进 C# 调。这个习惯帮我省下的时间远超过最初多花的十分钟。希望帮到你。本文还有配套的精品资源点击获取
返回列表