返回奇数 JSON 层次结构的集合导航属性
Collection Navigation Properties returning odd JSON hierarchy
我有2个类:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
public B myB { get; set; }
}
public class B
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<A> myAs { get; set; }
}
我正在使用 Postman 测试 Api 调用。
public IEnumerable<B> GetBs()
{
return _context.Bs.Include(b => b.myAs).ToList();
}
returns 正如预期的那样,B 对象及其关联的 A 对象的列表:
{
"Id": 1,
"Name": "B2",
"myAs": [
{
"Id": 1,
"Name": "A1"
},
{
"Id": 2,
"Name": "A2"
}
]
},
{
"Id": 2,
"Name": "B3",
"myAs": [
{
"Id": 3,
"Name": "A3"
},
{
"Id": 4,
"Name": "A4"
},
{
"Id": 5,
"Name": "A5"
}
]
}
相反returns一个奇怪的层次结构:
public IEnumerable<A> GetAs()
{
return _context.As.Include(a => a.myB).ToList();
}
returns:
[
{
"Id": 1,
"Name": "A1",
"myB": {
"Id": 1,
"Name": "B2",
"myAs": [
{
"Id": 2,
"Name": "A2"
}
]
}
},
{
"Id": 2,
"Name": "A2",
"myB": {
"Id": 1,
"Name": "B2",
"myAs": [
{
"Id": 1,
"Name": "A1"
}
]
}
},
{
"Id": 3,
"Name": "A3",
"myB": {
"Id": 2,
"Name": "B3",
"myAs": [
{
"Id": 4,
"Name": "A4"
},
{
"Id": 5,
"Name": "A5"
}
]
}
}
]
GetAs 方法returns A 对象与 B 对象以及进一步嵌套的 A 对象。
经过一些研究后我的理解(我在这里可能是非常错误的)是因为 A 有一个导航 属性 到 B (myB) 而 B 有一个导航 属性 到一个列表A 对象 (myAs) 这导致了某种循环。
我的问题是
- 我的理解对吗?这就是层次结构以这种奇怪的布局返回的原因吗?
- 我该如何解决这个问题?我可以将 ICollection 导航 属性 从域模型中取出,但我无法再查询 As 及其关联的 Bs ?
注意 A 和 B 实际上不是我的领域模型。我只是想让示例尽可能简单。
提前致谢。
这里有几件事:
输出具有预期的形状。正如您所怀疑的,双向引用正在被序列化程序扩展。想一想如果您递归地手动序列化每个对象的每个 属性 会发生什么。就是这样。
要解决眼前的问题,请像这样配置您的默认序列化程序设置:
jsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.Serialization.ReferenceLoopHandling.Ignore;
以上内容在制作原型时很有用,但当您的应用程序更加正式时,您应该从您的网络 API 端点创建和 return 专用视图模型类型。
public class AViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
public class BViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<AViewModel> myAs { get; set; }
}
public IEnumerable<BViewModel> GetBs()
{
return _context.Bs.Include(b => b.myAs)
.Select(b => new BViewModel
{
Id = b.Id,
Name = b.Name,
As = b.As.Select(a => new AViewModel
{
Id = a.Id,
Name = a.Name
})
})
.ToList();
}
值得注意的是,有些库(例如广受好评的 AutoMapper)可以为您执行模型类型之间的这些转换,使用反射按名称自动分配相应的属性。
就我个人而言,我尽量避免使用基于反射的方法,因为它们往往会使代码难以静态推理。这阻碍了像我们这样的人类读者和像 C# 语言这样的工具。
也就是说,根据手头的任务,权衡是值得的。我希望最终能看到语言级别的支持,消除这种样板分配,而不会进入严格的领域,但我还有很长的等待时间。
我有2个类:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
public B myB { get; set; }
}
public class B
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<A> myAs { get; set; }
}
我正在使用 Postman 测试 Api 调用。
public IEnumerable<B> GetBs()
{
return _context.Bs.Include(b => b.myAs).ToList();
}
returns 正如预期的那样,B 对象及其关联的 A 对象的列表:
{
"Id": 1,
"Name": "B2",
"myAs": [
{
"Id": 1,
"Name": "A1"
},
{
"Id": 2,
"Name": "A2"
}
]
},
{
"Id": 2,
"Name": "B3",
"myAs": [
{
"Id": 3,
"Name": "A3"
},
{
"Id": 4,
"Name": "A4"
},
{
"Id": 5,
"Name": "A5"
}
]
}
相反returns一个奇怪的层次结构:
public IEnumerable<A> GetAs()
{
return _context.As.Include(a => a.myB).ToList();
}
returns:
[
{
"Id": 1,
"Name": "A1",
"myB": {
"Id": 1,
"Name": "B2",
"myAs": [
{
"Id": 2,
"Name": "A2"
}
]
}
},
{
"Id": 2,
"Name": "A2",
"myB": {
"Id": 1,
"Name": "B2",
"myAs": [
{
"Id": 1,
"Name": "A1"
}
]
}
},
{
"Id": 3,
"Name": "A3",
"myB": {
"Id": 2,
"Name": "B3",
"myAs": [
{
"Id": 4,
"Name": "A4"
},
{
"Id": 5,
"Name": "A5"
}
]
}
}
]
GetAs 方法returns A 对象与 B 对象以及进一步嵌套的 A 对象。
经过一些研究后我的理解(我在这里可能是非常错误的)是因为 A 有一个导航 属性 到 B (myB) 而 B 有一个导航 属性 到一个列表A 对象 (myAs) 这导致了某种循环。
我的问题是
- 我的理解对吗?这就是层次结构以这种奇怪的布局返回的原因吗?
- 我该如何解决这个问题?我可以将 ICollection 导航 属性 从域模型中取出,但我无法再查询 As 及其关联的 Bs ?
注意 A 和 B 实际上不是我的领域模型。我只是想让示例尽可能简单。
提前致谢。
这里有几件事:
输出具有预期的形状。正如您所怀疑的,双向引用正在被序列化程序扩展。想一想如果您递归地手动序列化每个对象的每个 属性 会发生什么。就是这样。
要解决眼前的问题,请像这样配置您的默认序列化程序设置:
jsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.Serialization.ReferenceLoopHandling.Ignore;
以上内容在制作原型时很有用,但当您的应用程序更加正式时,您应该从您的网络 API 端点创建和 return 专用视图模型类型。
public class AViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
public class BViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<AViewModel> myAs { get; set; }
}
public IEnumerable<BViewModel> GetBs()
{
return _context.Bs.Include(b => b.myAs)
.Select(b => new BViewModel
{
Id = b.Id,
Name = b.Name,
As = b.As.Select(a => new AViewModel
{
Id = a.Id,
Name = a.Name
})
})
.ToList();
}
值得注意的是,有些库(例如广受好评的 AutoMapper)可以为您执行模型类型之间的这些转换,使用反射按名称自动分配相应的属性。
就我个人而言,我尽量避免使用基于反射的方法,因为它们往往会使代码难以静态推理。这阻碍了像我们这样的人类读者和像 C# 语言这样的工具。
也就是说,根据手头的任务,权衡是值得的。我希望最终能看到语言级别的支持,消除这种样板分配,而不会进入严格的领域,但我还有很长的等待时间。