使用 Linq to XML 创建基于类/模型的现有对象 - 工作,但有更好的方法吗?
Posted
技术标签:
【中文标题】使用 Linq to XML 创建基于类/模型的现有对象 - 工作,但有更好的方法吗?【英文标题】:Using Linq to XML to create existing object based on class/model - working, but is there a better way? 【发布时间】:2015-02-17 04:04:31 【问题描述】:我使用的网络服务(我无法控制它)返回一个 XML 字符串,我将其转换为 XDcoument,然后创建一个特定类型的对象列表:
private static List<ProductDetail> productList(XmlDocument _xDoc)
XDocument xdoc = XDocument.Load(new XmlNodeReader(_xDoc));
var pList = from p in xdoc.Root.Elements("DataRow")
select new ProductDetail
Product = (string)p.Element("Product"),
ProductDesc = (string)p.Element("ProductDesc"),
ExtraKey = (string)p.Element("ExtraKey"),
SalesGroup = (string)p.Element("SalesGroup"),
Donation = (string)p.Element("Donation"),
Subscription = (string)p.Element("Subscription"),
StockItem = (string)p.Element("StockItem"),
MinimumQuantity = (string)p.Element("MinimumQuantity"),
MaximumQuantity = (string)p.Element("MaximumQuantity"),
ProductVATCategory = (string)p.Element("ProductVATCategory"),
DespatchMethod = (string)p.Element("DespatchMethod"),
SalesDescription = (string)p.Element("SalesDescription"),
HistoryOnly = (string)p.Element("HistoryOnly"),
Warehouse = (string)p.Element("Warehouse"),
LastStockCount = (string)p.Element("LastStockCount"),
UsesProductNumbers = (string)p.Element("UsesProductNumbers"),
SalesQuantity = (string)p.Element("SalesQuantity"),
AmendedBy = (string)p.Element("AmendedBy")
;
return pList.ToList();
这很好用而且速度非常快。但是,这意味着如果代码发生更改,我必须将其与模型分开维护,我只是想知道是否有捷径可以避免我必须像我正在做的那样指定每个单独的字段?我已经有一个 ProductDetail 类,那么有什么方法可以在对象级别使用它吗?我感觉答案可能是“是的,但是使用反射”这可能会对处理速度产生负面影响,所以我不会热衷于此。
【问题讨论】:
【参考方案1】:我能想到另一种选择(除了您在问题中已经谈到的两个之外。即手动映射和基于反射的方法。
动态方法
是DynamicMethod
(MSDN 链接也有example)
这种方法可以让你两全其美......即
性能 动态但问题是,你可以用它来交换
代码复杂度增加 调试能力降低。它可以被认为是两者的混合体,从某种意义上说,它可以像您希望的那样灵活/动态(工作量也会相应变化),但性能优势类似于手动映射对象(上面的示例代码)。
使用这种方法,在适当的时间(应用程序启动/首次使用等)初始化您的 DynamicMethod
需要一次性成本。然后将其缓存以供后续使用。如果您只需要几次映射器..那么它的用处可能会小得多..但我假设您的情况并非如此。
我推荐的技术
您会从示例中注意到,创建 DynamicMethod
涉及在运行时发出 IL 操作码(以及反射),这看起来非常复杂和困难,因为 它是更复杂的代码,更难调试。但是,在这种情况下,我倾向于手动编写我想使用DynamicMethod
发出的方法(你已经有了),然后研究编译器为该方法生成的 IL。
换句话说,您不必成为手工编写 IL 的大师。如果您还不熟悉如何为给定场景编写 IL,只需按照您的想象用纯 C# 编写它。 . 编译它,然后使用 ILDASM 确定您希望如何为您的DynamicMethod
发出相似 IL。
其他选项 - 反序列化器
为了完整起见,我想说您要解决的问题通常是将 XML 有效负载反序列化为普通对象 POCO。如果您甚至考虑过它们,并将它们排除在一个选项中,或者甚至没有考虑过,问题就不是很清楚了。
如果您甚至没有朝那个方向看,您可以开始使用 .Net 中已有的内容 - DataContractSerializer
。您可能可以在 Internet 上找到其他实现。这可能适合您的需求。
它们可能不适合您的场景(如果我理解正确的话)的原因可能是 -
反序列化程序在功能上往往是通用的,因此性能肯定与您上面的代码不同。可能有一个反序列化器使用DynamicMethod
来提高性能,但我从未见过。另请注意,不同的实现显然可以具有不同的性能特征
如果您拥有的 XML 嵌套很深,并且您希望在不同级别映射属性/元素而不创建复杂的对象层次结构。 (现在有人可能会争辩说,这些问题可以通过 XSL 转换来解决。)
实施可能没有您真正需要的功能。例如,如果需要将数据映射到的对象是通用类型。
结论
手动映射最快,但最不灵活。
反射肯定会很慢,但可以提供更高的灵活性。
如果您愿意为更高的复杂性和开发工作付出代价,DynamicMethod
(System.Reflection.Emit
命名空间的一部分)可以为您提供最大的灵活性和性能(假设缓存实例的使用率很高)。
反序列化器为您提供不同程度的灵活性和性能,但并不总是合适的。
编辑:意识到,为了完整起见,可以将更多选项添加到列表中。
T4 templates - 也很难开发(恕我直言)和调试/故障排除(取决于您尝试使用它们实现的复杂性。我必须通过安装 VS 插件来调试它们,并通过附加一个来调试Visual Studio 实例作为 另一个 的调试器 Visual Studio 实例作为调试器 - 丑陋。可能有更好的方法,但我会避免使用它们)。可能仍需要手动操作来刷新生成的代码。 (很有可能它可以在构建过程中自动化,但我从未尝试过)
Code generation - 您编写自定义代码生成工具,并将其作为适当项目的预构建步骤挂钩。 (您可以将此代码生成作为构建的一部分进行,或者理论上,也可以在您的应用程序部署并首次运行之后进行,但对于这种情况,构建时生成应该更合适。)
【讨论】:
我开始对得到这个答案感到绝望,谢谢!代码的早期版本在每个返回的节点上使用了一个XMLSerializer.Deserialize
循环,并且由于返回了 240 个节点,所以它很慢。 7-10 秒,而我上面的代码是同一任务的几分之一秒。最终将返回多达 10 种不同的对象类型,因此我需要仔细研究动态方法,看看是否有帮助。我是否认为我需要为每个要反序列化的单独 XML 类型/模型使用动态方法?
同意.. 正如我已经说过的,序列化器可能非常慢,这就是为什么一些开发人员喜欢炫耀他们的自定义 OSS 实现的原因 :) (开个玩笑,其中一些确实比构建的更好在 .Net 中)。对于您的后一部分,如果您可以概括映射规则,那么您应该可以创建映射器的一个实现,并使用尽可能多的动态方法对其进行初始化......想想你将如何使用反射来做到这一点,并保持其通用性。只有这样,您才能在运行时混合使用发射动态方法,而不是使用纯反射。
示例(我只是编造这个 - 只是为了说明这一点) - MapperFactory.Initialize(List<Type> mappedTypes))
IMapper<T> MapperFactory.GetMapper()
T IMapper<T>.Map(string xml)
。所有的魔法都在MapperFactory.Initialize
中,它将使用反射来遍历每种类型,并创建和缓存DynamicMethod
以将xml 映射到该类型的实例。然后可以将此 Dynamic 方法包装在一个接口中,并作为给定类型的映射器返回,根据需要多次返回。
我不知道我是如何忘记在“其他选项”中提及更多选项的。曾经考虑过它们,但后来又错过了它们(实际上我在这个长答案中丢失了一些编辑,因为浏览器崩溃..打破了我的思路)..很抱歉..请检查更新的答案,以防其他两个选项也对您感兴趣..以上是关于使用 Linq to XML 创建基于类/模型的现有对象 - 工作,但有更好的方法吗?的主要内容,如果未能解决你的问题,请参考以下文章