Golang Gorm 一对多与一对一

Golang Gorm one-to-many with has-one

我正在尝试通过构建一个小原型订单管理应用程序来学习 Go 和 Gorm。数据库是 MySQL。通过简单的查询,Gorm 表现出色。然而,当试图获得一个包含一对多和一对一关系的组合的结果集时,Gorm 似乎达不到要求。毫无疑问,实际上是我缺乏理解。我似乎无法找到我正在努力完成的任何在线示例。任何帮助将不胜感激。

Go 结构体

// Order
type Order struct {
    gorm.Model
    Status  string
    OrderItems   []OrderItem
}

// Order line item
type OrderItem struct {
    gorm.Model
    OrderID uint
    ItemID  uint
    Item    Item
    Quantity int
}

// Product
type Item struct {
    gorm.Model
    ItemName     string
    Amount       float32
}

数据库表

orders
id | status
 1 | pending

order_items
id | order_id | item_id | quantity
 1 | 1        | 1       | 1
 2 | 1        | 2       | 4

items
id | item_name   | amount
 1 | Go Mug      | 12.49
 2 | Go Keychain | 6.95
 3 | Go T-Shirt  | 17.99

当前查询

order := &Order 
if err := db.Where("id = ? and status = ?", reqOrder.id, "pending")
.First(&order).Error; err != nil {
    fmt.Printf(err.Error())
}

db.Model(&order).Association("OrderItems").Find(&order.OrderItems)

结果(gorm 进行 2 个数据库查询)

order == Order {
  id: 1,
  status: pending,
  OrderItems[]: {
    {
      ID: 1,
      OrderID: 1,
      ItemID: 1,
      Item: nil,
      Quantity: 1,
    },
    {
      ID: 2,
      OrderID: 1,
      ItemID: 2,
      Item: nil,
      Quantity: 4,
    }
 }

替代查询

order := &Order
db.Where("id = ? and status = ?", reqOrder.id, "cart")
.Preload("OrderItems").Preload("OrderItems.Item").First(&order)

结果(gorm 进行了 3 次数据库查询)

order == Order {
  id: 1,
  status: pending,
  OrderItems[]: {
    {
      ID: 1,
      OrderID: 1,
      ItemID: 1,
      Item: {
        ID: 1,
        ItemName: Go Mug,
        Amount: 12.49,
      }
      Quantity: 1,
    },
    {
      ID: 2,
      OrderID: 1,
      ItemID: 2,
      Item: {
        ID: 2,
        ItemName: Go Keychain,
        Amount: 6.95,
      },
      Quantity: 4,
    }
 }

理想结果

上面的"Alternative query"产生了理想的查询结果。然而,Gorm 进行了 3 个单独的数据库查询来做到这一点。理想情况下,相同的结果将通过 1(或 2)次数据库查询来完成。

这可以在 MySQL 中通过几个连接来完成。 Gorm 允许连接。但是,我希望利用 Gorm 的一些关系魔法。

非常感谢!

如本文所述issue, gorm is not designed to use joins to preload other structs values. If you would like to continue to use gorm and have the ability to use joins to load values, one must use the SQL Builder在gorm中暴露,并编写一些代码来扫描所需的值。

如果需要处理的表格很多,这将变得很麻烦。 如果xorm is available as an option, they support loading struct values. Described under the find bullet point, here.

注意:我没有扫描所有字段,只是为了说明要点。

示例:

package main

import (
    "log"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
    "github.com/kylelemons/godebug/pretty"
)

// Order
type Order struct {
    gorm.Model
    Status     string
    OrderItems []OrderItem
}

// Order line item
type OrderItem struct {
    gorm.Model
    OrderID  uint
    ItemID   uint
    Item     Item
    Quantity int
}

// Product
type Item struct {
    gorm.Model
    ItemName string
    Amount   float32
}

var (
    items = []Item{
        {ItemName: "Go Mug", Amount: 12.49},
        {ItemName: "Go Keychain", Amount: 6.95},
        {ItemName: "Go Tshirt", Amount: 17.99},
    }
)

func main() {
    db, err := gorm.Open("sqlite3", "/tmp/gorm.db")
    db.LogMode(true)
    if err != nil {
        log.Panic(err)
    }
    defer db.Close()

    // Migrate the schema
    db.AutoMigrate(&OrderItem{}, &Order{}, &Item{})

    // Create Items
    for index := range items {
        db.Create(&items[index])
    }
    order := Order{Status: "pending"}
    db.Create(&order)
    item1 := OrderItem{OrderID: order.ID, ItemID: items[0].ID, Quantity: 1}
    item2 := OrderItem{OrderID: order.ID, ItemID: items[1].ID, Quantity: 4}
    db.Create(&item1)
    db.Create(&item2)

    // Query with joins
    rows, err := db.Table("orders").Where("orders.id = ? and status = ?", order.ID, "pending").
        Joins("Join order_items on order_items.order_id = orders.id").
        Joins("Join items on items.id = order_items.id").
        Select("orders.id, orders.status, order_items.order_id, order_items.item_id, order_items.quantity" +
            ", items.item_name, items.amount").Rows()
    if err != nil {
        log.Panic(err)
    }

    defer rows.Close()
    // Values to load into
    newOrder := &Order{}
    newOrder.OrderItems = make([]OrderItem, 0)

    for rows.Next() {
        orderItem := OrderItem{}
        item := Item{}
        err = rows.Scan(&newOrder.ID, &newOrder.Status, &orderItem.OrderID, &orderItem.ItemID, &orderItem.Quantity, &item.ItemName, &item.Amount)
        if err != nil {
            log.Panic(err)
        }
        orderItem.Item = item
        newOrder.OrderItems = append(newOrder.OrderItems, orderItem)
    }
    log.Print(pretty.Sprint(newOrder))
}

输出:

/tmp/main.go.go:55) 
[2018-06-18 18:33:59]  [0.74ms]  INSERT INTO "items" ("created_at","updated_at","deleted_at","item_name","amount") VALUES ('2018-06-18 18:33:59','2018-06-18 18:33:59',NULL,'Go Mug','12.49')  
[1 rows affected or returned ] 

(/tmp/main.go.go:55) 
[2018-06-18 18:33:59]  [0.50ms]  INSERT INTO "items" ("created_at","updated_at","deleted_at","item_name","amount") VALUES ('2018-06-18 18:33:59','2018-06-18 18:33:59',NULL,'Go Keychain','6.95')  
[1 rows affected or returned ] 

(/tmp/main.go.go:55) 
[2018-06-18 18:33:59]  [0.65ms]  INSERT INTO "items" ("created_at","updated_at","deleted_at","item_name","amount") VALUES ('2018-06-18 18:33:59','2018-06-18 18:33:59',NULL,'Go Tshirt','17.99')  
[1 rows affected or returned ] 

(/tmp/main.go.go:58) 
[2018-06-18 18:33:59]  [0.71ms]  INSERT INTO "orders" ("created_at","updated_at","deleted_at","status") VALUES ('2018-06-18 18:33:59','2018-06-18 18:33:59',NULL,'pending')  
[1 rows affected or returned ] 

(/tmp/main.go.go:61) 
[2018-06-18 18:33:59]  [0.62ms]  INSERT INTO "order_items" ("created_at","updated_at","deleted_at","order_id","item_id","quantity") VALUES ('2018-06-18 18:33:59','2018-06-18 18:33:59',NULL,'49','145','1')  
[1 rows affected or returned ] 

(/tmp/main.go.go:62) 
[2018-06-18 18:33:59]  [0.45ms]  INSERT INTO "order_items" ("created_at","updated_at","deleted_at","order_id","item_id","quantity") VALUES ('2018-06-18 18:33:59','2018-06-18 18:33:59',NULL,'49','146','4')  
[1 rows affected or returned ] 

(/tmp/main.go.go:69) 
[2018-06-18 18:33:59]  [0.23ms]  SELECT orders.id, orders.status, order_items.order_id, order_items.item_id, order_items.quantity, items.item_name, items.amount FROM "orders" Join order_items on order_items.order_id = orders.id Join items on items.id = order_items.id WHERE (orders.id = '49' and status = 'pending')  
[0 rows affected or returned ] 
--- ONLY ONE QUERY WAS USED TO FILL THE STRUCT BELOW
2018/06/18 18:33:59 {Model:      {ID:        49,
              CreatedAt: 0001-01-01 00:00:00 +0000 UTC,
              UpdatedAt: 0001-01-01 00:00:00 +0000 UTC,
              DeletedAt: nil},
 Status:     "pending",
 OrderItems: [{Model:    {ID:        0,
                          CreatedAt: 0001-01-01 00:00:00 +0000 UTC,
                          UpdatedAt: 0001-01-01 00:00:00 +0000 UTC,
                          DeletedAt: nil},
               OrderID:  49,
               ItemID:   145,
               Item:     {Model:    {ID:        0,
                                     CreatedAt: 0001-01-01 00:00:00 +0000 UTC,
                                     UpdatedAt: 0001-01-01 00:00:00 +0000 UTC,
                                     DeletedAt: nil},
                          ItemName: "Go Mug",
                          Amount:   12.489999771118164},
               Quantity: 1},
              {Model:    {ID:        0,
                          CreatedAt: 0001-01-01 00:00:00 +0000 UTC,
                          UpdatedAt: 0001-01-01 00:00:00 +0000 UTC,
                          DeletedAt: nil},
               OrderID:  49,
               ItemID:   146,
               Item:     {Model:    {ID:        0,
                                     CreatedAt: 0001-01-01 00:00:00 +0000 UTC,
                                     UpdatedAt: 0001-01-01 00:00:00 +0000 UTC,
                                     DeletedAt: nil},
                          ItemName: "Go Keychain",
                          Amount:   6.949999809265137},
               Quantity: 4}]}

根据上面的答案,要获取订单列表,它将是:

 .....

    orders := make([]Order, 0)

    for rows.Next() {
        order := Order{}
        orderItem := OrderItem{}

        item := Item{}
        err = rows.Scan(&order.ID, &order.Status, &orderItem.OrderID, &orderItem.ItemID, &orderItem.Quantity, &item.ItemName, &item.Amount)
        if err != nil {
            log.Panic(err)
        }

        orderItem.Item = item

        order.OrderItems = append(order.OrderItems, orderItem)

        orders = append(orders, order)
    }

    log.Print(pretty.Sprint(orders))
}