简体   繁体   中英

How to get self-referencing C# Entities to serialize children with JSON.Net?

I'm having a hell of a time getting a hierarchical child collection on a POCO to serialize. This is an Azure Mobile Services project. I've come up with a simplified example in order to try to get this to work.

The following is the sole DTO POCO in the project. Note the navigation properties (marked virtual) named Parent and Children , used to link the hierarchy together.

public class Node : EntityData
{
    public Node()
    {
        Children = new List<Node>();
    }

    public string Text { get; set; }

    public string ParentId { get; set; }

    public virtual Node Parent { get; set; }

    public virtual ICollection<Node> Children { get; set; }
}

The controller:

public class NodeController : TableController<Node>
{
    /* BEGIN boilerplate (from Azure Mobile Services project template) */
    protected override void Initialize(HttpControllerContext controllerContext)
    {
        base.Initialize(controllerContext);
        MyContext context = new MyContext();
        DomainManager = new EntityDomainManager<Node>(context, Request, Services);
    }
    /* END boilerplate (from Azure Mobile Services project template) */

    // GET tables/Node
    /// <summary>
    /// Gets all child nodes of a particular node, and their children.
    /// To retrieve the top-level node, pass no id.
    /// </summary>
    /// <param name="id">The id of the node you wish to retrieve.</param>
    /// <returns>The child nodes of a given id.</returns>
    public IQueryable<Node> GetNodes(string id = null)
    {
        return Query().Where(x => x.ParentId == id).Include(x => x.Children);
    }
}

The context class:

public class MyContext : DbContext
{
    private const string connectionStringName = "Name=MS_TableConnectionString";

    public MyContext() : base(connectionStringName)
    {
    } 

    public DbSet<Node> Nodes { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Node>()
            .HasMany(t => t.Children)
            .WithOptional(t => t.Parent)
            .HasForeignKey(t => t.ParentId);

        /* BEGIN boilerplate (from Azure Mobile Services project template) */
        string schema = ServiceSettingsDictionary.GetSchemaName();
        if (!string.IsNullOrEmpty(schema))
        {
            modelBuilder.HasDefaultSchema(schema);
        }

        modelBuilder.Conventions.Add(
            new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
                "ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
        /* END boilerplate (from Azure Mobile Services project template) */
    }
}

The context initializer:

public class MyContextInitializer : ClearDatabaseSchemaAlways<MyContext>
{
    protected override void Seed(MyContext context)
    {
        var node0_1 = new Node { Id = Guid.NewGuid().ToString(), Text = "Node 0-1" };
        var node0_2 = new Node { Id = Guid.NewGuid().ToString(), Text = "Node 0-2" };

        var node1_1 = new Node { Id = Guid.NewGuid().ToString(), Text = "Item 1-1", Parent = node0_1 };
        var node1_2 = new Node { Id = Guid.NewGuid().ToString(), Text = "Item 1-2", Parent = node0_1 };

        var node2_1 = new Node { Id = Guid.NewGuid().ToString(), Text = "Item 2-1", Parent = node0_2 };
        var node2_2 = new Node { Id = Guid.NewGuid().ToString(), Text = "Item 2-2", Parent = node0_2 };

        node0_1.Children.Add(node1_1);
        node0_1.Children.Add(node1_2);

        node0_2.Children.Add(node2_1);
        node0_2.Children.Add(node2_2);

        List<Node> nodes = new List<Node>
        {
            node0_1,
            node0_2
        };

        context.Set<Node>().AddRange(nodes);

        base.Seed(context);
    }
}

Why do the Children never show up in the results?

[
    {
        "$id": "1",
        "id": "2c381538-b8e9-4b7c-b25d-7f6fd8cd373e",
        "parentId": null,
        "text": "Node 0-2"
    },
    {
        "$id": "2",
        "id": "695af179-aa27-45d3-9299-a96c5e719448",
        "parentId": null,
        "text": "Node 0-1"
    }
]

...even though the API documentation sample that gets generated suggests that there SHOULD be Children:

[
    {
        "$id": "1",
        "text": "sample string 1",
        "parentId": "sample string 2",
        "parent": {
          "$ref": "1"
        },
        "children": [
          {
            "$ref": "1"
          },
          {
            "$ref": "1"
          },
          {
            "$ref": "1"
          }
        ],
        "id": "sample string 3",
        "__version": "QEBA",
        "__createdAt": "2015-06-26T01:46:14.108Z",
        "__updatedAt": "2015-06-26T01:46:14.108Z",
        "__deleted": true
      },
      {
        "$ref": "1"
      },
      {
        "$ref": "1"
      }
    ]

Finally figured it out. Just had to change the return type of the controller's GetNodes() method from IQueryable<Node> to IEnmerable<Node> . I still don't know exactly why , but I'm glad it's working. Now I can move on to the important stuff: modifying my actual service, and then consuming it in the Xamarin app.

Revised method signature (threw in some async for good measure):

public async Task<IEnumerable<Node>> GetNode(string id = null)
{
    return await Query()
        .Where(x => x.ParentId == id)
        .Include(x => x.Children)
        .ToListAsync();
}

Results:

[
  {
    "$id": "1",
    "children": [
      {
        "$id": "2",
        "children": [],
        "parent": {
          "$ref": "1"
        },
        "text": "Item 1-2",
        "parentId": "6a616abe-8328-4ca0-92e4-de0734101f2f",
        "id": "398cf2e6-dbfb-4fe1-8555-13090885292f",
        "__version": "AAAAAAABbzQ=",
        "__createdAt": "2015-06-26T21:07:31.466Z",
        "__updatedAt": "2015-06-26T21:07:31.466Z"
      },
      {
        "$id": "3",
        "children": [],
        "parent": {
          "$ref": "1"
        },
        "text": "Item 1-1",
        "parentId": "6a616abe-8328-4ca0-92e4-de0734101f2f",
        "id": "6560562b-0694-4436-bc50-08ead5af29e0",
        "__version": "AAAAAAABbzY=",
        "__createdAt": "2015-06-26T21:07:31.521Z",
        "__updatedAt": "2015-06-26T21:07:31.521Z"
      }
    ],
    "text": "Node 0-1",
    "id": "6a616abe-8328-4ca0-92e4-de0734101f2f",
    "__version": "AAAAAAABbzI=",
    "__createdAt": "2015-06-26T21:07:31.382Z",
    "__updatedAt": "2015-06-26T21:07:31.399Z"
  },
  {
    "$id": "4",
    "children": [
      {
        "$id": "5",
        "children": [],
        "parent": {
          "$ref": "4"
        },
        "text": "Item 2-1",
        "parentId": "fdf50979-e191-41c7-b6c4-c2067cd88dc9",
        "id": "9417571f-f89e-4183-8c14-ba7da3629624",
        "__version": "AAAAAAABbzo=",
        "__createdAt": "2015-06-26T21:07:31.634Z",
        "__updatedAt": "2015-06-26T21:07:31.634Z"
      },
      {
        "$id": "6",
        "children": [],
        "parent": {
          "$ref": "4"
        },
        "text": "Item 2-2",
        "parentId": "fdf50979-e191-41c7-b6c4-c2067cd88dc9",
        "id": "b077165c-1e3e-456f-b4c5-6b116941ba30",
        "__version": "AAAAAAABbzw=",
        "__createdAt": "2015-06-26T21:07:31.693Z",
        "__updatedAt": "2015-06-26T21:07:31.694Z"
      }
    ],
    "text": "Node 0-2",
    "id": "fdf50979-e191-41c7-b6c4-c2067cd88dc9",
    "__version": "AAAAAAABbzg=",
    "__createdAt": "2015-06-26T21:07:31.575Z",
    "__updatedAt": "2015-06-26T21:07:31.575Z"
  }
]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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