ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

.Net调用SAP RFC实战:从NCo配置到数据映射全解析

.Net调用SAP RFC实战:从NCo配置到数据映射全解析 简介这份汇编资料聚焦 .Net 框架调用 SAP RFC 接口读取数据的完整实战过程面向需要做 SAP 与 .NET 系统集成的软件开发工程师尤其适合初次接触 SAP.Net Connector 2.0、苦于版本兼容问题的读者。内容从关键前提讲起明确 VS2003、SAP.Net Connector 2.0、Java Runtime Environment 与 SAP Logon 的安装要求随后以 Winform 示例为主线逐步演示在 SAP 中创建 RFC、在 VS 中生成 SAPProxy 代理类、配置连接参数并获取客户数据的全过程。资料还对 saplogon.ini 配置、SAP 服务器连接报错等常见坑点做了专门提醒并延伸介绍了 Web App 与 Web Service 场景下的调用差异。压缩包内共 1 个 PDF 文件约 3.06MB内容为完整调试纪实与代码说明便于按步骤对照实践和排查问题。目前已有 284 人学习下载适合从事企业系统集成、希望快速打通 .Net 与 SAP 数据通道的开发人员参考。1. 用 .Net 调 SAP RFC 之前先搞懂这是函数调用不是查询做数仓、报表和 MES 集成的 .Net 工程师迟早会碰到一个问题SAP 的数据怎么出来。ERP 侧不会把数据库连接直接开放最常见的通道就是 SAP RFC 接口。所谓 RFCRemote Function Call是 SAP 的远程函数调用协议外部系统通过它调用 SAP 里的函数模块传入参数、拿回结果集。很多人第一次上手时把它当成查数据库这是最大的误区RFC 不是 SELECT而是一次带参数、带异常约定的函数调用。标题里的“实战纪实[汇编]”说明这更像一份工程资料汇总但真正值钱的是环境怎么配、参数怎么填、读出来的数据怎么解析。这篇文章把 .Net 调 SAP RFC 的完整路径拆开从 NCo 选型讲到表参数映射和排错方法。适合刚接手 SAP 集成的开发也适合做过几次但总在字段映射上翻车的读者当成一份对照清单用。2. SAP RFC 接口定义的四个参数区与 NCo 调用前置准备2.1 RFC 接口定义的四个参数区及 .Net 侧映射逻辑SAP 的 ABAP 函数模块一旦勾选“允许远程调用”就能对外以 RFC 方式暴露。一次 RFC 调用就是向 SAP 系统发起一次完整的请求从 .NET 侧传输配置、用户凭据和业务参数SAP 执行函数后把结果返回。这里的关键认知是接口定义不是一张表结构而是四块参数区的组合。每份 ABAP 函数接口固定包含四个区域导入IMPORTING用于从外部接收值导出EXPORTING用于把单个值返回给外部表TABLES用于传输行数据集异常EXCEPTIONS用于把业务错误码抛给调用方。SAP .NET ConnectorNCo里对应的 RfcFunction 对象把这四块原样平移到 .NET映射关系如下SAP 函数模块接口区NCo 侧访问入口典型用途IMPORTINGfunction.SetValue(参数名, 值)传入单号、日期、公司代码等过滤条件EXPORTINGfunction.GetString(参数名) / GetInt拿回总数、处理状态、返回码TABLESfunction.GetTable(表名)读取行数据集最常见的数据出口EXCEPTIONScatch RfcAbapException读 ex.Key按业务异常码做分支处理实际读取业务数据时九成以上的时间在处理 TABLES 参数。比如 FICO 的会计凭证、MM 的物料主数据、BP 主数据的抽取数据都在表参数里按行返回导出参数里往往放着“总条数”“消息文本”这些对账信息很多开发只读了表就收工漏掉了最重要的结果校验。NCo 侧操作顺序是固定的先用仓库对象创建函数实例然后填导入参数、填表参数调用最后从同一函数实例里取导出值和输出表。一个 RfcFunction 实例可以重复使用但要注意每次 Invoke 前把上一轮的表参数清掉否则数据会累积。2.2 NCo 选型与环境核查从 sapnwrfc.dll 到 .NET 版本SAP 官方提供的 .NET 连接器是 SAP .NET Connector简称 NCo。早期还有通过 P/Invoke 直接调 Classic RFC SDK 的写法现在基本都被 NCo 取代。NCo 3.0 面向 .NET FrameworkNCo 3.1 增加了对 .NET Core 和 .NET 5 以上版本的支持命名空间统一是 SAP.Middleware.ConnectorAPI 几乎没有差异。因此下面的代码在 .NET Framework 4.x 和 .NET 6/8 项目里都可以编译运行。最容易翻车的点不在 C# 代码而在 NCo 的组成结构。NCo 运行时包含一个托管 DLLSAP.Middleware.Connector.dll和一个原生库 sapnwrfc.dll。sapnwrfc.dll 负责底层网络协议必须能被进程加载且位数必须和宿主的应用程序一致x64 进程配 x64 的 dllx86 进程配 x86 的 dll。IIS 部署时应用程序池的“启用 32 位应用程序”设置和 dll 位数不一致启动阶段就会报 BadImageFormat 之类的异常。网络层面RFC 走 SAP 应用服务器的 33xx 端口其中 xx 是系统编号System Number。系统号 00 对应 3300系统号 01 对应 3301。排查问题前先确认这条链路是通的能省一半时间。完整的前置检查项按下面这个表格过一遍检查项操作方式典型故障sapnwrfc.dll 可用NCo 安装后将 dll 放到运行目录或系统 PATH找不到 dll、加载原生库失败位数匹配对比 dll 与应用程序池/进程位数x86 dll 配 x64 进程直接启动失败RFC 端口连通telnet 主机 3300防火墙放行问题表现为超时.NET 框架版本核对 NCo 3.0 还是 3.1Framework 项目引用 3.1 包时会少兼容目标环境就绪后先做一次握手探测比直接调业务函数更容易定位问题using SAP.Middleware.Connector; var cfg new RfcConfigParameters { { RfcConfigParameters.Name, SAP_DEV }, { RfcConfigParameters.AppServerHost, 10.10.1.20 }, { RfcConfigParameters.SystemNumber, 00 }, { RfcConfigParameters.Client, 800 }, { RfcConfigParameters.User, RFC_USER }, { RfcConfigParameters.Password, your-password }, { RfcConfigParameters.Language, ZH }, { RfcConfigParameters.PoolSize, 5 } }; var destination RfcDestinationManager.GetDestination(cfg); destination.Ping(); Console.WriteLine(RFC 连接正常);这里 RfcConfigParameters.Name 就是目的地名称同一个名称加相同连接串会复用一个连接池PoolSize 指定池内最大连接数。GetDestination 本身不会真正连 SAP只是按名字取一个目的地实例Ping() 才会触发一次真实握手。如果 Ping 能过说明网络、账号、sapnwrfc.dll 这条链路都已经就绪接下来调函数才有意义。2.3 调用前先读函数元数据接口定义里藏着的字段名拿到 SAP 组给的接口文档后不要直接对着文档写代码。更常见的做法是先把函数的元数据打印出来跟文档做一次比对。字段名偶尔被文档漏写或被复制错代码里用了不存在的字段名NCo 会抛异常排查时浪费的往往是整个下午。var repo destination.Repository; var fn repo.CreateFunction(RFC_READ_TABLE); var meta fn.Metadata; for (int i 0; i meta.ParameterCount; i) { var p meta.GetParameter(i); Console.WriteLine(${p.Name} | {p.Direction}); }创建函数实例后通过 Metadata 可以遍历到每个参数的名称和方向Direction 会标识 IMPORT、EXPORT、CHANGING、TABLE。这样能一次看清这个函数到底有哪些入参、哪些出参、哪几张表也方便把代码里的 SetValue/GetString/GetTable 调用和接口定义严格对上。SAP 字段名全部大写.NET 侧写字符串时大小写必须一致这是初学者最常见的低级报错来源。3. 最小可运行的 .Net 调用 RFC 代码与参数拆解3.1 用 RFC_READ_TABLE 读一张表的最小代码RFC_READ_TABLE 是 SAP 自带的通用读表函数不需要 ABAP 开发介入就能把透明表内容读出来常被用来验证链路、做开发期调试。生产环境长期依赖它并不推荐但作为第一段能跑通的代码非常合适。先把完整的最小实现贴出来using SAP.Middleware.Connector; var cfg new RfcConfigParameters { { RfcConfigParameters.Name, SAP_DEV }, { RfcConfigParameters.AppServerHost, 10.10.1.20 }, { RfcConfigParameters.SystemNumber, 00 }, { RfcConfigParameters.Client, 800 }, { RfcConfigParameters.User, RFC_USER }, { RfcConfigParameters.Password, your-password }, { RfcConfigParameters.Language, ZH } }; var destination RfcDestinationManager.GetDestination(cfg); var repo destination.Repository; var fn repo.CreateFunction(RFC_READ_TABLE); fn.SetValue(QUERY_TABLE, MARA); var options fn.GetTable(OPTIONS); options.Append(); options.SetValue(TEXT, MATNR LIKE 100000%); fn.Invoke(destination); var data fn.GetTable(DATA); foreach (IRfcStructure row in data) { Console.WriteLine(row.GetString(WA)); }逻辑说明QUERY_TABLE 指定要读的透明表这里用 MARA 是物料主数据表OPTIONS 表存放过滤条件每一行往 TEXT 字段写一条类 SQL 的表达式DATA 表是返回结果每行只有一个 WA 字段存放按固定宽度拼接的整行数据。Invoke 是真正发起 RFC 调用的动作调用后从同一个函数实例读取输出。这段代码里值得注意的细节是 OPTIONS它是表参数先 Append 新增一行再给 TEXT 赋值。条件写在 SAP 侧相当于把 WHERE 子句下推到了 SAP 数据库而不是把全表拉回来在 .NET 里过滤。这对后续大数据量处理是首要优化思路。3.2 OPTIONS / FIELDS / ROWCOUNT 的正确用法与截断坑RFC_READ_TABLE 看起来简单实际用起来有四个参数必须搞清楚否则读出来的数据可能缺列或者被截断。参数速查表如下参数名参数区作用关键细节QUERY_TABLE导入要读取的表名必须大写支持透明表和部分视图OPTIONS表行过滤条件每行是 WHERE 子句的一段表达式FIELDS表指定输出字段不传则输出整表字段按最宽行定输出长度ROWCOUNT / ROWSKIPS导入分页控制分别表示读取行数和跳过的行数不传 FIELDS 时DATA 表每行的 WA 字段默认最大 512 个字符。如果表字段很多或者存在长文本类型的列WA 会被截断读出来的数据后半段是断的。规避办法是显式指定 FIELDS 并配上 DELIMITER 分隔符fn.SetValue(QUERY_TABLE, MARA); fn.SetValue(DELIMITER, |); var fields fn.GetTable(FIELDS); string[] cols { MATNR, MTART, MEINS, MAKTX }; foreach (var col in cols) { fields.Append(); fields.SetValue(FIELDNAME, col); } fn.Invoke(destination); var data fn.GetTable(DATA); foreach (IRfcStructure row in data) { string[] parts row.GetString(WA).Split(|); // parts[0] 对应 MATNRparts[1] 对应 MTART以此类推 Console.WriteLine(${parts[0]} | {parts[1]} | {parts[2]} | {parts[3]}); }这种写法的输出逻辑是FIELDS 表里定义字段的顺序就是输出列的顺序指定 DELIMITER 后SAP 拼接输出行时不再用固定宽度而是用分隔符把各列隔开。之后在 .NET 侧按分隔符切割就能把行数据还原成字段集合。注意分隔符不要用普通空格或制表符因为字段值里可能自带这些字符竖线或分号更安全。另外有些 SAP 字段比如物料描述 MAKTX本身就可能包含分隔符稳妥的做法是在 SAP 侧自定义函数里用结构体输出而不是依赖字符串拼接。3.3 中文乱码与条件下推的正确姿势RFC_READ_TABLE 的 OPTIONS 参数直接拼在 SQL WHERE 条件里如果条件中包含中文常量SAP 侧的代码页转换经常导致查不到数据或乱码。这个坑不是 NCo 的编码问题而是 RFC_READ_TABLE 把整个条件当字符串处理中文经过不同代码页转换后失真。规避方式有两种一种是不在 OPTIONS 里写中文改用英文能唯一标识的字段做条件或者干脆读全量后在 .NET 侧过滤另一种是换用带正式导入参数的 BAPI 或自定义函数让 SAP 侧通过变量接收中文值不经历字符串拼接。LANG 参数设为 ZH 只会影响 SAP 返回的消息文本语言不会改变业务数据本身的编码。如果 SAP 系统是非 Unicode 代码页读出来的中文字段本身就可能是乱码的这时候在 .NET 侧再怎么转码都无济于事必须让 SAP 基系统保证数据编码正确。4. 实战读取数据表参数映射、大数据读取与异常排查4.1 从 BAPI 输出表按字段名取值用 RETURN 表对账生产环境读取业务数据推荐走标准 BAPI 或 SAP 组按需求开发的自定义 RFC 函数。这类函数的输出表是结构化的每一行不再是一个拼好的 WA 字符串而是可以按字段名直接取值的行结构。以常见的“按条件读取物料主数据”场景为例var fn repo.CreateFunction(ZRFC_MAT_LIST); fn.SetValue(I_BUKRS, 1000); fn.SetValue(I_MATKL, 0101); fn.Invoke(destination); var ret fn.GetTable(RETURN); if (ret.RowCount 0 ret[0].GetString(TYPE) E) { throw new Exception(ret[0].GetString(MESSAGE)); } var rows fn.GetTable(T_DATA); var materials new ListMaterialInfo(); foreach (IRfcStructure row in rows) { materials.Add(new MaterialInfo { MaterialNo row.GetString(MATNR), MaterialType row.GetString(MTART), Unit row.GetString(MEINS), Description row.GetString(MAKTX) }); }这里 ZRFC_MAT_LIST 是示例函数名实际以接口文档为准。RETURN 表是 SAP 接口的通用约定TYPE 为 E 表示错误MESSAGE 里带错误文本先检查 RETURN 再处理数据能避免异常时把空数据集当成正常结果返回。IRfcStructure 的行取值方法不只有 GetString还有 GetInt、GetDecimal、GetDate 等按元数据里的字段类型选择对应方法会更准确字段类型匹配不上时 NCo 会做转换但有精度损失风险。需要注意表参数里取出来的字符串结尾有时会带空格SAP 的 CHAR 类型是定长的.NET 侧最好统一做 TrimEnd()。很多对账差异就是这里少了一个 Trim 导致的。4.2 大数据量读取分页、增量与并发怎么选数据量超过几万行时一次性读取会造成 .NET 侧内存飙升SAP 侧数据库压力也集中在一个 RFC 调用上。按可靠性从高到低我一般会依次尝试四种策略。第一种是条件下推。把尽可能多的过滤条件通过导入参数传给 SAP让数据在 SAP 数据库层先缩减。这是成本最低的方案代价是需要对业务语义拆得足够细。第二种是用 ROWCOUNT 和 ROWSKIPS 手动翻页适用于标准 RFC_READ_TABLE 这类没有业务分页参数的函数int pageSize 500; int skip 0; while (true) { var paged repo.CreateFunction(RFC_READ_TABLE); paged.SetValue(QUERY_TABLE, MARA); paged.SetValue(ROWCOUNT, pageSize); paged.SetValue(ROWSKIPS, skip); paged.Invoke(destination); var pageData paged.GetTable(DATA); if (pageData.RowCount 0) { break; } // 处理当前页 skip pageData.RowCount; }注意 ROWCOUNT 和 ROWSKIPS 是数值参数直接传 int 即可。这种翻页方式本质是重复执行同一句查询不是数据库游标如果表数据在翻页过程中发生变化可能出现某行重复或漏读对时间不敏感的主数据表比较合适业务流水表不建议用。第三种是增量接口。让 SAP 组把函数设计成接收“上次同步到的最大主键”或“时间戳 批次号”SAP 侧只返回增量数据。这是生产环境最推荐的做法数据量和重复计算量都能受控。第四种是分片并发按公司代码、库存地点等维度把数据切段用多个 destination 同时读取最后在 .NET 侧合并。并发上限受 SAP 侧许可策略和数据库负载约束不是越大越好上线前要按实际压测结果定并发放置数。4.3 按异常特征定位问题LOGON_FAILURE 到短转储速查RFC 调用的报错信息通常很明确按特征对照下表处理比反复猜测效率高异常或日志特征常见原因处理方式LOGON_FAILURE用户名密码错误、Client 号不对、账号无 RFC 权限核对连接参数检查 SAP 侧授权对象RFC_COMMUNICATION_FAILURE主机名或系统号错、sapnwrfc.dll 缺失、33xx 端口不通先 Ping 目的地再逐层查网络与 dllRFC_SYSTEM_FAILURESAP 侧执行时报错或短转储到 SAP 侧查事务码 ST22 看短转储内容授权相关错误RFC_READ_TABLE 需要 S_TABU_RFC 授权对象由 SAP 基础管理员检查角色权限函数参数不存在字段名拼写或大小写不一致用 2.3 节的元数据打印逐一核对能在 SAP GUI 上操作时SM21 看系统日志、ST22 看短转储、SU53 看授权失败详情是三个最高频的排查入口。tRFC 队列事务码 SM58主要用于异步 RFC 的失败重试同步调用读数据时一般用不上但如果你遇到“RFC 请求发了但没返回”的诡异问题可以去 SM58 确认是不是有遗留队列在干扰会话。如果 NCo 侧的托管 DLL 版本和 sapnwrfc.dll 版本不一致报错往往会在连接阶段以加载原生库失败的形态出现。判断方法很简单把 NCo 安装目录下的两个文件一起部署不要混用不同安装包的版本。5. 把 RFC 表数据批量绑成 .Net 强类型对象的两个技巧5.1 按表元数据反射生成强类型列表每个 BAPI 或自定义 RFC 函数都有一张输出表手写字段映射是最枯燥也最容易出错的环节。NCo 的 IRfcTable 自带元数据可以利用反射写一个通用转换方法把任意 RFC 表参数映射成 .Net 强类型列表private static ListT MapRfcTableT(IRfcTable table) where T : class, new() { var props typeof(T).GetProperties(); var fieldNames table.Metadata.FieldNames; var cache props .Where(p fieldNames.Contains(p.Name.ToUpperInvariant())) .ToArray(); var result new ListT(); foreach (IRfcStructure row in table) { var item new T(); foreach (var prop in cache) { var raw row.GetValue(prop.Name.ToUpperInvariant()); if (raw null) { continue; } var targetType Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; prop.SetValue(item, Convert.ChangeType(raw, targetType)); } result.Add(item); } return result; }SAP 侧字段名全大写C# 属性名按 PascalCase 规范命名方法内部统一转大写后与元数据比对省去给每个属性加映射特性的麻烦。字段类型转换时先拆掉 Nullable 包装再走 Convert.ChangeType兼容 int?、decimal? 这类可空属性。空字符串字段在部分 NCo 版本里返回 string.Empty如果发现空列被赋了空字符串而不是 null在 GetValue 之后加一个 string.IsNullOrEmpty 判断即可。这个方法的代价是反射带来的少量性能损耗。一次性转换几千行结构简单的数据几乎无感如果单表百万行且对耗时敏感就要考虑用表达式树编译委托替代反射或者在模型上改为直接按字段索引取值。但从可维护性角度看反射方案在绝大多数报表和数仓抽取场景下都是值得的选择。5.2 用导出参数核对行数把漏读暴露在眼前数据读完后先别急着存库。大多数业务函数在 EXPORTING 区会返回总条数或者在 RETURN 表里写执行消息。这类导出参数的价值在于对账。具体做法是调用完成后先把 EV_COUNT、总数这类导出参数读出来再和实际读到的数据行数对比不一致时直接打日志告警。很多漏读问题发生在分页场景。ROWCOUNT/ROWSKIPS 循环里如果发生数据变更最后一行可能被跳过条件下推的 SQL 如果有隐式转换某些记录会静默失效。光看数据不对比总数这些问题都发现不了。把导出参数和行数同时记录到日志里持续观察能尽早暴露接口函数的边界行为。另一个实用的小操作是把 2.3 节的元数据打印保存到本地文本文件里每次发布接口后自动做一次字段差异比对。SAP 组调整函数接口是常有的事提前感知要比线上报错了再回来看代码轻松太多。本文还有配套的精品资源点击获取
返回列表