在GO中泛化*sql.行扫描 [英] Generalizing *sql.Rows Scan in Go
本文介绍了在GO中泛化*sql.行扫描的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!
问题描述
我正在使用GO开发一个Web API,并且有很多冗余的数据库查询扫描代码。
func (m *ContractModel) WorkQuestions(cid int) ([]models.WorkQuestion, error) {
results, err := m.DB.Query(queries.WORK_QUESTIONS, cid)
if err != nil {
return nil, err
}
var workQuestions []models.WorkQuestion
for results.Next() {
var wq models.WorkQuestion
err = results.Scan(&wq.ContractStateID, &wq.QuestionID, &wq.Question, &wq.ID, &wq.Answer, &wq.Compulsory)
if err != nil {
return nil, err
}
workQuestions = append(workQuestions, wq)
}
return workQuestions, nil
}
func (m *ContractModel) Questions(cid int) ([]models.Question, error) {
results, err := m.DB.Query(queries.QUESTIONS, cid)
if err != nil {
return nil, err
}
var questions []models.Question
for results.Next() {
var q models.Question
err = results.Scan(&q.Question, &q.Answer)
if err != nil {
return nil, err
}
questions = append(questions, q)
}
return questions, nil
}
func (m *ContractModel) Documents(cid int) ([]models.Document, error) {
results, err := m.DB.Query(queries.DOCUMENTS, cid)
if err != nil {
return nil, err
}
var documents []models.Document
for results.Next() {
var d models.Document
err = results.Scan(&d.Document, &d.S3Region, &d.S3Bucket, &d.Source)
if err != nil {
return nil, err
}
documents = append(documents, d)
}
return documents, nil
}
我需要泛化这段代码,以便将结果*sql.Rows
传递给函数并获得包含扫描行的结构切片。我知道sqlx包中有一个StructScan
方法,但不能使用它,因为我有大量代码是使用GO标准数据库/SQL包编写的。
func RowsToStructs(rows *sql.Rows, model interface{}) ([]interface{}, error) {
// 1. Create a slice of structs from the passed struct type of model
// 2. Loop through each row,
// 3. Create a struct of passed mode interface{} type
// 4. Scan the row results to a slice of interface{}
// 5. Set the field values of struct created in step 3 using the slice in step 4
// 6. Add the struct created in step 3 to slice created in step 1
// 7. Return the struct slice
}
我似乎找不到一种方法来扫描作为模型参数传递的结构,并使用反射包创建它的一部分。是否有解决方法,或者我是否以错误的方式看待该问题?
Struct字段具有从结果返回的正确的COL数量和正确的顺序
推荐答案
通过将指向目标片的指针作为参数传递,可以避免在调用函数中使用类型断言。以下是修改后的RowsToStructs:
// RowsToStructs scans rows to the slice pointed to by dest.
// The slice elements must be pointers to structs with exported
// fields corresponding to the the columns in the result set.
//
// The function panics if dest is not as described above.
func RowsToStructs(rows *sql.Rows, dest interface{}) error {
// 1. Create a slice of structs from the passed struct type of model
//
// Not needed, the caller passes pointer to destination slice.
// Elem() dereferences the pointer.
//
// If you do need to create the slice in this function
// instead of using the argument, then use
// destv := reflect.MakeSlice(reflect.TypeOf(model).
destv := reflect.ValueOf(dest).Elem()
// Allocate argument slice once before the loop.
args := make([]interface{}, destv.Type().Elem().NumField())
// 2. Loop through each row
for rows.Next() {
// 3. Create a struct of passed mode interface{} type
rowp := reflect.New(destv.Type().Elem())
rowv := rowp.Elem()
// 4. Scan the row results to a slice of interface{}
// 5. Set the field values of struct created in step 3 using the slice in step 4
//
// Scan directly to the struct fields so the database
// package handles the conversion from database
// types to a Go types.
//
// The slice args is filled with pointers to struct fields.
for i := 0; i < rowv.NumField(); i++ {
args[i] = rowv.Field(i).Addr().Interface()
}
if err := rows.Scan(args...); err != nil {
return err
}
// 6. Add the struct created in step 3 to slice created in step 1
destv.Set(reflect.Append(destv, rowv))
}
return nil
}
这样称呼它:
func (m *ContractModel) Documents(cid int) ([]*models.Document, error) {
results, err := m.DB.Query(queries.DOCUMENTS, cid)
if err != nil {
return nil, err
}
defer results.Close()
var documents []*models.Document
err := RowsToStruct(results, &documents)
return documents, err
}
通过将查询移到助手函数来消除更多样板:
func QueryToStructs(dest interface{}, db *sql.DB, q string, args ...interface{}) error {
rows, err := db.Query(q, args...)
if err != nil {
return err
}
defer rows.Close()
return RowsToStructs(rows, dest)
}
这样称呼它:
func (m *ContractModel) Documents(cid int) ([]*models.Document, error) {
var documents []*model.Document
err := QueryToStructs(&documents, m.DB, queries.DOCUMENTS, cid)
return documents, err
}
这篇关于在GO中泛化*sql.行扫描的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
查看全文