如何让 self-referencing C# 实体用 JSON.Net 序列化 children?
How to get self-referencing C# Entities to serialize children with JSON.Net?
我很难在 POCO 上获取分层 child 集合进行序列化。这是一个 Azure 移动服务项目。我想出了一个简化的例子来尝试让它工作。
以下是项目中唯一的DTO POCO。请注意名为 Parent
和 Children
的导航属性(标记为虚拟),用于 link 层次结构。
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; }
}
控制器:
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);
}
}
上下文 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) */
}
}
上下文初始化程序:
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);
}
}
为什么 Children 从未出现在结果中?
[
{
"$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"
}
]
...即使生成的 API 文档样本表明应该 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();
}
结果:
[
{
"$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"
}
]
我很难在 POCO 上获取分层 child 集合进行序列化。这是一个 Azure 移动服务项目。我想出了一个简化的例子来尝试让它工作。
以下是项目中唯一的DTO POCO。请注意名为 Parent
和 Children
的导航属性(标记为虚拟),用于 link 层次结构。
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; }
}
控制器:
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);
}
}
上下文 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) */
}
}
上下文初始化程序:
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);
}
}
为什么 Children 从未出现在结果中?
[
{
"$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"
}
]
...即使生成的 API 文档样本表明应该 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();
}
结果:
[
{
"$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"
}
]