如何使用 ServiceStack 将 POCO 存储到具有复杂类型(对象和结构)的 MariaDB,如 JSON?
How to use ServiceStack to store POCOs to MariaDB having complex types (objects and structs) blobbed as JSON?
我有以下设置:C#、ServiceStack、MariaDB、带有对象和结构的 POCO,JSON。
主要问题是:如何使用 ServiceStack 将 POCO 存储到具有复杂类型(对象和结构)的 MariaDB,如 JSON 并且仍然有效 de/serialization 相同的 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 默认将复杂类型存储为 blobbed JSV(第一部分的最后一段:https://github.com/ServiceStack/ServiceStack.OrmLite), so it is necessary to set it the way it is proposed:
MySqlDialect.Provider.StringSerializer = new JsonStringSerializer();
(https://github.com/ServiceStack/ServiceStack.OrmLite#pluggable-complex-type-serializers)
=> 默认 JSV 更改为 JSON.
- ServiceStack 的序列化不适用于结构,有必要以特殊方式对待它们:
a) 根据https://github.com/ServiceStack/ServiceStack.Text#c-structs-and-value-types and example https://github.com/ServiceStack/ServiceStack.Text/#using-structs-to-customize-json,有必要实施TStruct.ToString()
和static TStruct.ParseJson()/ParseJsv()
方法。
b) 根据 https://github.com/ServiceStack/ServiceStack.Text/#typeserializer-details-jsv-format and unit tests https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/CustomStructTests.cs 应为 TStruct.ToString()
(与 a 相同)和 static TStruct.Parse()
.
子问题 #1:哪一个是正确的?对我来说,ParseJson()
从未被调用过,Parse()
被调用过。文档问题还是用于其他情况?
我实施了选项 b)。结果:
IDbConnection.Save(_mainObject)
将项目保存到 MariaDB。成功。
通过保存过程 ToString()
和 Parse()
被调用。在 Parse 中,传入的 JSON 看起来是这样的:
"{\"StringProp\":\"SomeStruct's String\"}"
。很好
- 序列化有效。成功。
- 反序列化失败。我不知道原因,但是JSON传入Parse()是“双重转义”:
"{\\"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()。 结果:
- 保存失败:保存期间使用的
JsonConvert.DeserializeObject(json)
中的 json
输入只是类型名称 "WinAmbPrototype.SomeStruct"
.
- De/serialization 成功了。
b) 然后我也使用 Newtonsoft.Json 实现了 ToString()。 在保存期间使用 ToString()
而不是 JsConfig.SerializeFn
甚至 JsConfig.SerializeFn 仍然设置(也许是设计的,我不判断)。结果:
- 保存失败:但是在保存期间调用的 DeserializeFn 的
json
输入改变了,现在它是 JSV-like "{StringProp:SomeStruct's String}"
,但仍然不能反序列化为JSON.
- De/serialization 成功了。
然后(在写这篇文章的过程中我仍然没有任何解决方案)我发现 JsConfig.Raw* “覆盖”并尝试了它们:
JsConfig<SomeStruct>.RawSerializeFn = someStruct => JsonConvert.SerializeObject(someStruct);
JsConfig<SomeStruct>.RawDeserializeFn = json => JsonConvert.DeserializeObject<SomeStruct>(json);
a) 起初没有在 TStruct 中定义 ToString() 和 Parse()。 结果与 2a 相同。
b) 然后我实现了 ToString()。 结果:
- 两者都有效。此任务不需要
Parse()
方法。
但它的设置非常脆弱:
- 如果我删除了
ToString()
,它会失败(现在我明白了为什么,默认 ToString 生成 JSON,只在 2a、3a 中键入名称)。
- 如果我删除了
RawSerializeFn
设置,它会在 RawDeserializeFn
中失败(“双重转义” JSON)。
有没有更简单的解决办法?如果有人指出我更好的方向,我会很高兴。
可以接受的可能是两个(由于不同的情况,它们都可以访问):
- 如果我是 TStruct 所有者:仅使用纯
TStruct.ToString()
和 static TStruct.Parse()
支持开箱即用的 de/serialization 和 ServiceStack 的数据库([=22= 中没有不同的输入]).
- 如果我是 TStruct 的使用者,没有实现 JSON 支持并且我无法访问它的代码:直到现在我还没有找到方法,如果没有实现 ToString:保存到 DB不行。也许可以确保 JsConfig 序列化函数足以满足 de/serialization 以及在保存到 DB 期间使用时的需求。
最好的方法是不使用其他依赖项(例如 Newtonsoft.Json)来序列化结构。也许一些 JsConfig.ShallProcessStructs = true;
(警告:只是提示,从 2021-04-02 开始不起作用)在这种情况下会很好。
ServiceStack 将结构视为 单个标量值 类型,就像大多数核心 BCL 值类型(例如 TimeSpan
、DateTime
等) .重载 Parse()
和 ToString()
方法以及 Struct 的 Constructor
让您可以控制自定义结构的 serialization/deserialization。
Docs have been corrected. Structs use Parse
whilst classes use 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();
我有以下设置:C#、ServiceStack、MariaDB、带有对象和结构的 POCO,JSON。
主要问题是:如何使用 ServiceStack 将 POCO 存储到具有复杂类型(对象和结构)的 MariaDB,如 JSON 并且仍然有效 de/serialization 相同的 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 默认将复杂类型存储为 blobbed JSV(第一部分的最后一段:https://github.com/ServiceStack/ServiceStack.OrmLite), so it is necessary to set it the way it is proposed:
MySqlDialect.Provider.StringSerializer = new JsonStringSerializer();
(https://github.com/ServiceStack/ServiceStack.OrmLite#pluggable-complex-type-serializers)
=> 默认 JSV 更改为 JSON. - ServiceStack 的序列化不适用于结构,有必要以特殊方式对待它们:
a) 根据https://github.com/ServiceStack/ServiceStack.Text#c-structs-and-value-types and example https://github.com/ServiceStack/ServiceStack.Text/#using-structs-to-customize-json,有必要实施
TStruct.ToString()
和static TStruct.ParseJson()/ParseJsv()
方法。b) 根据 https://github.com/ServiceStack/ServiceStack.Text/#typeserializer-details-jsv-format and unit tests https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/CustomStructTests.cs 应为
TStruct.ToString()
(与 a 相同)和static TStruct.Parse()
.子问题 #1:哪一个是正确的?对我来说,
ParseJson()
从未被调用过,Parse()
被调用过。文档问题还是用于其他情况?我实施了选项 b)。结果:
IDbConnection.Save(_mainObject)
将项目保存到 MariaDB。成功。
通过保存过程ToString()
和Parse()
被调用。在 Parse 中,传入的 JSON 看起来是这样的:
"{\"StringProp\":\"SomeStruct's String\"}"
。很好- 序列化有效。成功。
- 反序列化失败。我不知道原因,但是JSON传入Parse()是“双重转义”:
"{\\"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()。 结果:
- 保存失败:保存期间使用的
JsonConvert.DeserializeObject(json)
中的json
输入只是类型名称"WinAmbPrototype.SomeStruct"
. - De/serialization 成功了。
b) 然后我也使用 Newtonsoft.Json 实现了 ToString()。 在保存期间使用
ToString()
而不是JsConfig.SerializeFn
甚至 JsConfig.SerializeFn 仍然设置(也许是设计的,我不判断)。结果:- 保存失败:但是在保存期间调用的 DeserializeFn 的
json
输入改变了,现在它是 JSV-like"{StringProp:SomeStruct's String}"
,但仍然不能反序列化为JSON. - De/serialization 成功了。
- 保存失败:保存期间使用的
然后(在写这篇文章的过程中我仍然没有任何解决方案)我发现 JsConfig.Raw* “覆盖”并尝试了它们:
JsConfig<SomeStruct>.RawSerializeFn = someStruct => JsonConvert.SerializeObject(someStruct); JsConfig<SomeStruct>.RawDeserializeFn = json => JsonConvert.DeserializeObject<SomeStruct>(json);
a) 起初没有在 TStruct 中定义 ToString() 和 Parse()。 结果与 2a 相同。
b) 然后我实现了 ToString()。 结果:
- 两者都有效。此任务不需要
Parse()
方法。
但它的设置非常脆弱:
- 如果我删除了
ToString()
,它会失败(现在我明白了为什么,默认 ToString 生成 JSON,只在 2a、3a 中键入名称)。 - 如果我删除了
RawSerializeFn
设置,它会在RawDeserializeFn
中失败(“双重转义” JSON)。
- 两者都有效。此任务不需要
有没有更简单的解决办法?如果有人指出我更好的方向,我会很高兴。
可以接受的可能是两个(由于不同的情况,它们都可以访问):
- 如果我是 TStruct 所有者:仅使用纯
TStruct.ToString()
和static TStruct.Parse()
支持开箱即用的 de/serialization 和 ServiceStack 的数据库([=22= 中没有不同的输入]). - 如果我是 TStruct 的使用者,没有实现 JSON 支持并且我无法访问它的代码:直到现在我还没有找到方法,如果没有实现 ToString:保存到 DB不行。也许可以确保 JsConfig 序列化函数足以满足 de/serialization 以及在保存到 DB 期间使用时的需求。
最好的方法是不使用其他依赖项(例如 Newtonsoft.Json)来序列化结构。也许一些 JsConfig.ShallProcessStructs = true;
(警告:只是提示,从 2021-04-02 开始不起作用)在这种情况下会很好。
ServiceStack 将结构视为 单个标量值 类型,就像大多数核心 BCL 值类型(例如 TimeSpan
、DateTime
等) .重载 Parse()
和 ToString()
方法以及 Struct 的 Constructor
让您可以控制自定义结构的 serialization/deserialization。
Docs have been corrected. Structs use
Parse
whilst classes useParseJson/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();