PDMS-AVEVA-E3D二次开发教程(11):数据库 .NET 接口——DbElement、数据模型类与“无参 GetElement 返回 null”

📅 发布时间:2026/10/17 13:37:54
PDMS-AVEVA-E3D二次开发教程(11):数据库 .NET 接口——DbElement、数据模型类与“无参 GetElement 返回 null”
版本声明本篇基线为AVEVA E3D Design 3.1文档版本线存在 1.1 / 2.1 / 3.1教程统一以 3.1 为准前代产品AVEVA PDMS 自 2024 年 4 月 1 日起不再销售、不再提供支持。数据库侧程序集为Aveva.Core.Database.dll与Aveva.Core.Database.Filters.dllPDMS 12.0 时代文档中的Pdms.Database.dll、PDMSFilters.dll是另一代资产官方未给出旧→新对照表跨代引用必须逐个验证。文中取值方法名与属性实例成员名凡未在官方章节中逐字给出的均已在代码处标注需以本机为准。一句话结论DbElement是这个数据库接口的通用句柄但它的静态GetElement()无参调用返回的是null而不是异常——所有取不到元素的分支都必须自己判空这是这段代码里最容易漏掉、后果最隐蔽的一行。〇、认知问题读完本篇你应该能回答DbElement.GetElement()有三种重载无参调用返回什么为什么这一点必须专门记住按名称取元素时名称里为什么必须带/数据模型的四个定义类分别是什么各自负责什么为什么说DbElementType对所有元素类型只有一个类值语义是什么意思取DbElementType有哪两种方式为什么 UDET 要带冒号官方推荐用哪一种Filters/Iterators解决什么问题单位体系里的DbUnits、DbDoubleUnits、DbDimension、DbFormat各管什么一、机制解析1.1DbElement通用元素类数据库接口的核心类是DbElement位于Aveva.Core.Database——它是通用元素类也是最常用的一个。所谓通用意思是不管你的元素是设备、管道、支吊架还是结构件在 .NET 侧你拿到手的都是DbElement类型的句柄通过类型与属性两把钥匙去问它具体是什么、有哪些值。这与 PML 侧用!!ce操作当前元素的思路是一致的只不过 .NET 侧是显式持有引用。理解这一点很关键DbElement不是某一种元素的类而是所有元素的统一句柄。所以你不会写BranchElement、EquipmentElement这样的类只会写DbElement 类型判断。1.2 静态GetElement()的三种重载官方文档明确静态DbElement.GetElement()有三种重载重载形态行为无参GetElement()返回null不是抛异常按名称GetElement(含 / 的名称)名称必须包含/按 ref typeGetElement(ref, type)用引用号加类型精确定位无参调用返回null而不是抛异常是这一篇最需要背下来的一条事实因为它的后果是工程性的如果你的代码里存在故意用无参形式或参数在某个分支上没传进去的路径你会得到一个null而不会得到任何提示紧接着的element.Name之类访问会抛NullReferenceException此时调用栈指向的是你的代码很容易被误判成数据库接口有问题正确的写法是每次取元素后立刻判空并在为空时给出元素不存在 / 路径错误这类明确的业务提示。1.3 名称里为什么必须带/按名称重载要求名称包含/因为/是数据库层级路径的分隔符数据库里元素的全名是一个从顶层往下串起来的路径。工程上由此产生两条实践写死路径的方法极其脆弱。一旦上级元素改名路径失效取回来的是null。更稳妥的做法是先按路径取到稳定的上级再在其下按名称遍历筛出目标。这与第 08 篇巡检时用COLL ALL … FOR CE先钉死范围的思路一致用结构定位不要用字符串路径赌。1.4 数据模型的四个定义类官方给出的数据模型定义类是四个类负责DbElementType元素类型“这是什么”DbAttribute属性“有什么字段”DbElementTypeInstance元素类型的实例集合拿现成类型用DbAttributeInstance属性的实例集合拿现成属性用其中DbElementType与DbAttribute属于定义层面描述数据模型长什么样DbElementTypeInstance与DbAttributeInstance属于实例层面把定义好的东西拿出来直接用。这个分工决定了你写代码时的取用顺序要判断类型、要取属性优先去…Instance里拿现成的而不是自己去构造DbElementType/DbAttribute。1.5DbElementType的每类型单类 值语义DbElementType有两个很特别的性质官方文档专门点出对所有元素类型只有一个类。也就是说无论分支、设备还是结构对应的 .NET 类型都是DbElementType——不是每种元素类型对应一个 .NET 类。具有值语义比较。既然是同一个类就无法靠类型不同来区分于是它在比较时按值比较——两个DbElementType实例只要代表同一种元素类型就是相等的。这两条合起来解释了一个常见困惑为什么在代码里看不到一堆Branch、Equipment之类的类型类因为设计上就是一个类 值语义靠值来区分具体是哪种类型。工程含义不要用之外的引用比较习惯去判断类型也不要缓存以为自己拿到了某个对象引用就代表某种类型——判断类型的正确方式是类型本身的值比较。1.6 取DbElementType的两种方式与DbElementTypeInstance官方给出的获取方式是静态GetElementType有两种取法取法写法要点按类型名类型名UDET 需要带冒号按 32 位 hash 值用数值 hash 定位UDET 需带冒号这一点很关键UDET用户自定义元素类型的名称与内置类型不同必须带冒号才能被正确解析。少一个冒号GetElementType就取不到你想要的东西。官方还给出了一条明确建议推荐直接使用DbElementTypeInstance中的全局实例而不是自己去按名字或 hash 构造。理由很实在——全局实例已经由框架准备好省掉了名字拼错、hash 算错、冒号漏掉这三类错误。1.7 周边能力一览官方 .NET 定制手册中与数据库相关的章节还有Filters / Iterators筛选与迭代。当你需要按条件批量取元素时用的是这套机制而不是取全部再在内存里过滤。工程上它的意义是把筛选下推到数据库侧避免把整个模型拉进内存。Dabacon TablesDABACON 是DAtaBAse CONstructor的缩写是 Plant Marine 的数据库管理系统用于构建、导航与查询数据库结构E3D Design 沿用同一 DABACON 引擎且它是自有面向对象数据库不是 Oracle/SQL Server 关系库。Dabacon Tables这一章说明关系型的那套表概念在 DABACON 里只是一层结构视图不能直接套 SQL 思维。单位体系DbUnits、DbDoubleUnits、DbDimension、DbFormat。这一组解决的是数值 单位 格式的问题——读到的是双精度数但它带着单位语义输出时必须用格式化对象转成人类可读的字符串不要自己拼单位符号。数据库事件数据库层面的变化通知。它给出的是数据被改动的钩子适合做缓存失效、审计记录。二、完整代码与逐行剖析以下 C# 代码来源性质按官方 NCUG 4.05 记载的类名、重载与机制构造需在本地 E3D Design 环境验证。其中取值方法名如GetString/GetDouble与属性实例的成员名官方章节未逐字给出请以本机Aveva.Core.Database.dll的 IntelliSense 与 NCUG 4.05 章节为准。2.1 按名称取元素并读属性usingSystem;usingAveva.Core.Database;// DbElement 等核心类namespaceMyCompany.E3DAddin{publicstaticclassDbDemo{/// summary/// 按名称取元素并读出若干属性。/// 关键点GetElement 取不到时返回 null必须自行判空。/// /summarypublicstaticvoidGetByName(){// 注意按名称重载要求名称包含 /层级路径分隔符conststringfullName/ZONE-A/PIPE-1/BRAN-1001;DbElementelementDbElement.GetElement(fullName);// 【必做】判空。无参/取不到都不会抛异常只会安静地返回 nullif(elementnull){Console.WriteLine(元素不存在或路径错误fullName);return;}// 以下取值方法名请以本机程序集为准按官方 API 签名构造stringnameelement.GetString(DbAttributeInstance.NAME);// 名称属性stringfullelement.GetString(DbAttributeInstance.FLNN);// 全名属性Console.WriteLine(名称name);Console.WriteLine(全名full);}}}逐行剖析using Aveva.Core.Database;DbElement与DbElementType等核心类都在这里筛选相关的能力在Aveva.Core.Database.Filters.dll对应的命名空间里。const string fullName /ZONE-A/PIPE-1/BRAN-1001;路径必须带/。示例路径是示意结构不代表任何真实工程的命名。DbElement.GetElement(fullName)调用按名称重载。if (element null) { … return; }这是本篇最重要的一行。官方明确无参重载返回null按名称取不到时同样需要判空处理。不判空的代码会在下一行崩掉而崩溃点看起来像属性读取的问题误导排查方向。element.GetString(DbAttributeInstance.NAME)从属性实例里取现成的属性对象再向元素要值。方法名GetString与DbAttributeInstance的成员名如NAME/FLNN以本机为准——这一处是本篇唯一需要你亲自核对签名的地方。2.2 按 ref type 取元素usingAveva.Core.Database;/// summary/// 按引用号 元素类型取元素。/// 相比按名称这种方式不受改名影响但需要你持有有效的 ref 与 type。/// /summarypublicstaticDbElementGetByRefAndType(intrefNo,DbElementTypetype){// 第二种重载ref typeDbElementelementDbElement.GetElement(refNo,type);if(elementnull){Console.WriteLine(按 ref/type 取元素失败refrefNo);returnnull;}returnelement;}要点这种重载在元素被改过名的场景下更稳因为 ref 不随名称变化。代价是你必须先拿到 type——而 type 的获取方式见下一节。2.3 取类型两种取法与推荐的全局实例usingAveva.Core.Database;/// summary/// 演示取 DbElementType 的两种形式。/// 官方推荐优先直接使用 DbElementTypeInstance 中的全局实例。/// /summarypublicstaticvoidHowToGetType(){// 取法一按类型名UDET 需要带冒号例如 MYUDET: 这一形态DbElementTypet1DbElementType.GetElementType(BRAN);// 取法二按 32 位 hash 值// int hashOfBran 本机从 DbElementTypeInstance 取得;// DbElementType t2 DbElementType.GetElementType(hashOfBran);// 官方推荐写法直接用 DbElementTypeInstance 中的全局实例// DbElementType t3 DbElementTypeInstance.某类型全局实例;// 值语义同一种元素类型对应的 DbElementType 实例按值相等boolsame(t1DbElementType.GetElementType(BRAN));Console.WriteLine(两次取得的 BRAN 类型是否相等same);}要点DbElementType.GetElementType(BRAN)是按类型名取法如果目标是 UDET类型名必须带冒号。注释里的第二种取法与推荐的全局实例留作占位是因为这部分的具体成员名必须从本机取得DbElementTypeInstance里有哪些全局实例取决于本机的元素类型体系。不要在没核对的情况下凭记忆写成员名——这正是引用到不存在的类型的典型来源。(t1 DbElementType.GetElementType(BRAN))演示值语义两次取到的是不同实例但按值比较相等。判断元素类型的正确方式就是这种比较而不是比较引用。2.4 按类型批量取元素Filters / Iterators 的位置usingAveva.Core.Database;// 筛选相关类型位于 Aveva.Core.Database.Filters.dll命名空间以本机为准/// summary/// 批量取元素的组织方式骨架。/// 官方提供的机制是 Filters / Iterators把筛选条件下推到数据库侧/// 而不是取全部元素再在内存里过滤。/// 具体 API 签名请以 NCUG 4.05 的 Filters/Iterators 章节与本机为准。/// /summarypublicstaticvoidIterateByType(DbElementTypewantType){// 骨架示意// 1) 构造筛选条件类型 wantType可叠加更多条件// 2) 构造迭代器// 3) 逐个取 DbElement逐个判空后处理//// 由于 Filter/Iterator 的具体构造方法名在本篇依据的资料中未逐字给出// 此处不编造方法名留作按本机 IntelliSense 补全的现场作业。Console.WriteLine(请按 NCUG 4.05 的 Filters/Iterators 章节补全本方法的实现。);}为什么这里刻意留空Filters/Iterators 是官方章节标题确有其章但具体构造方法名在本次依据的官方章节摘要中没有逐字给出。按本系列铁律宁可留白也不编造方法名。你在本机用 IntelliSense 把方法名补全后这个骨架就能立刻变成可用代码——把筛选条件交给数据库侧是这里唯一要记住的工程原则。2.5 单位与格式化别自己拼单位符号usingAveva.Core.Database;/// summary/// 单位体系示意读数 → 格式化 → 输出。/// DbUnits / DbDoubleUnits / DbDimension / DbFormat 这一组负责/// 数值 单位 格式三件事官方章节均在其列。/// 具体 API 以 NCUG 4.05 对应章节与本机为准。/// /summarypublicstaticvoidFormatValue(DbElementelement){// 读数得到的是双精度数值方法名以本机为准doublerawelement.GetDouble(DbAttributeInstance.HEIGHT);// 正确的输出路径是交给格式化对象转成字符串// 而不是 raw mm 这样手工拼单位。// 例如// DbDoubleUnits units ...; // 从元素/属性上取得其单位// DbFormat fmt ...; // 取得格式化规则// string text fmt.???; // 按本机 API 补全Console.WriteLine(原始数值raw请按本机单位与格式对象输出);}工程要点数据库里存的数值永远带着单位语义。手工拼单位是交付事故的常见源头模型用毫米、报告写成英寸。官方提供DbUnits/DbDoubleUnits/DbDimension/DbFormat这一组类就是为了让数值 → 可读字符串这一步由框架完成。2.6 结果落 CSVusingSystem;usingSystem.Collections.Generic;usingSystem.IO;usingSystem.Text;usingAveva.Core.Database;/// summary/// 把一批元素的巡检结果落成 CSV。/// 注意这里用的是 .NET 的文件写入与第 08 篇 PML 侧 FILE 对象是两条不同的路。/// /summarypublicstaticvoidDumpToCsv(stringcsvPath,IListDbElementelements){// UTF8 编码避免中文在 Excel 里乱码using(StreamWriterwriternewStreamWriter(csvPath,false,newUTF8Encoding(true))){writer.WriteLine(Name,FullName);// 表头foreach(DbElementelementinelements){// 逐个判空列表里可能混入取元素失败留下的 nullif(elementnull){continue;}stringnameelement.GetString(DbAttributeInstance.NAME);stringfullelement.GetString(DbAttributeInstance.FLNN);// 简单转义名称里若含逗号会破坏 CSV 结构writer.WriteLine(Escape(name),Escape(full));}}}/// summaryCSV 字段转义含逗号/引号时用双引号包裹并转义内部引号。/summaryprivatestaticstringEscape(stringvalue){if(string.IsNullOrEmpty(value)){return;}if(value.IndexOf(,)0||value.IndexOf()0){return\value.Replace(\,\\)\;}returnvalue;}要点null会一路传播——批量取元素时任何一个环节取不到都会留下null所以在落盘前必须再判一次空。CSV 的转义也不是可选项元素名称里出现逗号并不罕见。三、常见报错与排查3.1NullReferenceException最常见现象代码在访问元素属性时抛NullReferenceException。原因DbElement.GetElement()返回了null——可能是无参调用、名称不含/、路径失效、元素不存在。它不会抛异常只会安静返回null。排查在所有GetElement调用后立刻判空并打印入参尤其是路径字符串确认路径含/确认上级元素没有被改名。3.2 按名称取不到元素现象路径肉眼看着没错GetElement返回null。原因名称缺/或上级元素被改名导致路径失效或大小写/前缀与实际不一致。排查改用先按路径取到稳定的上级再在其下按类型迭代筛出目标的两段式取法路径字符串一律从界面复制后再粘贴不要手打。3.3 UDET 类型取不到现象GetElementType对用户自定义元素类型返回空。原因UDET 的类型名需要带冒号。排查确认类型名写法里带上了冒号或者干脆改用官方推荐的做法——从DbElementTypeInstance里取现成的全局实例绕开名字解析。3.4 类型比较结果不符合预期现象两个看起来是同一类型的对象比较不相等。原因用了引用比较而DbElementType是值语义。排查确认比较的是类型值而不是对象引用不要缓存对象引用 某种类型的假设。3.5 数值单位错乱现象导出的报表里数值明显不是工程单位例如毫米被当成英寸。原因直接把double拼上单位符号输出绕过了单位的格式化机制。排查改用DbUnits/DbDoubleUnits/DbDimension/DbFormat这一组把数值转成字符串绝对不要手工拼单位。3.6 想用 SQL/ODBC 直连数据库现象试图用连接字符串直连 DABACON。原因方向错了。DABACON 是自有面向对象数据库不是关系库官方未提供 Dabacon ODBC/SQL 直连规范。排查回到官方路径——用Aveva.Core.Database.dll旧代是Pdms.Database.dll这类 .NET 接口取数或用应用层的 Reporting 能力第 14 篇会展开报道能力清单。SQL 这条路在官方资料里没有依据。四、动手练习三种重载各跑一次重点观察无参分别用无参、按名称、按 reftype 三种方式取元素把返回值是否为null打印出来。判定标准能给出三种重载的返回值现象并明确说出无参调用返回null而不抛异常这一行为以及由此必须做判空。用结构定位替代路径赌先按路径取到一个稳定的上级元素再在其下按类型找目标元素比较两种方式的成功率。判定标准故意把一个上级元素改名观察直接路径法失效返回null而结构定位法仍然有效。验证DbElementType的值语义同一个类型名取两次比较是否相等。判定标准能说明两次得到的是不同实例、但按值比较相等并解释为什么这套设计里看不到每种元素一个类。落一份 CSV 并检查转义把某类型的一批元素导出为 CSV打开检查字段对齐。判定标准含逗号的名称在 Excel 里不串列用记事本打开能看到该字段被双引号包裹。五、小结与下一篇预告本篇要记住的其实只有三段话DbElement是所有元素的统一句柄类型与属性是问它的两把钥匙GetElement()取不到就是null不是异常——判空是一等公民DbElementType每类型单类 值语义取类型优先用DbElementTypeInstance的现成实例UDET 的名字要带冒号。以及一条方向性的判断DABACON 不是关系库官方没给 SQL/ODBC 直连规范取数的正道是 .NET 接口与应用层 Reporting。下一篇12 PML.NET从用 C# 主动访问数据库切换到让 PML 主动调用 C#“。核心是PMLNetCallable这个标注——只有被标注的程序集、类与方法才会被加载与可见加上IMPORT装载、USING NAMESPACE的必须写完整命名空间”以及官方列出的三条限制。FAQQ1DbElement.GetElement()有三种重载无参调用返回什么为什么这一点必须专门记住三种重载是无参调用、按名称调用名称必须包含/、按 ref type 调用。其中无参调用返回null而不是抛异常。必须专门记住它是因为这个行为会让错误静默传播你以为自己取到了元素实际拿到的是null紧接着的属性访问才抛NullReferenceException——崩溃点看起来像属性读取有问题实际根因在几步之前的取元素环节。正确做法是每次取元素后立刻判空并在为空时输出明确的业务提示元素不存在路径错误。Q2按名称取元素时名称里为什么必须带/因为/是数据库层级路径的分隔符“按名称重载要的是元素的完整层级路径例如/ZONE-A/PIPE-1/BRAN-1001这样的形态而不是一个裸名称。因此这个取法天然脆弱上级元素一旦改名写死的路径就失效返回的又是安静的null。工程上更稳妥的做法是先按路径取到稳定的上级再在其下按类型迭代筛出目标”用结构定位而不是字符串赌。Q3数据模型的四个定义类分别是什么各自负责什么四个类是DbElementType、DbAttribute、DbElementTypeInstance、DbAttributeInstance。前两个属于定义层面DbElementType描述元素类型这是什么DbAttribute描述属性有什么字段后两个属于实例层面DbElementTypeInstance提供元素类型的现成实例DbAttributeInstance提供属性的现成实例。使用顺序上的结论是要判断类型、要取属性优先从…Instance里取现成的而不是自己去构造定义对象。Q4为什么说DbElementType对所有元素类型只有一个类值语义是什么意思官方明确DbElementType对所有元素类型只有一个类——不是每种元素类型对应一个 .NET 类所以你在代码里看不到一堆Branch、Equipment之类的类型类。既然都是同一个类就无法靠类型不同来区分于是它具有值语义比较两个DbElementType实例只要代表同一种元素类型就相等。实践含义是判断类型时要做值比较不能沿用引用比较的习惯。Q5取DbElementType有哪两种方式为什么 UDET 要带冒号官方推荐用哪一种两种方式按类型名静态GetElementType与按 32 位 hash 值。UDET用户自定义元素类型的名称需要带冒号才能被正确解析少一个冒号就取不到目标类型。官方推荐的用法是直接使用DbElementTypeInstance中的全局实例因为框架已经把这些实例准备好可以规避名字拼错、hash 算错、冒号漏掉这三类错误。Q6Filters/Iterators解决什么问题单位体系里的DbUnits、DbDoubleUnits、DbDimension、DbFormat各管什么Filters/Iterators解决按条件批量取元素的问题要点是把筛选条件下推到数据库侧而不是把全部元素取进内存再过滤。单位体系的四个类共同解决数值 单位 格式这一组问题读到的数值是带单位语义的双精度数输出时必须经由这一组类转成可读字符串不能手工拼单位符号模型用毫米、报告写成英寸是典型交付事故。Dabacon Tables那一组则说明 DABACON 是自有面向对象库其表是一层结构视图不能直接套关系库的 SQL 思维。