关系数据库导致循环

huangapple go评论105阅读模式
英文:

Relational Database Resulting in Loop

问题

我有以下层次结构:用户 -> 地图 -> 元素 -> 帖子
一个用户可以拥有多个地图,每个地图都会有一些元素,每个元素都会有一些帖子。

  1. type User struct {
  2. UserID uint `gorm:"primarykey;autoIncrement;not_null"`
  3. UserName string `json:"user_name"`
  4. FirstName string `json:"first_name"`
  5. LastName string `json:"last_name"`
  6. Email string `json:"email"`
  7. Password []byte `json:"-"`
  8. Phone string `json:"phone"`
  9. Maps []Map `gorm:"-"`
  10. }
  11. type Map struct {
  12. MapID uint `gorm:"primarykey;autoIncrement;not_null"`
  13. UserID uint `json:"user_id"`
  14. User User `json:"user" gorm:"foreignkey:UserID"`
  15. Title string `json:"title"`
  16. Desc string `json:"desc"`
  17. Elements []Element `gorm:"foreignKey:MapID"`
  18. Date time.Time `json:"date"`
  19. }
  20. type Element struct {
  21. ElementID uint `gorm:"primarykey;autoIncrement;not_null"`
  22. ElementName string `json:"element_name"`
  23. Desc string `json:"desc"`
  24. MapID uint `json:"map_id"`
  25. Map Map `json:"map" gorm:"foreignkey:MapID"`
  26. Posts []Post `gorm:"foreignKey:ElementID"`
  27. Date time.Time `json:"date"`
  28. UserID uint `json:"user_id"`
  29. User User `json:"user" gorm:"foreignkey:UserID"`
  30. }
  31. type Post struct {
  32. PostID uint `gorm:"primarykey;autoIncrement;not_null"`
  33. Title string `json:"p_title"`
  34. Subject string `json:"subject"`
  35. Date time.Time `json:"date"`
  36. Entry string `json:"entry_text"`
  37. ElementID uint `json:"element_id"`
  38. Element Element `json:"element" gorm:"foreignkey:ElementID"`
  39. UserID uint `json:"user_id"`
  40. User User `json:"user" gorm:"foreignkey:UserID"`
  41. }

这些代码看起来都没问题,但是当我从后端发送JSON响应时,可能会出现无限循环的问题。

当我检索用户的所有地图时,它会列出与创建地图的用户相关的用户对象,但是地图还包括元素列表,而在元素对象内部,它将列出它所属的地图,而该地图对象将再次列出其所有元素。

那么,我应该通过只在一个方向上预加载层次结构来处理这个问题吗?

  1. var getmaps []models.Map
  2. database.DB.Preload("User").Preload("Map").Preload("Elements").Offset(offset).Limit(limit).Find(&getmaps)

还是应该修复结构和gorm设置,只获取一个方向上的关系?因为返回一个地图将返回其元素,而每个元素将返回它所属的地图,这样就会循环回到它的元素等等。

这个循环也会发生在元素和帖子之间,其中一个元素将有多个帖子,这些帖子对象将显示它们的元素,而该元素对象将显示其帖子。

我相信有一种理想或最佳的实现方式,所以我想知道人们会推荐什么。

例如,调用带有以下预加载的一个地图:

  1. func DetailMap(c *fiber.Ctx) error {
  2. id, _ := strconv.Atoi(c.Params("id"))
  3. fmt.Println(id)
  4. var smap models.Map
  5. database.DB.Where("map_id=?", id).Preload("User").Preload("Map").Preload("Elements.Posts").First(&smap)
  6. return c.JSON(fiber.Map{
  7. "data": smap,
  8. })
  9. }
  1. "data": {
  2. "MapID": 1,
  3. "UserID": 1,
  4. "user": {
  5. "UserID": 1,
  6. "UserName": "Chris",
  7. "FirstName": "Chris",
  8. "LastName": "XxxXxxxx",
  9. "Email": "xxxxx@gmail.com",
  10. "phone": "123-456-6789",
  11. "Maps": null
  12. },
  13. "Title": "My Map",
  14. "Desc": "This is the subject",
  15. "Elements": [
  16. {
  17. "ElementID": 1,
  18. "ElementType": "BASE",
  19. "ElementName": "Identity",
  20. "BriefDesc": "This is the identity ",
  21. "Desc": "In publishing and graphic design",
  22. "ParentId": "",
  23. "NumElements": 0,
  24. "NumEntries": 0,
  25. "MapID": 1,
  26. "map": {
  27. "MapID": 0,
  28. "UserID": 0,
  29. "user": {
  30. "UserID": 0,
  31. "UserName": "",
  32. "FirstName": "",
  33. "LastName": "",
  34. "Email": "",
  35. "phone": "",
  36. "Maps": null
  37. },
  38. "Title": "",
  39. "Desc": "",
  40. "Elements": null,
  41. "Date": "0001-01-01T00:00:00Z"
  42. },
  43. "Notes": null,
  44. "Questions": null,
  45. "Posts": [
  46. {
  47. "PostId": 1,
  48. "Title": "First Post",
  49. "Subject": "This is the subject",
  50. "Date": "2022-04-11T12:35:55.267-03:00",
  51. "Entry": "This is the Entry",
  52. "ElementID": 1,
  53. "element": {
  54. "ElementID": 0,
  55. "ElementType": "",
  56. "ElementName": "",
  57. "BriefDesc": "",
  58. "Desc": "",
  59. "ParentId": "",
  60. "NumElements": 0,
  61. "NumEntries": 0,
  62. "MapID": 0,
  63. "map": {
  64. "MapID": 0,
  65. "UserID": 0,
  66. "user": {
  67. "UserID": 0,
  68. "UserName": "",
  69. "FirstName": "",
  70. "LastName": "",
  71. "Email": "",
  72. "phone": "",
  73. "Maps": null
  74. },
  75. "Title": "",
  76. "Desc": "",
  77. "Elements": null,
  78. "Date": "0001-01-01T00:00:00Z"
  79. },
  80. "Notes": null,
  81. "Questions": null,
  82. "Posts": null,
  83. "Date": "0001-01-01T00:00:00Z",
  84. "UserID": 0,
  85. "user": {
  86. "UserID": 0,
  87. "UserName": "",
  88. "FirstName": "",
  89. "LastName": "",
  90. "Email": "",
  91. "phone": "",
  92. "Maps": null
  93. }
  94. },
  95. "UserID": 1,
  96. "user": {
  97. "UserID": 0,
  98. "UserName": "",
  99. "FirstName": "",
  100. "LastName": "",
  101. "Email": "",
  102. "phone": "",
  103. "Maps": null
  104. }
  105. }
  106. ],
  107. "Date": "2022-04-11T11:31:01.72-03:00",
  108. "UserID": 1,
  109. "user": {
  110. "UserID": 0,
  111. "UserName": "",
  112. "FirstName": "",
  113. "LastName": "",
  114. "Email": "",
  115. "phone": "",
  116. "Maps": null
  117. }
  118. },
英文:

I have the following hierarchy, Users -> Maps -> Elements -> Posts
A user can have a bunch of maps, each map will have a number of elements and each element will have a number of posts.

type User struct {

  1. UserID uint `gorm:"primarykey;autoIncrement;not_null"`
  2. UserName string `json: user_name`
  3. FirstName string `json: first_name`
  4. LastName string `json: last_name`
  5. Email string `json:email`
  6. Password []byte `json:"-"`
  7. Phone string `json:"phone"`
  8. Maps []Map `gorm:"-"`

}

type Map struct {

  1. MapID uint `gorm:"primarykey;autoIncrement;not_null"`
  2. UserID uint `json:userid`
  3. User User `json:"user"; gorm:"foreignkey:UserID`
  4. Title string `json:title`
  5. Desc string `json: "desc"`
  6. Elements []Element `gorm:"foreignKey:MapID"`
  7. Date time.Time `json: date`

}

type Element struct {

  1. ElementID uint `gorm:"primarykey;autoIncrement;not_null"`
  2. ElementName string `json: element_name`
  3. Desc string `json: desc`
  4. MapID uint `json:mapid`
  5. Map Map `json:"map"; gorm:"foreignkey:MapID`
  6. Posts []Post `gorm:"foreignKey:ElementID"`
  7. Date time.Time `json: date`
  8. UserID uint `json:userid`
  9. User User `json:"user"; gorm:"foreignkey:UserID`

}

type Post struct {

  1. PostId uint `gorm:"primarykey;autoIncrement;not_null"`
  2. Title string `json: p_title`
  3. Subject string `json: subject`
  4. Date time.Time `json: date`
  5. Entry string `json: entry_text`
  6. ElementID uint `json:elementid`
  7. Element Element `json:"element"; gorm:"foreignkey:ElementID`
  8. UserID uint `json:userid`
  9. User User `json:"user"; gorm:"foreignkey:UserID`

}

This all seems to work fine, but now when I send the JSON response from the backend there seems to be potential for an infinite loop.

When I retrieve all of a user's maps, it then lists the user object relating to the user that created the map, but the map then also includes a list of elements and within the element object it is going to list the map it belongs to and that map object will again list all of it's elements.

So should I be handling this by just preloading the hierarchy in one direction?

var getmaps []models.Map
database.DB.Preload("User").Preload("Map").Preload("Elements").Offset(offset).Limit(limit).Find(&getmaps)

Or should I be fixing the struct and gorm settings to only get relationships in one direction? Since returning a map will return it's elements and each element will return the map it belongs to which loops back to its elements etc.

This loop would also happen with Elements and posts where an element will have many posts, those post objects would display their element and that element object would display its posts.

I'm sure there is an ideal or optimum way to implement this so just curious what people would recommend.

Example calling one map with the following preloads

  1. func DetailMap(c *fiber.Ctx) error {
  2. id,_ := strconv.Atoi(c.Params("id"))
  3. fmt.Println(id)
  4. var smap models.Map
  5. database.DB.Where("map_id=?", id).Preload("User").Preload("Map").Preload("Elements.Posts").First(&smap)
  6. return c.JSON(fiber.Map{
  7. "data":smap,
  8. })
  9. }

"data": {

  1. "MapID": 1,
  2. "UserID": 1,
  3. "user": {
  4. "UserID": 1,
  5. "UserName": "Chris",
  6. "FirstName": "Chris",
  7. "LastName": "XxxXxxxx",
  8. "Email": "xxxxx@gmail.com",
  9. "phone": "123-456-6789",
  10. "Maps": null
  11. },
  12. "Title": "My Map",
  13. "Desc": "This is the subject",
  14. "Elements": [
  15. {
  16. "ElementID": 1,
  17. "ElementType": "BASE",
  18. "ElementName": "Identity",
  19. "BriefDesc": "This is the identity ",
  20. "Desc": "In publishing and graphic design
  21. "ParentId": "",
  22. "NumElements": 0,
  23. "NumEntries": 0,
  24. "MapID": 1,
  25. "map": {
  26. "MapID": 0,
  27. "UserID": 0,
  28. "user": {
  29. "UserID": 0,
  30. "UserName": "",
  31. "FirstName": "",
  32. "LastName": "",
  33. "Email": "",
  34. "phone": "",
  35. "Maps": null
  36. },
  37. "Title": "",
  38. "Desc": "",
  39. "Elements": null,
  40. "Date": "0001-01-01T00:00:00Z"
  41. },
  42. "Notes": null,
  43. "Questions": null,
  44. "Posts": [
  45. {
  46. "PostId": 1,
  47. "Title": "First Post",
  48. "Subject": "This is the subject",
  49. "Date": "2022-04-11T12:35:55.267-03:00",
  50. "Entry": "This is the Entry",
  51. "ElementID": 1,
  52. "element": {
  53. "ElementID": 0,
  54. "ElementType": "",
  55. "ElementName": "",
  56. "BriefDesc": "",
  57. "Desc": "",
  58. "ParentId": "",
  59. "NumElements": 0,
  60. "NumEntries": 0,
  61. "MapID": 0,
  62. "map": {
  63. "MapID": 0,
  64. "UserID": 0,
  65. "user": {
  66. "UserID": 0,
  67. "UserName": "",
  68. "FirstName": "",
  69. "LastName": "",
  70. "Email": "",
  71. "phone": "",
  72. "Maps": null
  73. },
  74. "Title": "",
  75. "Desc": "",
  76. "Elements": null,
  77. "Date": "0001-01-01T00:00:00Z"
  78. },
  79. "Notes": null,
  80. "Questions": null,
  81. "Posts": null,
  82. "Date": "0001-01-01T00:00:00Z",
  83. "UserID": 0,
  84. "user": {
  85. "UserID": 0,
  86. "UserName": "",
  87. "FirstName": "",
  88. "LastName": "",
  89. "Email": "",
  90. "phone": "",
  91. "Maps": null
  92. }
  93. },
  94. "UserID": 1,
  95. "user": {
  96. "UserID": 0,
  97. "UserName": "",
  98. "FirstName": "",
  99. "LastName": "",
  100. "Email": "",
  101. "phone": "",
  102. "Maps": null
  103. }
  104. }
  105. ],
  106. "Date": "2022-04-11T11:31:01.72-03:00",
  107. "UserID": 1,
  108. "user": {
  109. "UserID": 0,
  110. "UserName": "",
  111. "FirstName": "",
  112. "LastName": "",
  113. "Email": "",
  114. "phone": "",
  115. "Maps": null
  116. }
  117. },`

答案1

得分: 1

在你的PostMapElement结构体中,你有以下字段:

  1. UserID uint `json:userid`
  2. User User `json:"user"; gorm:"foreignkey:UserID`

你应该从你的内容结构体中移除User字段,因为你已经有了UserID。在这种情况下,一个"引用"(ID)比包含整个用户对象更合理。客户端可以调用/users/{id}端点并获取更多信息(如果需要的话)。

同时,通过移除User结构体中的Maps []Map(负责你提到的循环),限制User结构体的内容。然后你需要设置像/user/{id}/maps这样的端点,以便客户端可以获取用户的内容。

对于PostElement也是一样的。你可以只存储ID,或者只存储"子"模型的数组。(Map嵌入ElementElement不嵌入Map)。所以要找到一个元素关联的地图,你可以调用端点/maps/{your element's map ID}。对于Element > Post也是一样的。

  1. type Map struct {
  2. gorm.Model // 这会处理ID字段
  3. UserID uint `json:userid`
  4. Title string `json:title`
  5. Desc string `json: "desc"`
  6. Elements []Element // gorm会自动处理关系
  7. Date time.Time `json: date`
  8. }
  1. type Element struct {
  2. gorm.Model // 包含ID
  3. ElementName string `json: element_name`
  4. Desc string `json: desc`
  5. MapID uint `json:mapid`
  6. // Map Map ... 这个关系由另一个端点描述 - /elements/{elementId}/map 来获取相关的地图
  7. Posts []Post // gorm会处理这个
  8. Date time.Time `json: date`
  9. UserID uint `json:userid`
  10. }
  1. type Post struct {
  2. gorm.Model
  3. Title string `json: p_title`
  4. Subject string `json: subject`
  5. Date time.Time `json: date`
  6. Entry string `json: entry_text`
  7. ElementID uint `json:elementid` // gorm会将其作为外键使用
  8. UserID uint `json:userid`
  9. }

为了避免循环,你需要在结构体级别上使关系单向,并设置更多的HTTP路由来实现双向关系(参见注释的代码)。

我描述的是一个简单的REST API。Microsoft有一个很好的概述:https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design#organize-the-api-design-around-resources - 特别是客户/订单关系会对你有兴趣。

在gorm方面,你将使用一对多关联:https://gorm.io/docs/has_many.html

英文:

In your Post, Map and Element structs you have the fields:

  1. UserID uint `json:userid`
  2. User User `json:"user"; gorm:"foreignkey:UserID`

You should remove the User field from your content structs because you already have a UserID. A "reference" (ID) in this case is more sensible than including the whole user object. The client can call a /users/{id} endpoint and find more info if needed.

Also limit the content of the User struct by removing Maps []Map (responsible for the loop you mentioned). You would then need to set up endpoints like /user/{id}/maps so the client can get the user's content.

The same applies for Post and Element. You could go all-out and store only IDs, or you can store an array of only "child" models. (Map embeds Element, Element DOES NOT embed Map). So to find the associated map of an element, you would call endpoint /maps/{your element's map ID}. same for Element > Post

  1. type Map struct {
  2. gorm.Model // this takes care of the ID field
  3. UserID uint `json:userid`
  4. Title string `json:title`
  5. Desc string `json: "desc"`
  6. Elements []Element // gorm will handle the relationship automatically
  7. Date time.Time `json: date`
  8. }
  1. type Element struct {
  2. gorm.Model // includes ID
  3. ElementName string `json: element_name`
  4. Desc string `json: desc`
  5. MapID uint `json:mapid`
  6. // Map Map ... This relationship is described by another endpoint - /elements/{elementId}/map to get the related map
  7. Posts []Post // gorm handles this
  8. Date time.Time `json: date`
  9. UserID uint `json:userid`
  10. }
  1. type Post struct {
  2. gorm.Model
  3. Title string `json: p_title`
  4. Subject string `json: subject`
  5. Date time.Time `json: date`
  6. Entry string `json: entry_text`
  7. ElementID uint `json:elementid` // gorm will use this as fk
  8. UserID uint `json:userid`
  9. }

To avoid loops you will need to make the relationships one-directional at a struct level, and set up more http routes to go in the other direction (see commented code).

What I described is a simple REST api. Microsoft has a nice overview: https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design#organize-the-api-design-around-resources - specifically the customer/order relationship will interest you.

On the gorm side you would be using one-to-many associations: https://gorm.io/docs/has_many.html

huangapple
  • 本文由 发表于 2022年4月12日 04:38:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/71834031.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定