简体   繁体   English

在EF模型的JSON序列化期间向嵌套对象添加属性

[英]Adding properties to nested objects during JSON serialization of EF model

We have a situation where we are using JSON to serialize EF models for use in data synchronization. 我们有一种情况是我们使用JSON来序列化EF模型以用于数据同步。 For the synchronization to work properly we need the table name of the models. 为了使同步正常工作,我们需要模型的表名。 That's not hard, and we have code that gives us that already. 这并不难,我们的代码已经提供给我们了。 The main issue is transmitting that data in the JSON. 主要问题是在JSON中传输该数据。

For example let's say we have the following models. 例如,假设我们有以下模型。

public class Foo
{
    // ...
    public virtual ICollection<Bar> Bars { get; set; }
}

public class Bar
{
    // ...
    public virtual ICollection<FooBar> FooBars { get; set; }
}

public class FooBar
{
    // ...
}

We pull down all the nested items via includes and then serialize it. 我们通过includes下拉所有嵌套项,然后序列化它。 The issue is this, we need to insert the table name for the entities as metadata into the JSON without adding it to the models themselves. 问题是,我们需要将实体的表名作为元数据插入到JSON中,而不将其添加到模型本身。

For example, in the above scenario the JSON would look something like 例如,在上面的场景中,JSON看起来像

{
    "__tableName": "Foos",
    // ...
    "Bars": [
        {
           "__tableName": "Bars"
           // ...
           "FooBars": [
               {
                   "__tableName": "FooBars"
                   // ...
               }
            ]
        }
    ]
}

I figured a custom serializer in JSON.Net would be the best way to go, but either I'm not plugging in at the correct spot, or they don't work the way I thought they do. 我认为JSON.Net中的自定义序列化程序是最好的方法,但要么我没有插入正确的位置,要么它们不按照我认为的方式工作。

I attempted to make a custom JsonConverter as that seems to be the default way of handling custom serialization scenarios. 我尝试制作自定义JsonConverter因为这似乎是处理自定义序列化方案的默认方式。 However, it only seems to be called on the base object to be serialized, not any of the child objects. 但是,它似乎只是在要对序列化的基础对象上调用,而不是任何子对象。

Is there a place I need to plug in to JSON.Net to actually put in this metadata? 有没有我需要插入JSON.Net来实际放入这个元数据的地方? Almost everything I have found on this topic points to JsonConverter but I'm not sure that actually does what I need in this situation. 我在这个主题上找到的几乎所有内容都指向JsonConverter,但我不确定在这种情况下实际上我需要做什么。

One thought I had was loading the object into a JObject in the JsonConverter , then walking the model tree myself and inserting keys as needed, but I was hoping for something a bit more elegant than that. 我有一个想法是将对象加载到JObject中的JsonConverter ,然后自己走模型树并根据需要插入键,但我希望有一些比这更优雅的东西。

Thanks. 谢谢。

Although a JsonConverter seems like the appropriate choice here, it actually doesn't work that well in practice for this type of problem. 虽然JsonConverter在这里看起来是合适的选择,但实际上对于这类问题实际上并没有那么好用。 The issue is that you are wanting to programmatically apply the converter to a broad set of classes, and those classes can contain other classes which use the same converter. 问题是您希望以编程方式将转换器应用于一组广泛的类,并且这些类可以包含使用相同转换器的其他类。 So you will probably get into issues with recursive loops in the converter, which you will need to work around. 因此,您可能会遇到转换器中的递归循环问题,您需要解决这个问题。 It can be done, but it might get a little messy. 它可以做到,但它可能会有点凌乱。

Fortunately, there is a better alternative for this situation. 幸运的是,这种情况有一个更好的选择。 You can use a custom IContractResolver in combination with an IValueProvider to insert the __tableName property into the JSON for each object that has a table name. 您可以将自定义IContractResolverIValueProvider结合使用,将__tableName属性插入到具有表名的每个对象的JSON中。 The resolver is responsible for checking whether a particular object type has an associated table name, and if so, setting up the extra property for the type. 解析器负责检查特定对象类型是否具有关联的表名,如果是,则设置该类型的额外属性。 The value provider simply returns the table name when it is asked. 值提供程序只是在询问时返回表名。

Here is the code you would need: 以下是您需要的代码:

class TableNameInsertionResolver : DefaultContractResolver
{
    private Dictionary<string, string> tableNames;

    public TableNameInsertionResolver(Dictionary<string, string> tableNames)
    {
        this.tableNames = tableNames;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);

        // If there is an associated table name for this type, 
        // add a virtual property that will return the name
        string tableName;
        if (tableNames.TryGetValue(type.FullName, out tableName))
        {
            props.Insert(0, new JsonProperty
            {
                DeclaringType = type,
                PropertyType = typeof(string),
                PropertyName = "__tableName",
                ValueProvider = new TableNameValueProvider(tableName),
                Readable = true,
                Writable = false
            });
        }

        return props;
    }

    class TableNameValueProvider : IValueProvider
    {
        private string tableName;

        public TableNameValueProvider(string tableName)
        {
            this.tableName = tableName;
        }

        public object GetValue(object target)
        {
            return tableName;
        }

        public void SetValue(object target, object value)
        {
            throw new NotImplementedException();
        }
    }
}

To plug this into the serialization pipeline, create an instance of JsonSerializerSettings and set the ContractResolver property to an instance of the custom resolver. 要将其插入序列化管道,请创建JsonSerializerSettings的实例, JsonSerializerSettings ContractResolver属性设置为自定义解析程序的实例。 Then pass the settings to the serializer. 然后将设置传递给序列化程序。 And that's it; 就是这样; it should "just work". 它应该“正常工作”。

Here is a demo: 这是一个演示:

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo
        {
            Id = 1,
            Bars = new List<Bar>
            {
                new Bar
                {
                    Id = 10,
                    FooBars = new List<FooBar>
                    {
                        new FooBar { Id = 100 },
                        new FooBar { Id = 101 }
                    }
                },
                new Bar
                {
                    Id = 11,
                    FooBars = new List<FooBar>
                    {
                        new FooBar { Id = 110 },
                        new FooBar { Id = 111 },
                    }
                }
            }
        };

        // Dictionary mapping class names to table names.
        Dictionary<string, string> tableNames = new Dictionary<string, string>();
        tableNames.Add(typeof(Foo).FullName, "Foos");
        tableNames.Add(typeof(Bar).FullName, "Bars");
        tableNames.Add(typeof(FooBar).FullName, "FooBars");

        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.ContractResolver = new TableNameInsertionResolver(tableNames);
        settings.Formatting = Formatting.Indented;

        string json = JsonConvert.SerializeObject(foo, settings);
        Console.WriteLine(json);
    }
}

public class Foo
{
    // ...
    public int Id { get; set; }
    public virtual ICollection<Bar> Bars { get; set; }
}

public class Bar
{
    // ...
    public int Id { get; set; }
    public virtual ICollection<FooBar> FooBars { get; set; }
}

public class FooBar
{
    // ...
    public int Id { get; set; }
}

Output: 输出:

{
  "__tableName": "Foos",
  "Id": 1,
  "Bars": [
    {
      "__tableName": "Bars",
      "Id": 10,
      "FooBars": [
        {
          "__tableName": "FooBars",
          "Id": 100
        },
        {
          "__tableName": "FooBars",
          "Id": 101
        }
      ]
    },
    {
      "__tableName": "Bars",
      "Id": 11,
      "FooBars": [
        {
          "__tableName": "FooBars",
          "Id": 110
        },
        {
          "__tableName": "FooBars",
          "Id": 111
        }
      ]
    }
  ]
}

Fiddle: https://dotnetfiddle.net/zG5Zmm 小提琴: https//dotnetfiddle.net/zG5Zmm

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

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