问题描述
我有以下设置:C#、ServiceStack、MariaDB、带有对象和结构的 POCO、JSON。
主要问题是:如何使用 ServiceStack 将 POCO 存储到具有复杂类型(对象和结构)作为 JSON 的 MariaDB 并且仍然可以对相同的 POCO 进行反/序列化?所有这些单个任务都受支持,但我在将所有任务放在一起时遇到了问题,主要是因为结构。
... 终于在写这篇文章的过程中找到了一些解决方案,看起来好像我回答了我自己的问题,但我仍然想知道更熟练的人的答案,因为我找到的解决方案有点复杂,我认为。详细信息和两个子问题在上下文中稍后出现。
很抱歉由于我的知识有限而造成的长度和可能的错误信息。
简单的例子。这是我结束的最后一个工作。一开始没有 SomeStruct.ToString()/Parse()
方法,也没有 JsConfig
设置。
using Newtonsoft.Json;
using ServiceStack;
using ServiceStack.DataAnnotations;
using ServiceStack.Ormlite;
using ServiceStack.Text;
using System.Diagnostics;
namespace Test
{
public class MainObject
{
public int Id { get; set; }
public string StringProp { get; set; }
public SomeObject ObjectProp { get; set; }
public SomeStruct StructProp { get; set; }
}
public class SomeObject
{
public string StringProp { get; set; }
}
public struct SomeStruct
{
public string StringProp { get; set; }
public override string ToString()
{
// Unable to use .ToJson() here (ServiceStack does not serialize structs).
// Unable to use ServiceStack's JSON.stringify here because it just takes ToString() => stack overflow.
// => Therefore Newtonsoft.Json used.
var serializedStruct = JsonConvert.SerializeObject(this);
return serializedStruct;
}
public static SomeStruct Parse(string json)
{
// This method behaves differently for just deserialization or when part of Save().
// Details in the text.
// After playing with different options of altering the json input I ended with just taking what comes.
// After all it is not necessary,but maybe useful in other situations.
var structItem = JsonConvert.DeserializeObject<SomeStruct>(json);
return structItem;
}
}
internal class ServiceStackMariaDbStructTest
{
private readonly MainObject _mainObject = new MainObject
{
ObjectProp = new SomeObject { StringProp = "SomeObject's String" },StringProp = "MainObject's String",StructProp = new SomeStruct { StringProp = "SomeStruct's String" }
};
public ServiceStackMariaDbStructtest()
{
// This one line is needed to store complex types as blobbed JSON in MariaDB.
MysqLDialect.Provider.StringSerializer = new JsonStringSerializer();
JsConfig<SomeStruct>.RawSerializefn = someStruct => JsonConvert.SerializeObject(someStruct);
JsConfig<SomeStruct>.RawDeserializefn = json => JsonConvert.DeserializeObject<SomeStruct>(json);
}
public void Test_Serialization()
{
try
{
var json = _mainObject.ToJson();
if (!string.IsNullOrEmpty(json))
{
var objBack = json.FromJson<MainObject>();
}
}
catch (System.Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
public void Test_Save()
{
var cs = "ConnectionStringToMariaDB";
var dbf = new OrmliteConnectionFactory(cs,MysqLDialect.Provider);
using var db = dbf.OpenDbConnection();
db.DropAndCreateTable<MainObject>();
try
{
db.Save(_mainObject);
var dbObject = db.SingleById<MainObject>(_mainObject.Id);
}
catch (System.Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
}
什么(我认为)我知道/已经尝试过但起初并没有帮助自己解决它:
- ServiceStack 在 DB 中默认将复杂类型存储为 blobed JSV(第一部分的最后一段:https://github.com/ServiceStack/ServiceStack.OrmLite),因此有必要按照建议的方式设置它:
MysqLDialect.Provider.StringSerializer = new JsonStringSerializer();
({{3 }})
=> 默认 JSV 更改为 JSON。 - ServiceStack 的序列化不适用于结构体,必须以特殊方式对待它们:
-
a) 根据 https://github.com/ServiceStack/ServiceStack.OrmLite#pluggable-complex-type-serializers 和示例 https://github.com/ServiceStack/ServiceStack.Text#c-structs-and-value-types,有必要实现
TStruct.ToString()
和static TStruct.ParseJson()/ParseJsv()
方法。b) 根据 https://github.com/ServiceStack/ServiceStack.Text/#using-structs-to-customize-json 和单元测试 https://github.com/ServiceStack/ServiceStack.Text/#typeserializer-details-jsv-format,它应该是
TStruct.ToString()
(与 a 中相同)和static TStruct.Parse()
。子问题 #1:哪一个是正确的?对我来说,
ParseJson()
从来没有被调用过,Parse()
是。文档问题还是用于其他情况?我实施了选项 b)。结果:
-
IDbConnection.Save(_mainObject)
将项目保存到 MariaDB。成功。
通过保存过程ToString()
和Parse()
被调用。在 Parse 中,传入的 JSON 看起来是这样的:"{\"StringProp\":\"SomeStruct's String\"}"
。很好。 - 序列化有效。成功。
-
反序列化失败。我不知道原因,但传入 Parse() 的 JSON 被“双重转义”:
"{\\\"StringProp\\\":\\\"SomeStruct's String\\\"}"
子问题 #2:为什么 Parse 中的反序列化“双重转义”?
-
-
我尝试使用 JsConfig(和 Newtonsoft.Json 来获得正确的 JSON)来解决结构:
JsConfig<SomeStruct>.Serializefn = someStruct => JsonConvert.SerializeObject(someStruct); JsConfig<SomeStruct>.DeSerializefn = json => JsonConvert.DeserializeObject<SomeStruct>(json);
a) 起初没有在 TStruct 中定义 ToString() 和 Parse()。 结果:
-
保存失败:保存期间使用的
json
中的JsonConvert.DeserializeObject(json)
输入只是类型名称"WinAmbPrototype.someStruct"
。 - 反/序列化成功。
b) 然后我也使用 Newtonsoft.Json 实现了 ToString()。 在保存期间使用
ToString()
而不是JsConfig.Serializefn
,即使 JsConfig.Serializefn 仍然设置(也许根据设计,我不判断)。结果: -
保存失败:保存期间使用的
-
然后(在写这篇文章时我仍然没有任何解决方案)我发现 JsConfig.Raw* "overrides" 并尝试了它们:
JsConfig<SomeStruct>.RawSerializefn = someStruct => JsonConvert.SerializeObject(someStruct); JsConfig<SomeStruct>.RawDeserializefn = json => JsonConvert.DeserializeObject<SomeStruct>(json);
a) 最初没有在 TStruct 中定义 ToString() 和 Parse()。 结果与 2a 中的相同。
b) 然后我实现了 ToString()。 结果:
-
两者都有效。此任务不需要
Parse()
方法。
但这是非常脆弱的设置:
-
两者都有效。此任务不需要
-
有没有更简单的解决方案?如果有人为我指明更好的方向,我会很高兴。
可以接受的可能是两个(由于情况不同,两者都可以访问):
- 如果我是 TStruct 所有者:仅使用纯
TStruct.ToString()
和static TStruct.Parse()
来支持 ServiceStack 开箱即用的反序列化和 DB(在Parse()
中没有不同的输入)。 - 如果我是 TStruct 的使用者,没有实现 JSON 支持并且我无法访问它的代码:直到现在我没有找到方法,如果 ToString 没有实现:保存到 DB 不起作用。也许可以确保 JsConfig 序列化函数足以用于反/序列化以及在保存到数据库期间使用时。
最好的方法是不使用其他依赖项(例如 Newtonsoft.Json)来序列化结构。也许一些JsConfig.ShallProcessstructs = true;
(警告:只是一个提示,从 2021 年 4 月 2 日起不起作用)在这种情况下会很好。
解决方法
ServiceStack 将结构视为单一标量值类型,就像大多数核心 BCL 值类型(例如 TimeSpan
、DateTime
等)一样。重载 Parse()
和 ToString()
方法以及 Struct 的 Constructor
可让您控制自定义结构的序列化/反序列化。
文档已更正。结构使用 Parse
而类使用 ParseJson/ParseJsv
如果您想序列化模型属性,我建议您改用 class
,因为您正在寻找的行为是 POCO DTO 的行为。
如果您想在 RDBMS 中将结构序列化为 DTO,您可以尝试的替代方法是仅使用 JSON.NET 进行复杂类型序列化,例如:
public class JsonNetStringSerializer : IStringSerializer
{
public To DeserializeFromString<To>(string serializedText) =>
JsonConvert.DeserializeObject<To>(serializedText);
public object DeserializeFromString(string serializedText,Type type) =>
JsonConvert.DeserializeObject(serializedText,type);
public string SerializeToString<TFrom>(TFrom from) =>
JsonConvert.SerializeObject(from);
}
MySqlDialect.Provider.StringSerializer = new JsonNetStringSerializer();