简体   繁体   English

如何使用 ServiceStack 将 POCO 存储到具有复杂类型(对象和结构)的 MariaDB 被标记为 JSON?

[英]How to use ServiceStack to store POCOs to MariaDB having complex types (objects and structs) blobbed as JSON?

I've got following setup: C#, ServiceStack, MariaDB, POCOs with objects and structs, JSON.我有以下设置:C#、ServiceStack、MariaDB、带有对象和结构的 POCO、JSON。

The main question is : how to use ServiceStack to store POCOs to MariaDB having complex types (objects and structs) blobbed as JSON and still have working de/serialization of the same POCOs?主要问题是:如何使用 ServiceStack 将 POCO 存储到 MariaDB 具有复杂类型(对象和结构)的 JSON 并且仍然可以对相同的 POCO 进行反序列化/序列化? All of these single tasks are supported, but I had problems when all put together mainly because of structs.所有这些单个任务都受支持,但是当所有这些任务放在一起时我遇到了问题,主要是因为结构。

... finally during writing this I found some solution and it may look like I answered my own question, but I still would like to know the answer from more skilled people, because the solution I found is a little bit complicated, I think. ...最后在写这篇文章的过程中,我找到了一些解决方案,看起来我回答了我自己的问题,但我仍然想知道更多技术人员的答案,因为我认为我找到的解决方案有点复杂。 Details and two subquestions arise later in the context.细节和两个子问题在上下文中稍后出现。

Sorry for the length and for possible misinformation caused by my limited knowledge.对不起,由于我的知识有限,可能造成的长度和错误信息。

Simple example.简单的例子。 This is the final working one I ended with.这是我结束的最后一个工作。 At the beginning there were no SomeStruct.ToString()/Parse() methods and no JsConfig settings.一开始没有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);
            }
        }
    }
}

What (I think) I know / have tried but at first didn't help to solve it myself:我知道/尝试过什么(我认为),但起初并没有帮助自己解决:

  • ServiceStack stores complex types in DB as blobbed JSV by default (last paragraph of first section: https://github.com/ServiceStack/ServiceStack.OrmLite ), so it is necessary to set it the way it is proposed: MySqlDialect.Provider.StringSerializer = new JsonStringSerializer(); 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 ) https://github.com/ServiceStack/ServiceStack.OrmLite#pluggable-complex-type-serializers
    => default JSV changed to JSON. => 默认 JSV 更改为 JSON。
  • the ServiceStack's serialization does not work with structs, it is necessary to treat them special way: ServiceStack 的序列化不适用于结构,有必要以特殊方式对待它们:
    1. a) according to https://github.com/ServiceStack/ServiceStack.Text#c-structs-and-value-types and example https://github.com/ServiceStack/ServiceStack.Text/#using-structs-to-customize-json it is necessary to implement TStruct.ToString() and static TStruct.ParseJson()/ParseJsv() methods. 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) according to 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 it shall be TStruct.ToString() (the same as in a) and static TStruct.Parse() . 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()

      Subquestion #1 : which one is the right one?子问题#1 :哪一个是正确的? For me, ParseJson() was never called, Parse() was.对我来说, ParseJson()从未被调用过, Parse()被调用过。 Documentation issue or is it used in other situation?文档问题还是在其他情况下使用?

      I implemented option b).我实施了选项 b)。 Results:结果:

      • IDbConnection.Save(_mainObject) saved the item to MariaDB. IDbConnection.Save(_mainObject)将项目保存到 MariaDB。 Success.成功。
        Through the saving process ToString() and Parse() were called.通过保存过程调用了ToString()Parse() In Parse, incoming JSON looked this way:在 Parse 中,传入的 JSON 看起来是这样的:
        "{\"StringProp\":\"SomeStruct's String\"}" . "{\"StringProp\":\"SomeStruct's String\"}" Fine.美好的。
      • Serialization worked.序列化工作。 Success.成功。
      • Deserialization failed.反序列化失败。 I don't know the reason, but JSON incoming to Parse() was "double-escaped":我不知道原因,但是传入 Parse() 的 JSON 被“双重转义”:
        "{\\\"StringProp\\\":\\\"SomeStruct's String\\\"}"

      Subquestion #2 : Why the "double-escaping" in Parse on deserialization?子问题 #2 :为什么 Parse 中的“双重转义”反序列化?

    2. I tried to solve structs with JsConfig (and Newtonsoft.Json to get proper JSON):我尝试使用 JsConfig 解决结构(和 Newtonsoft.Json 以获得正确的 JSON):

       JsConfig<SomeStruct>.SerializeFn = someStruct => JsonConvert.SerializeObject(someStruct); JsConfig<SomeStruct>.DeSerializeFn = json => JsonConvert.DeserializeObject<SomeStruct>(json);

      a) at first without ToString() and Parse() defined in the TStruct. a)一开始没有在 TStruct 中定义 ToString() 和 Parse()。 Results:结果:

      • Save failed : the json input in JsonConvert.DeserializeObject(json) that is used during Save was just type name "WinAmbPrototype.SomeStruct" .保存失败:保存期间使用的JsonConvert.DeserializeObject(json)中的json输入只是类型名称"WinAmbPrototype.SomeStruct"
      • De/serialization worked.反序列化工作。

      b) then I implemented ToString() also using Newtonsoft.Json. b)然后我也使用 Newtonsoft.Json 实现了 ToString()。 During Save ToString() was used instead of JsConfig.SerializeFn even the JsConfig.SerializeFn was still set (maybe by design, I do not judge).在 Save ToString()被用来代替JsConfig.SerializeFn甚至 JsConfig.SerializeFn 仍然设置(也许是设计使然,我不判断)。 Results:结果:

      • Save failed : but the json input of DeserializeFn called during Save changed, now it was JSV-like "{StringProp:SomeStruct's String}" , but still not deserializable as JSON.保存失败:但在保存期间调用的 DeserializeFn 的json输入发生了变化,现在它类似于 JSV "{StringProp:SomeStruct's String}" ,但仍不能反序列化为 JSON。
      • De/serialization worked.反序列化工作。
    3. Then (during writing this I was still without any solution) I found JsConfig.Raw* "overrides" and tried them:然后(在写这篇文章时我仍然没有任何解决方案)我找到了 JsConfig.Raw* “覆盖”并尝试了它们:

       JsConfig<SomeStruct>.RawSerializeFn = someStruct => JsonConvert.SerializeObject(someStruct); JsConfig<SomeStruct>.RawDeserializeFn = json => JsonConvert.DeserializeObject<SomeStruct>(json);

      a) at first without ToString() and Parse() defined in the TStruct. a)一开始没有在 TStruct 中定义 ToString() 和 Parse()。 Results are the same as in 2a.结果与 2a 相同。

      b) then I implemented ToString(). b)然后我实现了 ToString()。 Results:结果:

      • BOTH WORKED .两者都有效 No Parse() method needed for this task.此任务不需要Parse()方法。

      But it is very fragile setup:但这是非常脆弱的设置:

      • if I removed ToString() , it failed (now I understand why, default ToString produced JSON with just type name in 2a, 3a).如果我删除了ToString() ,它就会失败(现在我明白了为什么,默认 ToString 生成 JSON ,只在 2a、3a 中输入名称)。
      • if I removed RawSerializeFn setting, it failed in RawDeserializeFn ("double-escaped" JSON).如果我删除RawSerializeFn设置,它会在RawDeserializeFn (“双转义”JSON)中失败。

Is there some simpler solution?有没有更简单的解决方案? I would be very glad if someone points me to better direction.如果有人指出我更好的方向,我会很高兴。

Acceptable would be maybe two (both of them accessible because of different circumstances):可接受的可能是两个(由于不同的情况,它们都可以访问):

  • if I am the TStruct owner: with just pure TStruct.ToString() and static TStruct.Parse() to support out of the box de/serialization and DB by ServiceStack (without different input in Parse() ).如果我是 TStruct 所有者:仅使用纯TStruct.ToString()static TStruct.Parse()来支持 ServiceStack 的开箱即用反序列化和 DB(在Parse()中没有不同的输入)。
  • if I am a consumer of TStruct with no JSON support implemented and I am without access to its code: until now I did not find the way, if the ToString is not implemented: Save to DB did not work.如果我是 TStruct 的消费者,没有实现 JSON 支持并且我无法访问它的代码:直到现在我还没有找到方法,如果没有实现 ToString:保存到数据库不起作用。 Maybe would be fine to ensure JsConfig serialize functions are enough for both de/serialization and when used during saving to DB.也许可以确保 JsConfig 序列化函数足以用于反序列化和在保存到数据库期间使用时。

And the best one would be without employing other dependency (eg Newtonsoft.Json) to serialize structs.最好的方法是不使用其他依赖项(例如 Newtonsoft.Json)来序列化结构。 Maybe some JsConfig.ShallProcessStructs = true;也许一些JsConfig.ShallProcessStructs = true; ( WARNING: just a tip, not working as of 2021-04-02 ) would be fine for such situations. 警告:只是一个提示,自 2021-04-02 起不起作用)对于这种情况是可以的。

ServiceStack treats structs like a single scalar value type, just like most of the core BCL Value Types (eg TimeSpan , DateTime , etc). ServiceStack 将结构视为单个标量值类型,就像大多数核心 BCL 值类型(例如TimeSpanDateTime等)一样。 Overloading the Parse() and ToString() methods and Struct's Constructor let you control the serialization/deserialization of custom structs.重载Parse()ToString()方法以及 Struct 的Constructor可以让您控制自定义结构的序列化/反序列化。

Docs have been corrected.文档已更正。 Structs use Parse whilst classes use ParseJson/ParseJsv结构使用Parse ,而类使用ParseJson/ParseJsv

If you want to serialize a models properties I'd suggest you use a class instead as the behavior you're looking for is that of a POCO DTO.如果您想序列化模型属性,我建议您使用class代替,因为您正在寻找的行为是 POCO DTO 的行为。

If you want to have structs serailized as DTOs in your RDBMS an alternative you can try is to just use JSON.NET for the complex type serialization, eg:如果您想在 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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM