Golang GORM嵌入式结构体

4
我想要加入和预加载两个表,并将结果合并,但是我无法得到期望的结果。

models.go

type Product struct {
    ID          uint   `json:"-"`
    Name        string `json:"name"`
    Description string `json:"description"`
}

type TransactionItem struct {
    ID        uint `json:"-"`
    ProductID uint `json:"-"`
    Qty       int  `json:"qty"`
    Price     int  `json:"price"`
    Product   // I want to combine properties of Product into Transaction Item
}

controllers.go

var items []TransactionItem
if e := db.Preload("Product").Find(&items).Error; e != nil {
    log.Println(e)
}

期望结果:

[
  {
     "qty": 1,
     "price": 50,
     "name": "Product name",
     "description": "This is the product description"
  }, { ... }
]

我尝试过使用"gorm:foreigKey=ProductID;references=ID;embedded;embeddedPrefix:product",并尝试了不同的组合,但仍然无法将product.name和product.description的数据合并到TransactionItem中。如果我将Transaction Item更改为以下内容:
type TransactionItem struct {
    ID        uint `json:"-"`
    ProductID uint `json:"-"`
    Qty       int  `json:"qty"`
    Price     int  `json:"price"`
    Product   Product 
}

//This will work and display data from product table, but the result is as follow:
[
  {
     "qty": 1,
     "price": 50,
     "product": {
       "name": "Product name",
       "description": "This is the product description"
     }
  }, { ... }
]

我可以通过循环这个结果并手动设置名称和描述来实现所需的结果,但我认为 GORM 应该有一种自动完成这个任务的方式,对吗?提前谢谢你。
4个回答

1
为了实现您期望的结果,即在生成的JSON中,Product字段直接嵌入到TransactionItem中,您需要使用嵌入结构体,并使用所需的JSON字段名称对嵌入结构体字段进行别名。
给定以下结构体,GORM会自动将Product结构体的字段嵌入到TransactionItem中:
type Product struct {
    ID          uint   `json:"-"`
    Name        string `json:"name"`
    Description string `json:"description"`
}

type TransactionItem struct {
    ID        uint `json:"-"`
    ProductID uint `json:"-"`
    Qty       int  `json:"qty"`
    Price     int  `json:"price"`
    Product   `gorm:"foreignKey:ProductID"` // Embed Product struct
}

当GORM加载一个TransactionItem时,它应该自动加载并嵌入相应的Product,而无需显式调用Preload("Product")。
然而,这将生成如下的JSON结构:
{
    "qty": 1,
    "price": 50,
    "ID": 1,
    "Name": "Product name",
    "Description": "This is the product description"
}

为了生成您所需的JSON结构,您需要向嵌套在TransactionItem结构中的Product结构的字段添加JSON字段标签,就像这样:
type TransactionItem struct {
    ID          uint `json:"-"`
    ProductID   uint `json:"-"`
    Qty         int  `json:"qty"`
    Price       int  `json:"price"`
    Name        string `json:"name"`        // Alias for Product.Name
    Description string `json:"description"` // Alias for Product.Description
    Product     `gorm:"foreignKey:ProductID"` // Embed Product struct
}

这将生成您所需的JSON结构:
{
    "qty": 1,
    "price": 50,
    "name": "Product name",
    "description": "This is the product description"
}

记得在没有显式预加载的情况下运行此加载代码。
var items []TransactionItem
if e := db.Find(&items).Error; e != nil {
    log.Println(e)
}

请注意,这种技术会将TransactionItem中的Product字段进行别名处理,从而覆盖它们,因此必须确保ProductTransactionItem中的字段之间没有命名冲突。

0
你可以使用嵌套来实现这个,如果我没记错的话。它会将两个结构体合并而不是将其视为关联关系,然后你可以进行预加载。就像下面这样:
type Author struct {
  Name  string
  Email string
}

type Blog struct {
  ID      int
  Author  Author `gorm:"embedded"` // <-- Use this
  Upvotes int32
}

// equals
type Blog struct {
  ID    int64
  Name  string
  Email string
  Upvotes  int32
}

注意:代码未经测试。

要了解有关嵌入式的更多信息,请阅读此处


是的,这也是我想的,但显然这产生了相同的结果,而不是期望的结果。 - undefined

0
如果这个方法不起作用:
type TransactionItem struct {
    ID              uint `gorm:"primaryKey" json:"-"`
    ProductID       uint `gorm:"column:product_id" json:"-"`
    Qty             int  `gorm:"column:qty" json:"qty"`
    Price           int  `gorm:"column:price" json:"price"`
    Product         Product `gorm:"foreignKey:ProductID;references:ID;embedded;embeddedPrefix:product_"`
}

这意味着/说明GORM在查询过程中不直接支持将嵌入的结构字段合并到父结构中。GORM中的"嵌入特性"更多地涉及表结构,而不是定制查询结果。
要实现您期望的输出,您需要在获取记录后以编程方式进行操作。
var items []TransactionItem
if e := db.Preload("Product").Find(&items).Error; e != nil {
    log.Println(e)
}

type ResponseItem struct {
    Qty         int    `json:"qty"`
    Price       int    `json:"price"`
    Name        string `json:"name"`
    Description string `json:"description"`
}

var response []ResponseItem
for _, item := range items {
    response = append(response, ResponseItem{
        Qty:         item.Qty,
        Price:       item.Price,
        Name:        item.Product.Name,
        Description: item.Product.Description,
    })
}

// Now `response` will have the desired structure
// You can then marshal it to JSON for your response

通过一个新的结构体ResponseItem,来匹配你所期望的JSON结构。
在获取TransactionItem切片之后,我遍历它来创建一个新的ResponseItem切片,将Product.NameProduct.Description字段提取出来,并设置到新的ResponseItem实例中。

我实际上也为解决方法做了类似的事情,但我希望GORM本身内置了一些更简便、代码更少的功能。 - undefined
@Charas 我明白,并且也希望你的悬赏能够吸引到更好的答案 :) - undefined
谢谢你的回答,虽然如此,如果在三天内没有更好的答案,我会把奖励给你,以感谢你的努力和迄今为止最接近最佳答案的回答。 - undefined

0
你可以使用join来实现结果。
var items []TransactionItem

db.Joins("JOIN products ON transaction_items.product_id = products.id").
    Select("transaction_items.qty, transaction_items.price, products.name, products.description").
    Find(&items)

抱歉,但我不想使用加入,因为这完全违背了它的初衷,它必须使用预加载。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接