如何在创建新对象后返回嵌套实体?

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

How to return nested entities after creating a new object?

问题

模型Account包含嵌套结构-CurrencyUser

当我在数据库中创建Account的新实例,并在响应中返回它时,嵌套实体为空:


type Account struct {
    BaseModel
    Name       string          `gorm:"size:64;not null" json:"name"`
    Balance    decimal.Decimal `gorm:"type:decimal(16, 2);default:0;not null;" json:"balance"`
    UserID     int             `gorm:"not null" json:"-"`
    User       User            `gorm:"foreignKey:UserID" json:"user"`
    CurrencyID int             `gorm:"not null" json:"-"`
    Currency   Currency        `gorm:"foreignKey:CurrencyID" json:"currency"`
}

type CreateAccountBody struct {
    Name       string          `json:"name" binding:"required"`
    Balance    decimal.Decimal `json:"balance"`
    CurrencyID int             `json:"currency_id" binding:"required"`
}

func CreateAccount(ctx *gin.Context) {
    body := CreateAccountBody{}

    if err := ctx.Bind(&body); err != nil {
        log.Println("Error while binding body:", err)
        ctx.JSON(
            http.StatusBadRequest,
            gin.H{"error": "Wrong request parameters"},
        )
        return
    }

    account := Account{
        Name:       body.Name,
        Balance:    body.Balance,
        CurrencyID: body.CurrencyID,
        UserID:     1,
    }

    if result := db.DB.Create(&account); result.Error != nil {
        log.Println("Unable to create an account:", result.Error)
    }

    ctx.JSON(http.StatusCreated, gin.H{"data": account})
}


为了避免这个问题,我使用单独的查询刷新account变量:

db.DB.Create(&account)
db.DB.Preload("User").Preload("Currency").Find(&account, account.ID)
ctx.JSON(http.StatusCreated, gin.H{"data": account})

这是实现所需结果的最有效和正确的方法吗?

英文:

Model Account contains nested structures - Currency and User

When I create a new instance of Account in DB, and then return it in my response, nested entities are empty:


type Account struct {
	BaseModel
	Name       string          `gorm:"size:64;not null" json:"name"`
	Balance    decimal.Decimal `gorm:"type:decimal(16, 2);default:0;not null;" json:"balance"`
	UserID     int             `gorm:"not null" json:"-"`
	User       User            `gorm:"foreignKey:UserID" json:"user"`
	CurrencyID int             `gorm:"not null" json:"-"`
	Currency   Currency        `gorm:"foreignKey:CurrencyID" json:"currency"`
}

type CreateAccountBody struct {
	Name       string          `json:"name" binding:"required"`
	Balance    decimal.Decimal `json:"balance"`
	CurrencyID int             `json:"currency_id" binding:"required"`
}

func CreateAccount(ctx *gin.Context) {
    body := CreateAccountBody{}

	if err := ctx.Bind(&body); err != nil {
		log.Println("Error while binding body:", err)
		ctx.JSON(
			http.StatusBadRequest,
			gin.H{"error": "Wrong request parameters"},
		)
		return
	}

    account := Account {
		Name:       body.Name,
		Balance:    body.Balance,
		CurrencyID: body.CurrencyID,
		UserID:     1,
	}
    
    if result := db.DB.Create(&account); result.Error != nil {
		log.Println("Unable to create an account:", result.Error)
	}    

    ctx.JSON(http.StatusCreated, gin.H{"data": account})
}


To avoid this problem, I refresh account variable with separate query:

db.DB.Create(&account)
db.DB.Preload("User").Preload("Currency").Find(&account, account.ID)
ctx.JSON(http.StatusCreated, gin.H{"data": account})

Is this the most effective and correct way to achieve the desired result?

答案1

得分: 1

我将为您翻译以下内容:

我将与您分享通常我是如何处理这种情况的。首先,让我分享一下代码。

main.go 文件

package main

import (
	"context"
	"gogindemo/handlers"
	"github.com/gin-gonic/gin"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

var (
	db  *gorm.DB
	ctx *gin.Context
)

func init() {
	dsn := "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable"
	var err error
	db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}

	db.AutoMigrate(&handlers.Currency{})
	db.AutoMigrate(&handlers.User{})
	db.AutoMigrate(&handlers.Account{})
}

func AddDb() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		ctx.Request = ctx.Request.WithContext(context.WithValue(ctx.Request.Context(), "DB", db))
		ctx.Next()
	}
}

func main() {
	db.Create(&handlers.User{Id: 1, Name: "john doe"})
	db.Create(&handlers.User{Id: 2, Name: "mary hut"})
	db.Create(&handlers.Currency{Id: 1, Name: "EUR"})
	db.Create(&handlers.Currency{Id: 2, Name: "USD"})

	r := gin.Default()
	r.POST("/account", AddDb(), handlers.CreateAccount)

	r.Run()
}

在这里,我刚刚添加了用于引导数据库对象并向其添加一些虚拟数据的代码。

handlers/handlers.go 文件

package handlers

import (
	"net/http"
	"github.com/gin-gonic/gin"
	"github.com/shopspring/decimal"
	"gorm.io/gorm"
)

type User struct {
	Id   int
	Name string
}

type Currency struct {
	Id   int
	Name string
}

type Account struct {
	Id         int
	Name       string          `gorm:"size:64;not null" json:"name"`
	Balance    decimal.Decimal `gorm:"type:decimal(16, 2);default:0;not null;" json:"balance"`
	UserID     int             `gorm:"not null" json:"-"`
	User       User            `gorm:"foreignKey:UserID" json:"user"`
	CurrencyID int             `gorm:"not null" json:"-"`
	Currency   Currency        `gorm:"foreignKey:CurrencyID" json:"currency"`
}

type CreateAccountBody struct {
	Name       string          `json:"name" binding:"required"`
	Balance    decimal.Decimal `json:"balance"`
	CurrencyID int             `json:"currency_id" binding:"required"`
}

func CreateAccount(c *gin.Context) {
	db, ok := c.Request.Context().Value("DB").(*gorm.DB)
	if !ok {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
		return
	}
	var accountReq CreateAccountBody
	if err := c.BindJSON(&accountReq); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "wrong request body payload"})
		return
	}

	// 创建账户并更新 "account" 变量
	account := Account{Name: accountReq.Name, Balance: accountReq.Balance, CurrencyID: accountReq.CurrencyID, UserID: 1}
	db.Create(&account).Preload("Currency").Preload("User").Find(&account, account.Id)

	c.IndentedJSON(http.StatusCreated, account)
}

在这个文件中,我通过传递给上下文的 DB 与数据库进行交互。现在,回到您的问题。

如果 Currency/AccountUser/Account 之间的关系是 1:1 类型,那么您应该依赖于 Preload 子句。这将在单独的查询中加载相关实体,而不是将其添加到 INNER JOIN 子句中。

如果这解决了您的问题,请告诉我,谢谢!

英文:

I'm gonna share you how usually I managed this scenario. First, let me share the code.

main.go file

package main

import (
	"context"

	"gogindemo/handlers"

	"github.com/gin-gonic/gin"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

var (
	db  *gorm.DB
	ctx *gin.Context
)

func init() {
	dsn := "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable"
	var err error
	db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}

	db.AutoMigrate(&handlers.Currency{})
	db.AutoMigrate(&handlers.User{})
	db.AutoMigrate(&handlers.Account{})
}

func AddDb() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		ctx.Request = ctx.Request.WithContext(context.WithValue(ctx.Request.Context(), "DB", db))
		ctx.Next()
	}
}

func main() {
	db.Create(&handlers.User{Id: 1, Name: "john doe"})
	db.Create(&handlers.User{Id: 2, Name: "mary hut"})
	db.Create(&handlers.Currency{Id: 1, Name: "EUR"})
	db.Create(&handlers.Currency{Id: 2, Name: "USD"})

	r := gin.Default()
	r.POST("/account", AddDb(), handlers.CreateAccount)

	r.Run()
}

Here, I've just added the code for bootstrapping the database objects and add some dummy data to it.

handlers/handlers.go file

package handlers

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/shopspring/decimal"
	"gorm.io/gorm"
)

type User struct {
	Id   int
	Name string
}

type Currency struct {
	Id   int
	Name string
}

type Account struct {
	Id         int
	Name       string          `gorm:"size:64;not null" json:"name"`
	Balance    decimal.Decimal `gorm:"type:decimal(16, 2);default:0;not null;" json:"balance"`
	UserID     int             `gorm:"not null" json:"-"`
	User       User            `gorm:"foreignKey:UserID" json:"user"`
	CurrencyID int             `gorm:"not null" json:"-"`
	Currency   Currency        `gorm:"foreignKey:CurrencyID" json:"currency"`
}

type CreateAccountBody struct {
	Name       string          `json:"name" binding:"required"`
	Balance    decimal.Decimal `json:"balance"`
	CurrencyID int             `json:"currency_id" binding:"required"`
}

func CreateAccount(c *gin.Context) {
	db, ok := c.Request.Context().Value("DB").(*gorm.DB)
	if !ok {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
		return
	}
	var accountReq CreateAccountBody
	if err := c.BindJSON(&accountReq); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "wrong request body payload"})
		return
	}

	// create Account & update the "account" variable
	account := Account{Name: accountReq.Name, Balance: accountReq.Balance, CurrencyID: accountReq.CurrencyID, UserID: 1}
	db.Create(&account).Preload("Currency").Preload("User").Find(&account, account.Id)

	c.IndentedJSON(http.StatusCreated, account)
}

Within this file, I actually talk with the database through the DB passed in the context. Now, back to your question.
If the relationship between the Currency/Account and User/Account is of type 1:1, then, you should rely on the Preload clause. This will load the related entity in a separate query instead of adding it in an INNER JOIN clause.

Let me know if this solves your issue, thanks!

huangapple
  • 本文由 发表于 2023年2月5日 07:40:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/75349134.html
匿名

发表评论

匿名网友

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

确定