最近为了解决循环依赖关系,我们需要将枚举类移动到不同命名空间下的不同项目中。有一些参与者和有状态服务将此枚举值的实例保持在其可靠状态。
枚举类是这样的:
namespace com.libA
{
public enum Foo
{
None = 0,
Foo1 = 1,
Foo2 = 2,
}
}
我们想将其转移到另一个名称空间为com.libB
的项目中。这些枚举值存储在actor和有状态服务中的可靠状态中,并且如下所示获取:
Foo foo = await this.StateManager.GetStateAsync<Foo>("FooKey").ConfigureAwait(false);
存储Foo
的值的actor服务之一是非常长寿命的actor。理论上,它可以在快乐的道路上活到无穷大,如果删除永远不会从外部调用的话。我们尝试了简单的重构>在我们的非生产环境中移动并尝试。这开始在我们的非生产环境中导致SerializationException
。错误消息显示:Expecting element 'Foo' from namespace 'http://schemas.datacontract.org/2004/07/com.libB'.. Encountered 'Element' with name 'Foo', namespace 'http://schemas.datacontract.org/2004/07/com.libA'.
这些异常发生在获取旧参与者的Foo
值之前。
我的问题是:
- 如何将
Foo
移动到命名空间com.libB
?两阶段升级对这里有帮助吗 - 是否有可能在没有数据丢失/损坏的情况下做到这一点
您可以将带有命名空间的DataContract
属性添加到类型中。由于您已经在生产环境中运行,因此可以使用错误中的命名空间来解决问题。
示例:
[DataContract(Name = "Foo", Namespace = "http://schemas.datacontract.org/2004/07/com.libA")]
public enum Foo
{
// ...
}
更好的方法可能是制定升级计划。
- 让这两种类型共存
- 检索状态时,请使用try机制,使用旧类型检索,如果由于序列化异常而失败,请使用新类型尝试
- 当持久化状态时,当它是旧类型时,将其转换为新命名空间中的新类型。(添加一些日志记录,以便验证是否发生了转换(
- 部署到测试,看看它是否有效,如果可以,部署到生产
- 删除旧类型,删除转换代码
- 部署到测试,看看它是否工作,如果可以,部署到生产
一个选项是为所有使用Foo
的类型创建一个封装DataContractSerializer
的自定义序列化程序,在反序列化过程中修复/忽略命名空间。
IReliableStateManager.TryAddStateSerializer用于注册给定类型T的自定义序列化程序。此注册应该在StatefulServiceBase的构建中发生,以确保在恢复开始之前,所有可靠集合都可以访问相关的序列化程序读取其持久化的数据。
- 在
IStateSerializer<OrderKey>.Read(BinaryReader reader)
中,将序列化的数据读取为XML - 根据需要更改XML命名空间
- 将XML提供给
DataContractSerializer
以创建对象 - 返回对象