【问题标题】:How can I substitute a property during serialization with DataContractSerializer?如何在使用 DataContractSerializer 进行序列化期间替换属性?
【发布时间】:2009-08-19 14:14:19
【问题描述】:

我有一个可以正常工作的 ChangeTrackingList 实现,它可以很好地完成它的工作,但我想在将它从客户端发送回服务器时“过滤”它的内容,以便它只包含更改。获取更改很容易,因为我的列表为此目的公开了一个 GetChanges 方法。如何中断 DataContractSerializer 并替换 List 曾经所在的 List.GetChanges()?

更多细节: 考虑一个父/子关系,其中我有一个父级和多个子级,每个子级都有一个对父级的引用,例如客户/订单。将整个子列表序列化到客户端应用程序很好,因为我需要显示所有订单。当我保存时,我不想将所有订单都带回服务器,但是,只是更改。

难度: 我已经研究过实现 ISerializable 和实现我自己的 GetObjectData,如果不是因为我还需要保留对象引用这一事实,这不会很难。如果我将 DataContractSerializer 指向我的图表,并启用 PreserveObjectReferences(通过添加行为或显式通过构造函数),我将得到一个非常漂亮的图表,没有重复,但它需要包含我的整个 ChangeTrackingList。如果我实现 ISerializable,我可以手动写出我的 ChangeTrackingList,并且只包含更改,但那些子对象将不再知道它们的父引用。

说明: 这是一个高度简化的示例,旨在说明问题。我不是在寻找这个特定问题的替代解决方案。我的实际问题不以任何方式涉及客户、订单或订单项。客户/订单问题的替代解决方案不是我要寻找的答案。

很简单,我正在寻找一种仅序列化对象图“有趣”部分的方法。识别和过滤到“有趣”的机制已经完成,我只需要在序列化期间将这些部分替换到对象图中。

另一个例子: 假设我们有一个“Person”实体,它下面有一组“Phone”实体。电话没有父母是无效的,业务规则说一个人没有至少一部电话是无效的。我不能简单地保存这个人,然后在两个单独的电话中保存电话,因为每个电话都会出错。我必须在一次调用中将它们保存为图表。稍后,如果我更新 Person 以更改其存储在 Person 上的地址,并添加新的电话号码,我需要将更新后的人员以及对电话列表的更改发送。我不想发送原来的手机,因为它没有改变。

这又是一个人为的例子,但更接近于现实生活中的问题。我的父母至少没有一个孩子是无效的,没有父母的孩子是无效的。

更新: 看起来我想要执行的“替换”可以通过 DataContractSurrogate 类来完成。我见过一些这样的例子,它们相对简单,但那是因为它们的例子也是如此。它们通常属于“Swap EmployeeSurrogate for Employee”种类,其中“Employee”是一些不可序列化的类。就我而言,事情变得更奇怪了,因为我要交换的类是泛型类型。

所以,问题的一个更简单的版本可能是这样的。假设我有一个完全不可序列化的 MyList 类。 (在有人提出建议之前,替换 MyList 类也不是一个有效的解决方案。请记住,这只是一个示例。)我想设置一个 DataContractSurrogate ,以便每当 MyList 出现在我的对象图中,我想转换将其转换为一个简单的数组进行序列化。

这听起来对任何人来说都是一个有效的方向吗?有没有人尝试过代理泛型类型?考虑到它我是不是疯了?

【问题讨论】:

    标签: .net wcf serialization


    【解决方案1】:

    解决方案是认识到您有两个不同的图:原始对象图和变化图。这些不是同一类型。原始的将有一个客户类型的对象,但另一个将有一个 ChangedCustomer 类型的对象。 Customer 可能有一个 Orders 集合,但 ChangedCustomer 会有一个 ChangedOrders 集合等。

    您的 GetChanges 操作将返回 ChangedCustomer 列表,而不是 Customer 列表。

    【讨论】:

    • 我认为这属于创建一个完整的替代对象图的标题,我试图避免这种情况。我可以创建一些访问者来遍历图表并进行修剪,然后将新的根对象传递给服务。我只是想创建一个更加“放手”的解决方案,这样我们就没有那么多活动部件。我可以让 ChangeTrackingList 实现 ISerializable,并在自定义序列化期间进行修剪,如果不是参考保存问题,这将是解决方案。我有两个答案,但它们不适合。
    • 它们不适合,因为您试图使这变得比可能更简单。记住 - 尽可能简单,但仅此而已。
    • “两件”我指的是自定义序列化,在这种情况下,我可以准确地决定应该如何序列化 ChangeTrackingList,并轻松地只写出更改。第二个是DataContractSerializer,它知道如何保存对象引用以避免循环引用问题。例如,我可以从 DataContractSerializer 派生或重新实现,并以这种方式解决问题,但我真的希望有人知道将代码注入 DCS“管道”的方法。我认为这是一个更简单的解决方案。
    【解决方案2】:

    您以错误的方式处理此问题。您希望在将列表发送到客户端时具有一组序列化行为,并在将列表发送回服务器时具有另一组行为。

    您应该在客户端上有明确的代码,该代码将调用 GetChanges,然后将修剪后的列表发送回服务器。


    由于您似乎有一个根只更改了一小部分子项,并且您只想发回那些已更改的子项,因此您需要创建一个新类型,其中包含您要更改的子项列表, 而不是整个对象图。

    换句话说,您需要创建一个List<T>(或其他适当的容器)并将其序列化回服务器。问题是,你不会有完全再水化的对象,只有被改变的孩子。

    如果您确实需要完全再水化的对象,那么无论如何您都应该发回整个对象图。

    【讨论】:

    • 我可以明确发回更改,没问题。我的问题是只发回整个图表中有趣的部分。例如,用户在现有订单中添加了多个订单项,从而导致父级发生更改,并添加了几个新子级,并且可能会修改现有子级。这个保存需要是原子的,所以虽然我可以显式地发送父更改以及对子更改的更改,但我需要一次性完成所有操作。当然,这个例子是高度简化的。现实世界的问题有点……复杂。
    • @Mel:但是在对象图中不是所有改变的对象都相互连接吗?如果是这样,我看不到仅发送对象列表中图形中发生变化的对象的问题。如果您正在谈论尝试仅发回父母和/或孩子,那就是另一回事了,您将需要一个新的容器来将该信息发回。
    • 是的,孩子们连接到他们的父母,但并不是所有的孩子都改变了。在这种情况下,我正在保存父级,并且只想序列化已更改的子级。我将扩展问题以包含另一个示例。
    【解决方案3】:

    我一直在努力解决类似的问题。我认为@casperOne 是在正确的轨道上。从服务器到客户端的通信应该包含一大块完整的对象图。但是应该使用交换有限数据子集的小型辅助方法来更改完整的对象图。

    例如,您将使用 ReadFullCustomer() 操作获取完整的客户/订单图,然后使用 AddOrder() 方法回调服务器,仅传递该任务所需的订单信息。让您的服务器的域逻辑处理有关保持父记录与其子记录一致的问题。

    Rory Primrose 有一篇关于 WCF service contract design 的文章讨论了厚实与健谈的界面。在您的情况下,听起来您需要将两者结合起来:粗略的读取操作和繁琐的修改操作。


    编辑:我不确定我是否完全掌握了您的情况,@Mel。听起来您想在序列化期间更改对象图。我知道这样做的唯一方法是让图形中的类实现IXmlSerializable 并滚动您自己的WriteXmlReadXml 方法。 DataContractSerializer 理解实现IXmlSerializable 的类,因此值得一试。

    【讨论】:

    • 我同意,而且在大多数情况下,这就是我所做的。正如我所指出的,客户/订单示例既简单又做作。我确实有一个真实世界的情况,我确实需要发送一个图表,其中只有 5% 会发生变化,我正在尝试为这个问题创建通用解决方案,这样我就没有稍后再解决。我也可以“遍历”树并制作仅包含更改的副本,但我更愿意通过序列化来解决它,以便它适用于任何地方。
    【解决方案4】:

    好的,我终于有了一些有用的东西,并想分享答案。不幸的是,我不能简单地分享代码,因为它是在客户的时间编写的。

    解决方案的关键是 DataContractSurrogate 类,您可以阅读有关 herehere 的信息。通常,您会使用它来代替对象图中的不可序列化类。

    假设我们有一个不可序列化的 MyList 类。我们需要创建一个代理类来充当它的“替身”。这里的命名约定非常糟糕,因为名为 MyListSurrogate 的命名约定实际上更像是一个工厂,而名为 MyListSurrogated 的命名约定是我认为的实际代理。无论如何,“代理”类公开了一个普通数组或列表,并被标记为 DataContract。这是将通过电线的课程。 MyListSurrogate 类实现了 IDataContractSurrogate,并实现了四个重要的方法。

    GetDataContractType 方法返回给定实体类型的替代类型。当给定类型 MyList 时,它应该返回 MyListSurrogated。任何其他类型都应该只返回原始类型。这种方法涉及到一些混乱的反射,所以我将包含它的代码,而不是仅仅解释它。

    public Type GetDataContractType(Type type)
    {
        if(type.IsGenericType 
            && (type.GetGenericTypeDefinition() == typeof(MyList<>)))
        {
            var itemType = type.GetGenericArguments()[0];
            var result = typeof(MyListSurrogated<>)
                .GetGenericTypeDefinition().MakeGenericType(itemType);
            return result;
        }
        return type;
    }
    

    以类似的方式,GetObjectToSerialize 将 MyList instance 转换为 MyListSurrogated 实例。 GetDeserializedObject 将 MyListSurrogated 实例转换回 MyList 实例。 IDataContractSurrogate 包括其他几个我们不会使用的方法,我刚刚为它们中的大多数返回了 null。

    然后可以将 MyListSurrogate 类的一个实例传递给 DataContractSerializer 构造函数,它会在序列化和反序列化过程中被调用以根据需要动态替换类型。不幸的是,没有简单的方法可以通过配置指定代理类,因此如果您不实例化自己的 DataContractSerializer,则必须实现 DataContractSerializerOperationBehavior,您可以阅读有关 here 的信息。应用操作行为类似于here描述的方法。

    要使用代理,您必须处理一些已知的类型问题。在我的示例中,必须将 MyListSurrogated 类型添加到您尝试调用的服务的已知类型列表中。您可以手动执行此操作,也可以在我的情况下作为代码生成模板的一部分。

    最后一件事。我最初的目标是创建一个 ChangeTrackingList,它将其全部内容从服务器传输到客户端,但仅从客户端到服务器的更改。一旦代理被映射,这实际上非常简单。我的 ChangeTrackingList 有一个名为“IncludeOriginalsWhenSerializing”的布尔属性,默认为 true。代理类的 GetObjectToSerialize 方法查看此标志以决定是否将原始项复制到替代项中。在该行的另一端,GetDeserializedObject 方法重新创建原始的 ChangeTrackingList,然后在反序列化期间将标志设置为 false。因此,将标志设置为 true 的列表进入一端,并在标志设置为 false 的另一端出现。很简单,它是自动的,它已经完成了。

    【讨论】:

    • 一些用户评论说这是“错误的方式”,正确的行为是实现两个完全独立的操作,一个从服务器到客户端,另一个从客户端到服务器的过程完全不同。是的,这是一种更合适的方法。这个特定类 (ChangeTrackingList) 的目标是专门避免这种情况,并创建一个易于使用的类,该类会根据序列化的方向自​​动修剪自身。目标是这个练习的“自动”部分。它执行一次而不是 N*2 次。
    猜你喜欢
    • 2010-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多