GORM多表关联查询中BrandID/BrandName字段映射失败的替代方案
问题:GORM查询结果无法映射自定义字段到结构体,避免存入数据库
背景
我有products表,与items表、brands表均为一对多关系;brands表与items表同样是一对多关系。按product_id和brand_id分组查询products数据时,原生SQL执行结果符合预期,但Product结构体中的BrandID和BrandName字段始终为nil。
模型定义(省略部分字段)
type Brand struct { ID int `json:"id" gorm:"primaryKey"` Name string `json:"name" gorm:"index;not null;type:varchar(50);default:null"` ProductID int `json:"productId"` Product *Product `json:"product" gorm:"foreignKey:ProductID;"` } type Product struct { ID int `json:"id" gorm:"primaryKey"` Name string `json:"name" gorm:"index;not null;type:varchar(50);default:null"` StoreID *int `json:"storeId"` Store *Store `json:"store" gorm:"foreignKey:StoreID;constraint:OnUpdate:RESTRICT,OnDelete:RESTRICT;"` BrandID *int `json:"brandId" gorm:"-"` // 当前标签 BrandName *string `json:"brandName" gorm:"-"` // 当前标签 Brands []*Brand `json:"brands" gorm:"constraint:OnUpdate:CASCADE,OnDelete:RESTRICT;"` } type Item struct { ID int `json:"id" gorm:"primaryKey"` Quantity int `json:"quantity" gorm:"type:integer;not null;unsigned;"` ProductID int `json:"productId"` Product *Product `json:"product" gorm:"foreignKey:ProductID;not null;constraint:OnUpdate:RESTRICT,OnDelete:CASCADE;"` BrandID *int `json:"brandId"` Brand *Brand `json:"brand" gorm:"foreignKey:BrandID;constraint:OnUpdate:RESTRICT,OnDelete:CASCADE;"` }
查询代码
var products []*model.Product var result *gorm.DB query := DB.Table("products"). Where(&model.Product{StoreID: &StoreID}). Joins("INNER JOIN items ON items.product_id = products.id"). Joins("LEFT JOIN brands ON brands.id = items.brand_id"). Where("items.quantity > 0"). Group("products.id, brands.id"). Select("products.*,brands.id AS brand_id, brands.name AS brand_name") if err := query.Find(&products).Error; err != nil { panic(err) } // 结果中BrandID和BrandName始终为nil fmt.Printf("result %+v\n", products)
GORM生成的原生SQL(执行结果符合预期)
SELECT products.*,brands.id AS brand_id, brands.name AS brand_name FROM "products" INNER JOIN items ON items.product_id = products.id LEFT JOIN brands ON brands.id = items.brand_id WHERE "products"."store_id" = 2 AND items.quantity > 0 GROUP BY products.id, brands.id
当前尝试方案的弊端
曾尝试将GORM标签从-改为->:
BrandID *int `json:"brandId" gorm:"->"` BrandName *string `json:"brandName" gorm:"->"`
但该设置会导致这两个字段被纳入数据库写入操作(INSERT/UPDATE),不符合需求。
替代解决方案
方案1:使用只读标签明确映射
修改Product结构体的字段标签,指定列名并设置只读属性:
BrandID *int `json:"brandId" gorm:"column:brand_id;->"` BrandName *string `json:"brandName" gorm:"column:brand_name;->"`
->表示该字段仅从数据库读取映射,不会在写入操作中被包含,既解决了映射问题,又避免了存入数据库。
方案2:使用自定义查询结构体
创建包含所需字段的临时结构体,查询后转换为原Product类型:
// 定义临时结构体,组合原Product和额外字段 type ProductWithBrand struct { model.Product BrandID *int `json:"brandId"` BrandName *string `json:"brandName"` } var productsWithBrand []*ProductWithBrand if err := query.Find(&productsWithBrand).Error; err != nil { panic(err) } // 转换为原Product切片 var products []*model.Product for _, pwb := range productsWithBrand { p := pwb.Product p.BrandID = pwb.BrandID p.BrandName = pwb.BrandName products = append(products, &p) }
此方法无需修改原模型标签,保持原模型的数据库行为不变。
方案3:手动扫描结果
通过Rows()获取原始结果集,手动扫描字段并赋值:
rows, err := query.Rows() if err != nil { panic(err) } defer rows.Close() var products []*model.Product for rows.Next() { var p model.Product var brandID *int var brandName *string // 先扫描Product的基础字段 if err := DB.ScanRows(rows, &p); err != nil { panic(err) } // 再扫描额外的brand_id和brand_name(需与SELECT语句字段顺序一致) if err := rows.Scan( &p.ID, &p.Name, &p.StoreID, /* 其他Product字段... */, &brandID, &brandName, ); err != nil { panic(err) } p.BrandID = brandID p.BrandName = brandName products = append(products, &p) }
此方法适合字段较少的场景,需要严格对应SELECT语句的字段顺序。
内容的提问来源于stack exchange,提问作者Kateile
相关产品推荐
相关产品推荐

