简体   繁体   English

ServiceStack.Text序列化循环引用

[英]ServiceStack.Text serialize circular references

I need to serialize an object graph like this: 我需要像这样序列化一个对象图:

public class A
{
     public B Link1 {get;set;}
}

public class B
{
     public A Link2 {get;set;}
}

So that the json only gets two instances, but is deserialized correctly again. 这样json只获得两个实例,但是再次正确反序列化。 Eg using a meta Id or something similiar. 例如,使用元ID或类似的东西。

I know that there is a way in Json.NET as described here: http://note.harajuku-tech.org/serializing-circular-references-with-jsonnet with meta ids. 我知道Json.NET中有一种方法如下所述: http//note.harajuku-tech.org/serializing-circular-references-with-jsonnet with meta ids。

Is there a similiar feature in ServiceStack.Text Json Serializer? ServiceStack.Text Json Serializer中是否有类似的功能?

Otherwise, is it possible to use Json.NET in ServiceStack and how? 否则,是否可以在ServiceStack使用Json.NET以及如何使用?

EDIT: 编辑:

To make it clear, i ask for instance references, not just the same type. 为了说清楚,我要求提供实例引用,而不仅仅是相同类型。 An example of this may be: 一个例子可能是:

[
    {
        "$id": "1",
        "BroId": 0,
        "Name": "John",
        "Bros": [
            {
                "$id": "2",
                "BroId": 0,
                "Name": "Jared",
                "Bros": [
                    {
                        "$ref": "1"
                    }
                ]
            }
        ]
    },
    {
        "$ref": "2"
    }
]

There are only 2 objects "really" serialized, the rest is reused using the $ref property field. 只有2个对象“真正”序列化,其余对象使用$ref属性字段重用。 Think of an object model having a collection of subitems. 想象一下具有子项集合的对象模型。 These subitems have a back-reference to their parent object. 这些子项具有对其父对象的反向引用。 EG Customer/Order. EG客户/订单。 One customer has multiple orders, each order has a reference to its customer. 一个客户有多个订单,每个订单都有一个对其客户的引用。 Now think of what happens, if you serialize one customer. 现在想想如果你序列化一个客户会发生什么。

Customer
 -> Order
  -> Customer
   -> Order
    -> ...

And you result in something similiar to this site's name. 而且你会得到类似于这个网站名称的东西。 ;) ;)

I really like ServiceStack for its clearity, not to require KnownTypeAttribute s etc. 我非常喜欢ServiceStack的清晰度,不需要KnownTypeAttribute等。

I would love to keep it that clean, without to implement custom loader/object initializers in my business logic pocos. 我希望保持它干净,而不是在我的业务逻辑pocos中实现自定义加载器/对象初始化器。

I solved the problem by an alternative way. 我通过另一种方式解决了这个问题。 This actually works, but it could make problems later when using more complex data structures with multiple circular references. 这实际上是有效的,但是当使用具有多个循环引用的更复杂的数据结构时,它可能会出现问题。 But for now there is no need. 但目前没有必要。

I tried in adding the circular references feature to ServiceStack.Text but found no point to start at it. 我尝试将循环引用功能添加到ServiceStack.Text但发现没有必要从它开始。 Maybe mythz could give me a hint? 也许mythz可以给我一个提示? The feature should be really simple to be done. 该功能应该非常简单。

I needed that feature for serialization of my data model to fully support NHibernate 's merge function. 我需要这个功能来序列化我的数据模型,以完全支持NHibernate的合并功能。

I followed mythz suggestion to just ignore the properties with the IgnoreDataMemberAttribute which cause the circular references. 我遵循mythz建议,忽略了导致循环引用的IgnoreDataMemberAttribute属性。 But this also requires to rebuild them after deserialization again, to get the merge feature working. 但是这也需要在反序列化之后重建它们,以使合并功能正常工作。

-> This is the solution, now follows how i did it: - >这是解决方案,现在遵循我的方式:

I started with a simple prototype to test this, a data model of 我开始用一个简单的原型来测试这个,一个数据模型

Customer 1->n Orders 1->n OrderDetail . Customer 1->ñ Orders 1->ñ OrderDetail

Each class derives from the entity class. 每个类都派生自实体类。

public class Customer : Entity
{
    public virtual string Name { get; set; }
    public virtual string City { get; set; }
    public virtual IList<Order> Orders { get; set; }
}

public class Order : Entity
{
    public virtual DateTime OrderDate { get; set; }
    public virtual IList<OrderDetail> OrderDetails { get; set; }
    [IgnoreDataMember]
    public virtual Customer Customer { get; set; }
}

public class OrderDetail : Entity
{
    public virtual string ProductName { get; set; }
    public virtual int Amount { get; set; }
    [IgnoreDataMember]
    public virtual Order Order{ get; set; }
}

As you can see, Order and OrderDetail have a back reference to it's parent objects, which caused the circular references when serialized. 如您所见, OrderOrderDetail具有对其父对象的反向引用,这会在序列化时导致循环引用。 This can be fixed by ignoring the back reference with the IgnoreDataMemberAttribute . 这可以通过使用IgnoreDataMemberAttribute忽略后引用来IgnoreDataMemberAttribute

My assumption now is, that every child instance of Order which is inside Customer 's list property Orders has a back reference to this Customer instance. 我现在的假设是,在Customer的list属性OrdersOrder每个子实例都有一个对这个Customer实例的后引用。

So this is how i rebuild the circular tree: 所以这就是我重建圆形树的方式:

public static class SerializationExtensions
{
    public static void UpdateChildReferences(this object input)
    {
        var hashDictionary = new Dictionary<int, object>();
        hashDictionary.Add(input.GetHashCode(), input);

        var props = input.GetType().GetProperties();
        foreach (var propertyInfo in props)
        {
            if (propertyInfo.PropertyType.GetInterfaces()
                .Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
            {

                var instanceTypesInList = propertyInfo.PropertyType.GetGenericArguments();
                if(instanceTypesInList.Length != 1)
                    continue;

                if (instanceTypesInList[0].IsSubclassOf(typeof(Entity)))
                {
                    var list = (IList)propertyInfo.GetValue(input, null);
                    foreach (object t in list)
                    {
                        UpdateReferenceToParent(input, t);
                        UpdateChildReferences(t);
                    }
                }
            }
        }
    }

    private static void UpdateReferenceToParent(object parent, object item)
    {
        var props = item.GetType().GetProperties();
        var result = props.FirstOrDefault(x => x.PropertyType == parent.GetType());

        if (result != null)
            result.SetValue(item, parent, null);
    }
}

This code does not work for 1->1 entity references for now (no need yet) but i assume it could be easily extended. 此代码现在不适用于1-> 1实体引用(不需要),但我认为它可以很容易地扩展。

This now allows me to have a POCO class model at client, add/update/remove child objects and send the whole tree back to the server. 这现在允许我在客户端有一个POCO类模型,添加/更新/删除子对象并将整个树发送回服务器。 Nhibernate is clever enough to determine, which entity is new/updated/removed. Nhibernate足够聪明地确定哪个实体是新的/更新/删除的。 It also only updates the changed entity and only the changed property as well! 它也只更新已更改的实体,并且仅更新已更改的属性! It also removes all OrderDetails if an Order is removed. 如果删除订单,它还会删除所有OrderDetails。

Thats the fluent nhibernate mapping for completeness: 这是流畅的nhibernate映射的完整性:

public class CustomerMap : ClassMap<Customer>
{
    public CustomerMap()
    {
        Schema("YOURSCHEMA");
        Table("CUSTOMER");
        Id(x => x.Id, "ID").GeneratedBy.Assigned();
        Map(x => x.Name, "NAM");
        Map(x => x.City, "CITY");
        HasMany(x => x.Orders)
            .KeyColumn("CUSTOMER_ID")
            .Not.LazyLoad()
            .Inverse()
            .Cascade.AllDeleteOrphan();


        DynamicUpdate();
    }
}

public class OrderMap : ClassMap<Order>
{
    public OrderMap()
    {
        Schema("YOURSCHEMA");
        Table("CUSTOMER_ORDER");
        Id(x => x.Id, "ID").GeneratedBy.Assigned();
        Map(x => x.OrderDate, "ORDER_DATE");
        HasMany(x => x.OrderDetails)
            .KeyColumn("ORDER_ID")
            .Not.LazyLoad()
            .Inverse()
            .Cascade.AllDeleteOrphan();

        References<Customer>(x => x.Customer, "CUSTOMER_ID");
        DynamicUpdate();
    }
}

public class OrderDetailMap : ClassMap<OrderDetail>
{
    public OrderDetailMap()
    {
        Schema("YOURSCHEMA");
        Table("ORDER_DETAIL");
        Id(x => x.Id, "ID").GeneratedBy.Assigned();
        Map(x => x.ProductName, "PRODUCT_NAME");
        Map(x => x.Amount, "AMOUNT");

        References<Order>(x => x.Order, "ORDER_ID");
        DynamicUpdate();
    }
}

DynamicUpdate() is used to let nhibernate only update the changed properties. DynamicUpdate()用于让nhibernate仅更新已更改的属性。 You now only need to use the ISession.Merge(customer) function to save everything correctly. 您现在只需要使用ISession.Merge(customer)功能来正确保存所有内容。

If anyone needs to be able to serialize object graphs with cycles, JSON.NET does support it: 如果有人需要能够使用循环序列化对象图,JSON.NET确实支持它:

new JsonSerializer
{
    PreserveReferencesHandling = PreserveReferencesHandling.Objects
};

ServiceStack supports circular references by default. ServiceStack默认支持循环引用。

Why not try this yourself first to verify if there's an actual issue before posting? 为什么不在发布前先自行尝试验证是否存在实际问题? This would take less effort than creating a new question and asking someone else to do it. 这比创建一个新问题并要求其他人这样做更省力。

Following your example: 按照你的例子:

public class A
{
    public string Name { get; set; }
    public B Link1 { get; set; }
}

public class B
{
    public string Name { get; set; }
    public A Link2 { get; set; }
}

var dto = new A { 
   Name = "A1", 
   Link1 = new B { Name = "B1", Link2 = new A { Name = "A2" } } 
};
dto.ToJson().Print();

Will print the JSON string: 将打印JSON字符串:

{"Name":"A1","Link1":{"Name":"B1","Link2":{"Name":"A2"}}}

Whilst serializing it to JSON and deserializing it back again like: 虽然将它序列化为JSON并将其再次反序列化,如:

var fromJson = dto.ToJson().FromJson<A>();
fromJson.PrintDump();

Will dump the contents: 将转储内容:

{
    Name: A1,
    Link1: 
    {
        Name: B1,
        Link2: 
        {
            Name: A2
        }
    }
}

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

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