如何在使用DataContractSerializer进行序列化期间替换属性?

时间:2009-08-19 14:14:20

标签: .net wcf serialization

我有一个正常工作的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出现在我的对象图中时,我想转换它是一个简单的序列化数组。

这对任何人来说听起来都是一个有效的方向吗?有没有人试图代替泛型?即使考虑它我也疯了吗?

4 个答案:

答案 0 :(得分:0)

你正以错误的方式接近这一点。您希望在将列表发送到客户端时具有一组序列化行为,并在将其发送回服务器时具有另一组行为。

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


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

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

如果确实需要完全重新水化的物体,那么无论如何都应该发回整个物体图。

答案 1 :(得分:0)

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

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

Rory Primrose有一篇关于WCF service contract design的文章,讨论了chunky和chatty接口。在你的情况下,听起来你需要两者的结合:一个粗略的读操作与繁琐的修改操作。


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

答案 2 :(得分:0)

解决方案是识别您有两个不同的图形:原始对象的图形和变化图形。这些不是同一类型。原始文件将具有Customer类型的对象,但另一个将具有ChangedCustomer类型的对象。客户可能有一组订单,但ChangedCustomer会有一组ChangedOrders等。

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

答案 3 :(得分:0)

好的,最后我有一些有用的东西,想分享答案。不幸的是,我不能简单地共享代码,因为它是在客户端编写的。

解决方案的关键是DataContractSurrogate类,您可以阅读有关herehere的内容。通常,您可以使用它为对象图中的不可序列化的类提供支持。

假设我们有一个不可序列化的MyList类。我们需要创建一个代理类来充当它的“替身”。这里的命名约定非常可怕,因为名为MyListSurrogate的名称实际上更像是一个工厂,而名为MyListSurrogated的名称就是我认为的实际代理。无论如何,“代理”类暴露了一个普通数组或List,并被标记为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 实例转换为MyListSurrogated实例。 GetDeserializedObject将MyListSurrogated实例转换回MyList实例。 IDataContractSurrogate包含了我们不会使用的其他几种方法,我刚刚为大多数方法返回了null。

然后可以将MyListSurrogate类的实例传递给DataContractSerializer构造函数,并在序列化和反序列化期间调用它以根据需要替换类型。遗憾的是,没有简单的方法可以通过配置来指定代理类,因此如果您没有实例化自己的DataContractSerializer,则必须实现DataContractSerializerOperationBehavior,您可以阅读here。应用操作行为类似于描述的方法here

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

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