我有以下设置:C#、ServiceStack、MariaDB、带有对象和结构的 POCO、JSON。
主要问题是:如何使用 ServiceStack 将 POCO 存储到 MariaDB 中,具有复杂类型(对象和结构)的 JSON 并且仍然可以对相同的 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中作为blobbed JSV(第一部分的最后一段:https ://github.com/ServiceStack/ServiceStack.OrmLite ),因此有必要按照建议的方式设置它:
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和示例https://github.com/ServiceStack/ServiceStack.Text/#using-structs-to- custom-json是需要实现的
TStruct.ToString()
和static TStruct.ParseJson()/ParseJsv()
方法。b) 根据https://github.com/ServiceStack/ServiceStack.Text/#typeserializer-details-jsv-format和单元测试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\"}"
. 美好的。- 序列化工作。成功。
- 反序列化失败。我不知道原因,但传入 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()。在使用 Save 期间
ToString()
,JsConfig.SerializeFn
甚至仍然设置了 JsConfig.SerializeFn(也许是设计使然,我不判断)。结果:- 保存失败:但是
json
在保存期间调用的 DeserializeFn 的输入发生了变化,现在它是类似 JSV 的"{StringProp:SomeStruct's String}"
,但仍然不能反序列化为 JSON。 - 反序列化工作。
- 保存失败:保存期间使用的
然后(在写这篇文章时我仍然没有任何解决方案)我找到了 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 中键入名称)。 - 如果我删除了设置,它会在(“双转义”JSON)
RawSerializeFn
中失败。RawDeserializeFn
- 两者都有效。此任务不需要任何
有没有更简单的解决方案?如果有人指出我更好的方向,我会很高兴。
可接受的可能是两个(由于不同的情况,它们都可以访问):
- 如果我是 TStruct 所有者:仅使用纯
TStruct.ToString()
并static TStruct.Parse()
支持 ServiceStack 开箱即用的反序列化和 DB(在 中没有不同的输入Parse()
)。 - 如果我是 TStruct 的消费者,没有实现 JSON 支持并且我无法访问它的代码:直到现在我还没有找到方法,如果 ToString 没有实现:保存到数据库不起作用。也许可以确保 JsConfig 序列化函数足以用于反序列化和在保存到数据库期间使用时。
最好的方法是不使用其他依赖项(例如 Newtonsoft.Json)来序列化结构。对于这种情况,也许有些JsConfig.ShallProcessStructs = true;
(警告:只是一个提示,截至 2021-04-02 不起作用)会很好。