Go语言,使用sqlx.StructScan扫描嵌套结构体

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

Go language, scanning embeded struct with sqlx.StructScan

问题

我刚开始学习Go语言。
我写了下面这个简单的程序。

在这里,我试图用所有的书籍和相关作者填充结构体。

Book 结构体嵌入了 Author 结构体。

package main
import (
    "fmt"
    "log"
    "time"
    "github.com/jmoiron/sqlx"
    _ "github.com/lib/pq"
)

type Book struct {
    ID      int
    Title   string
    Year    int
    Author  Author `db:"author"`
}

type Author struct {
    ID      int
    Name    string
    Dob     time.Time
}

func main() {
    db, err := sqlx.Open("postgres", "host=localhost user=testuser dbname=testdb password=testuser")
    if err != nil {
       log.Fatal("DB Conn error: ", err)
    }

    if err = db.Ping(); err != nil {
        log.Fatal("DB Ping error: ", err)
    }
    defer db.Close()

    rows, err := db.Queryx("Select b.*, a.name from books b left outer join authors a on a.ID=b.author;")
    if err != nil {
        log.Fatal("DB Query error: ", err)
    }
    defer rows.Close()

    var books []*Book
    for rows.Next() {
        var b = &Book{}
        err := rows.StructScan(b)
        if err != nil {
            log.Fatal("Scan error: ", err)
        }
        books = append(books, b)
    }

    // 打印所有的书籍
    for _, b := range books {
        fmt.Printf("%v", b)
    }
}

但是当我运行它时,出现以下错误:

[samtech@sam sqlxapp]$ go run main.go
2016/02/11 18:45:46 Scan error: missing destination name name
exit status 1

我做错了什么?

我还尝试将 Book 结构体中的字段标签更改为:

Author  Author `db:"author,prefix=auth."`

并将查询更改为:

rows, err := db.Queryx("Select b.*, auth.name from books b left outer join authors auth on auth.ID=b.author;")

但是没有任何变化。

编辑

经过几次尝试和错误,最后我让它正常工作了。

我稍微改变了我创建的模型。我将 Book 结构体从

type Book struct {
    ID      int
    Title   string
    Year    int
    Author  Author
}

改为

type Book struct {
    ID          int                  // 主键
    Title       string
    Year        int
    AuthorID    int `db:"author"`   // 外键
    Author 
}

现在,它正常工作了。我之前的错误是,我将 Author 字段添加为 Author 类型。Sqlx 无法理解它。但是当我将 Author 添加为匿名嵌入的结构体时,问题解决了。

但是它引入了另一个问题 Go语言,使用sqlx.StructScan扫描嵌套结构体

由于 BookAuthor 结构体中都存在 ID 字段,ScanStruct 在所有行中将 Book.ID 填充为 0。

有没有什么办法可以避免这个问题?

英文:

I have just started learning Go language.
I wrote following simple program.

Here i am trying to fill struct with all Books and related Authers.

Book struct has embeded Author struct.

package main
import (
    "fmt"
    "log"
    "time"
    "github.com/jmoiron/sqlx"
    _ "github.com/lib/pq"
)

type Book struct {
    ID      int
    Title   string
    Year    int
    Bauther  Auther `db:"auther"`
}

type Auther struct {
    ID      int
    Name    string
    Dob     time.Time
}

func main() {
    db, err := sqlx.Open("postgres", "host=localhost user=testuser dbname=testdb password=testuser")
    if err != nil {
       log.Fatal("DB Conn error: ", err)
    }

    if err = db.Ping(); err != nil {
        log.Fatal("DB Ping error: ", err)
    }
    defer db.Close()

    rows, err := db.Queryx("Select b.*, a.name from books b left outer join authers a on a.ID=b.auther;")
    if err != nil {
        log.Fatal("DB Query error: ", err)
    }
    defer rows.Close()

    var books []*Book
    for rows.Next() {
        var b = &Book{}
        err := rows.StructScan(b)
        if err != nil {
            log.Fatal("Scan error: ", err)
        }
        books = append(books, b)
    }

    // print all books
    for _, b := range books {
        fmt.Printf("%v", b)
    }
}

But when i run it, it is giving following error

[samtech@sam sqlxapp]$ go run main.go
2016/02/11 18:45:46 Scan error: missing destination name name
exit status 1

What i am doing wrong?

I have also tried changing field tag in Book struct to

Bauther  Auther `db:"auther,prefix=auth."`

and change query to

rows, err := db.Queryx("Select b.*, auth.name from books b left outer join authers auth on auth.ID=b.auther;")

But it doesn't make any change.

EDIT

After few try and errors, finally i make it working.

I have to slightly change the Models that i have created. I changed Book struct from

type Book struct {
    ID      int
    Title   string
    Year    int
    Bauther Auther
}

to

type Book struct {
    ID          int                  // Key
    Title       string
    Year        int
    AutherID    int `db:"auther"`   // FKey
    Auther 
}

Now, it is working fine. The mistake i was doing was, i added Bauther field as Auther. Sqlx could not understand it. But when i added Auther as anonymous embedded struct, the problem resolved.

But it introduces another problem Go语言,使用sqlx.StructScan扫描嵌套结构体

As ID field exist in Book as well as in Auther both the structs. Now ScanStruct is filling Book.ID with 0 in all rows.

Is there anything that i can do to avoid it?

答案1

得分: 5

这在目前的sqlx中似乎是不可能的。这个问题有一个未解决的问题:https://github.com/jmoiron/sqlx/issues/131

不过,你可以很容易地在不使用sqlx的情况下实现这个功能:

package main

import (
	"database/sql"
	"fmt"
	"log"
	"time"

	_ "github.com/lib/pq"
)

type Book struct {
	ID      int
	Title   string
	Year    int
	Bauther Auther
}

type Auther struct {
	ID   int
	Name string
	Dob  time.Time
}

func main() {
	db, err := sql.Open("postgres", "postgres://localhost/testdb?sslmode=disable")
	if err != nil {
		log.Fatal("DB Conn error: ", err)
	}

	if err = db.Ping(); err != nil {
		log.Fatal("DB Ping error: ", err)
	}
	defer db.Close()

	rows, err := db.Query("Select b.id, b.title, b.year, a.id, a.name, a.dob from books b left outer join authers a on a.ID=b.auther;")
	if err != nil {
		log.Fatal("DB Query error: ", err)
	}
	defer rows.Close()

	var books []*Book
	for rows.Next() {
		var b = &Book{}
		if err := rows.Scan(&b.ID, &b.Title, &b.Year, &b.Bauther.ID, &b.Bauther.Name, &b.Bauther.Dob); err != nil {
			log.Fatal(err)
		}
		books = append(books, b)
	}

	// print all books
	for _, b := range books {
		fmt.Printf("%v", b)
	}
}

基本上,你需要在SELECT语句中明确指定列,以便知道它们的顺序。然后,你创建你的结构体,并按照与SELECT语句中列的顺序相同的顺序进行扫描。

英文:

This doesn't seem possible with sqlx at the moment. There is a open issue for this: https://github.com/jmoiron/sqlx/issues/131

You can however do that easily without using sqlx:

package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/lib/pq"
)
type Book struct {
ID      int
Title   string
Year    int
Bauther Auther
}
type Auther struct {
ID   int
Name string
Dob  time.Time
}
func main() {
db, err := sql.Open("postgres", "postgres://localhost/testdb?sslmode=disable")
if err != nil {
log.Fatal("DB Conn error: ", err)
}
if err = db.Ping(); err != nil {
log.Fatal("DB Ping error: ", err)
}
defer db.Close()
rows, err := db.Query("Select b.id, b.title, b.year, a.id, a.name, a.dob from books b left outer join authers a on a.ID=b.auther;")
if err != nil {
log.Fatal("DB Query error: ", err)
}
defer rows.Close()
var books []*Book
for rows.Next() {
var b = &Book{}
if err := rows.Scan(&b.ID, &b.Title, &b.Year, &b.Bauther.ID, &b.Bauther.Name, &b.Bauther.Dob); err != nil {
log.Fatal(err)
}
books = append(books, b)
}
// print all books
for _, b := range books {
fmt.Printf("%v", b)
}
}

Basically you have to be explicit of your columns in your SELECT statement, so that you know the order of them. Then you create your struct and scan into each element with the same order as your columns in your SELECT statement.

huangapple
  • 本文由 发表于 2016年2月11日 21:57:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/35341314.html
匿名

发表评论

匿名网友

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

确定