Gorm预加载多对多关系

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

Gorm preload m2m relation

问题

我想使用gorm预加载M2M关系,但使用Preload函数时,它没有填充切片。

这是SQL模式

create table project (
  id int generated by default as identity,
  name varchar(64) not null,
  slug varchar(64) not null,
  image_url text,
  constraint project_pk primary key (id),
  constraint project_unq unique (name, slug)
);

create table donation (
  id int generated by default as identity,
  user_id varchar(36) not null,
  total_amount numeric(7, 2) not null,
  currency varchar(3) not null,
  constraint donation_pk primary key (id)
);

create table donation_detail (
  donation_id int not null,
  project_id int not null,
  amount numeric(7, 2) not null,
  primary key (donation_id, project_id)
);

这是我的gorm模型

type Donation struct {
    ID              uint64            `json:"id" gorm:"primarykey"`
	UserID          string            `json:"user_id"`
	PaypalOrderID   string            `json:"paypal_order_id"`
	TotalAmount     float64           `json:"total_amount"`
	Currency        string            `json:"currency"`
	DonationDetails []*DonationDetail `json:"donation_details" gorm:"many2many:donation_detail;"`
}

type Project struct {
    ID          uint64  `json:"id" gorm:"primarykey"`
	Name        string  `json:"name"`
	Slug        string  `json:"slug"`
	ImageURL    string  `json:"image_url"`
}

type DonationDetail struct {
	DonationID uint64  `json:"donation_id" gorm:"primaryKey"`
	ProjectID  uint64  `json:"project_id" gorm:"primaryKey"`
	Amount     float64 `json:"amount"`
	Project    Project
}

我想返回包含项目信息的捐赠和其详细信息。类似这样:

{
  "id": 1,
  "user_id": "e14a98d1-0c4a-45c3-b748-5ac6ba733b99",
  "paypal_order_id": "6SR91505YA360210R",
  "total_amount": 10000,
  "currency": "EUR",
  "donation_details": [
    {
      "donation_id": 1,
      "project_id": 1,
      "amount": 3000,
      "project": {
        "project_id": 1,
        "name": "First Project",
        "slug": "first_project",
        "image_url": "img1.jpg"
      }
    },
    {
      "donation_id": 2,
      "project_id": 2,
      "amount": 7000,
      "project": {
        "project_id": 2,
        "name": "Second Project",
        "slug": "second_project",
        "image_url": "img2.jpg"
      }
    }
  ]
}

我尝试使用以下代码预加载DonationDetails切片,但最终得到一个空切片:

func List(donationID string) (*Donation, error) {
  var d *Donation

  if err := db.Debug().Preload("DonationDetails").First(&d, donationID).Error; err != nil {
    return nil, fmt.Errorf("could not list donation details: %w", err)
  }

  return d, nil
}

SQL调试的输出似乎打印了以下3个查询:

[rows:3] SELECT * FROM "donation_detail" WHERE "donation_detail"."donation_id" = 1
[rows:0] SELECT * FROM "donation_detail" WHERE ("donation_detail"."donation_id","donation_detail"."project_id") IN (NULL)
[rows:1] SELECT * FROM "donation" WHERE "donation"."id" = '1' ORDER BY "donation"."id" LIMIT 1

注意:我在.sql文件中手动执行迁移,而不是使用Gorm Automigrate功能。也许我漏掉了一些结构标签注释,或者我完全误解了它的工作原理。

PS:我只是想做类似于以下的操作:

SELECT * FROM donation d 
 LEFT JOIN donation_detail dd on d.id = dd.donation_id 
 LEFT JOIN project p on p.id = dd.project_id

使用gorm,应该如何实现这个理想的方式?肯定有人知道。我只是想做一个愚蠢的连接,这有多难呢?

英文:

i want to preload M2M relation with gorm and it is not populating the slice with Preload function.

This is the sql schema

create table project (
  id int generated by default as identity,
  name varchar(64) not null,
  slug varchar(64) not null,
  image_url text,
  constraint project_pk primary key (id),
  constraint project_unq unique (name, slug)
);

create table donation (
  id int generated by default as identity,
  user_id varchar(36) not null,
  total_amount numeric(7, 2) not null,
  currency varchar(3) not null,
  constraint donation_pk primary key (id)
);

create table donation_detail (
  donation_id int not null,
  project_id int not null,
  amount numeric(7, 2) not null,
  primary key (donation_id, project_id)
);

These are my gorm models

type Donation struct {
    ID              uint64            `json:"id" gorm:"primarykey"`
	UserID          string            `json:"user_id"`
	PaypalOrderID   string            `json:"paypal_order_id"`
	TotalAmount     float64           `json:"total_amount"`
	Currency        string            `json:"currency"`
	DonationDetails []*DonationDetail `json:"donation_details" gorm:"many2many:donation_detail;"`
}

type Project struct {
    ID          uint64  `json:"id" gorm:"primarykey"`
	Name        string  `json:"name"`
	Slug        string  `json:"slug"`
	ImageURL    string  `json:"image_url"`
}

type DonationDetail struct {
	DonationID uint64  `json:"donation_id" gorm:"primaryKey"`
	ProjectID  uint64  `json:"project_id" gorm:"primaryKey"`
	Amount     float64 `json:"amount"`
	Project    Project
}

I want to return the donation and and it's details that include project information. something like this:

{
  "id": 1,
  "user_id": "e14a98d1-0c4a-45c3-b748-5ac6ba733b99",
  "paypal_order_id": "6SR91505YA360210R",
  "total_amount": 10000,
  "currency": "EUR",
  "donation_details": [
    {
      "donation_id": 1,
      "project_id": 1,
      "amount": 3000,
      "project": {
        "project_id": 1,
        "name": "First Project",
        "slug": "first_project",
        "image_url": "img1.jpg"
      }
    },
    {
      "donation_id": 2,
      "project_id": 2,
      "amount": 7000,
      "project": {
        "project_id": 2,
        "name": "Second Project",
        "slug": "second_project",
        "image_url": "img2.jpg"
      }
    }
  ]
}

i am trying this code to preload DonationDetails slice but it ends up being empty slice:

func List(donationID string) (*Donation, error) {
  var d *Donation

  if err := db.Debug().Preload("DonationDetails").First(&d, donationID).Error; err != nil {
    return nil, fmt.Errorf("could not list donation details: %w", err)
  }

  return d, nil
}

The output of sql debug apparently prints these 3 queries:

[rows:3] SELECT * FROM "donation_detail" WHERE "donation_detail"."donation_id" = 1
[rows:0] SELECT * FROM "donation_detail" WHERE ("donation_detail"."donation_id","donation_detail"."project_id") IN (NULL)
[rows:1] SELECT * FROM "donation" WHERE "donation"."id" = '1' ORDER BY "donation"."id" LIMIT 1

Note: I am doing migrations manually in .sql file and not using Gorm Automigrate functionality.
Maybe i am missing some struct tag annotation or am i completely misunderstanding how this works.

PS: i am just trying to do something like this

SELECT * FROM donation d 
 LEFT JOIN donation_detail dd on d.id = dd.donation_id 
 LEFT JOIN project p on p.id = dd.project_id

What is the ideal way to do this with gorm? Someone has to know. i am just trying to do a stupid join, how hard can it be.

答案1

得分: 1

有几件事情可以尝试和修复:

你可能不需要使用many2many属性来加载DonationDetail片段,因为它们只能通过DonationID加载。如果你有一个外键,你可以像这样添加它:

type Donation struct {
    ID              uint64            `json:"id" gorm:"primarykey"`
    UserID          string            `json:"user_id"`
    PaypalOrderID   string            `json:"paypal_order_id"`
    TotalAmount     float64           `json:"total_amount"`
    Currency        string            `json:"currency"`
    DonationDetails []*DonationDetail `json:"donation_details" gorm:"foreignKey:DonationID"`
} 

First方法中,你不需要使用&d,因为d已经是一个指针。然而,为了加载Project数据,你还需要进行预加载。

func List(donationID string) (*Donation, error) {
  var d *Donation

  if err := db.Debug().Preload("DonationDetails.Project").First(d, donationID).Error; err != nil {
    return nil, fmt.Errorf("无法列出捐赠详情:%w", err)
  }

  return d, nil
}
英文:

There are a couple of things to try out and fix:

You probably don't need the many2many attribute to load the DonationDetail slice, since they can be loaded only with DonationID. If you have a foreign key, you can add it like this:

type Donation struct {
    ID              uint64            `json:"id" gorm:"primarykey"`
    UserID          string            `json:"user_id"`
    PaypalOrderID   string            `json:"paypal_order_id"`
    TotalAmount     float64           `json:"total_amount"`
    Currency        string            `json:"currency"`
    DonationDetails []*DonationDetail `json:"donation_details" gorm:"foreignKey:DonationID"`
} 

You don't need &d in the First method, since d is already a pointer. However, to load the Project data, you will need to preload as well.

func List(donationID string) (*Donation, error) {
  var d *Donation

  if err := db.Debug().Preload("DonationDetails.Project").First(d, donationID).Error; err != nil {
    return nil, fmt.Errorf("could not list donation details: %w", err)
  }

  return d, nil
}

答案2

得分: 0

你可以使用Associations来实现这一点。

当你已经找到了你已经拥有的捐赠对象时,这是可以的。

var projects []Project
var donation Donation
conf.DB.First(&donation, "id = ?", donationID)
conf.DB.Model(&donation).Association("Donation").Find(&projects)
英文:

You achieve this using Associations.

This is Ok when you already found your donation object which you already had.

var projects []Project
var donation Donation
conf.DB.First(&donation, "id = ?", donationID)
conf.DB.Model(&donation).Association("Donation").Find(&projects)

huangapple
  • 本文由 发表于 2021年6月15日 20:49:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/67986609.html
匿名

发表评论

匿名网友

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

确定