1

我有以下设置: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 的序列化不适用于结构,有必要以特殊方式对待它们:
    1. 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 中的“双重转义”反序列化?

    2. 我尝试使用 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。
      • 反序列化工作。
    3. 然后(在写这篇文章时我仍然没有任何解决方案)我找到了 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 不起作用)会很好。

4

1 回答 1

0

ServiceStack 将结构视为单个标量值类型,就像大多数核心 BCL 值类型(例如TimeSpanDateTime等)一样。重载Parse()andToString()方法和 StructConstructor让您可以控制自定义结构的序列化/反序列化。

文档已更正。结构使用Parse而类使用ParseJson/ParseJsv

如果您想序列化模型属性,我建议您使用 aclass代替,因为您正在寻找的行为是 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();
于 2021-04-02T09:19:15.130 回答