アソシエーション

GENはGORMと同様にアソシエーションを自動保存します。リレーションシップ(BelongsTo/HasOne/HasMany/Many2Many)はGORMのタグを再利用します。
この機能は今のところ既存のモデルのみをサポートします。

リレーション

リレーションシップには4種類あります。

const (
HasOne RelationshipType = RelationshipType(schema.HasOne) // HasOneRel has one relationship
HasMany RelationshipType = RelationshipType(schema.HasMany) // HasManyRel has many relationships
BelongsTo RelationshipType = RelationshipType(schema.BelongsTo) // BelongsToRel belongs to relationship
Many2Many RelationshipType = RelationshipType(schema.Many2Many) // Many2ManyRel many to many relationship
)

既存モデルとの関連付け

package model

// exist model
type Customer struct {
gorm.Model
CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer"`
}

type CreditCard struct {
gorm.Model
Number string
CustomerRefer uint
}

GENはモデルのアソシエーションを検出します。

// specify model
g.ApplyBasic(model.Customer{}, model.CreditCard{})

// assoications will be detected and converted to code
package query

type customer struct {
...
CreditCards customerHasManyCreditCards
}

type creditCard struct{
...
}

データベース内のテーブルとの関連付け

アソシエーションはgen.FieldRelateで指定する必要があります。

card := g.GenerateModel("credit_cards")
customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card,
&field.RelateConfig{
// RelateSlice: true,
GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}},
}),
)

g.ApplyBasic(card, custormer)

GENは関連フィールドを持つモデルを生成します。

// customers
type Customer struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"`
CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer;references:ID" json:"credit_cards"`
}


// credit_cards
type CreditCard struct {
ID int64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"`
CustomerRefer int64 `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
}

関連モデルがすでに存在する場合、gen.FieldRelateModelはそれらの間のアソシエーションを構築するのに役立ちます。

customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
&field.RelateConfig{
// RelateSlice: true,
GORMTag: field.GormTag{"foreignKey": []string{"CustomerRefer"},"references": []string{"ID"}},
}),
)

g.ApplyBasic(custormer)

関連設定

type RelateConfig struct {
// specify field's type
RelatePointer bool // ex: CreditCard *CreditCard
RelateSlice bool // ex: CreditCards []CreditCard
RelateSlicePointer bool // ex: CreditCards []*CreditCard

JSONTag string // related field's JSON tag
GORMTag string // related field's GORM tag
NewTag string // related field's new tag
OverwriteTag string // related field's tag
}

操作

自動作成/更新のスキップ

user := model.User{
Name: "modi",
BillingAddress: Address{Address1: "Billing Address - Address 1"},
ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
Emails: []Email{
{Email: "modi@example.com"},
{Email: "modi-2@example.com"},
},
Languages: []Language{
{Name: "ZH"},
{Name: "EN"},
},
}

u := query.Use(db).User

u.WithContext(ctx).Select(u.Name).Create(&user)
// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

u.WithContext(ctx).Omit(u.BillingAddress.Field()).Create(&user)
// Skip create BillingAddress when creating a user

u.WithContext(ctx).Omit(u.BillingAddress.Field("Address1")).Create(&user)
// Skip create BillingAddress.Address1 when creating a user

u.WithContext(ctx).Omit(field.AssociationFields).Create(&user)
// Skip all associations when creating a user

メソッドFieldは、一連のフィールド名を''で結合します。例:u.BillingAddress.Field("Address1", "Street")
これはBillingAddress.Address1.Streetと同じです。

アソシエーションの検索

一致するアソシエーションを検索します。

u := query.Use(db).User

languages, err = u.Languages.Model(&user).Find()

条件付きでアソシエーションを検索します。

q := query.Use(db)
u := q.User

languages, err = u.Languages.Where(q.Language.Name.In([]string{"ZH","EN"})).Model(&user).Find()

アソシエーションの追加

多対多一対多の場合、新しいアソシエーションを追加します。一対一所属の場合、現在のアソシエーションを置き換えます。

u := query.Use(db).User

u.Languages.Model(&user).Append(&languageZH, &languageEN)

u.Languages.Model(&user).Append(&Language{Name: "DE"})

u.CreditCards.Model(&user).Append(&CreditCard{Number: "411111111111"})

アソシエーションの置換

現在のアソシエーションを新しいものと置き換えます。

u.Languages.Model(&user).Replace(&languageZH, &languageEN)

アソシエーションの削除

ソースと引数の間の関係が存在する場合、それを削除します。参照のみを削除し、DBからこれらのオブジェクトを削除しません。
DB。

u := query.Use(db).User

u.Languages.Model(&user).Delete(&languageZH, &languageEN)

u.Languages.Model(&user).Delete([]*Language{&languageZH, &languageEN}...)

アソシエーションのクリア

ソースとアソシエーション間のすべての参照を削除します。アソシエーション自体は削除しません。

u.Languages.Model(&user).Clear()

アソシエーションのカウント

現在のアソシエーションの数を返します。

u.Languages.Model(&user).Count()

選択による削除

レコードを削除する際に、Selectを使用して選択された一対一/一対多/多対多の関係を削除できます。
例:

u := query.Use(db).User

// delete user's account when deleting user
u.Select(u.Account).Delete(&user)

// delete user's Orders, CreditCards relations when deleting user
db.Select(u.Orders.Field(), u.CreditCards.Field()).Delete(&user)

// delete user's has one/many/many2many relations when deleting user
db.Select(field.AssociationFields).Delete(&user)

プリローディング

この機能は今のところ既存のモデルのみをサポートします。

プリロード

GENは、他のSQLでのリレーションシップの遅延ロードをPreloadで許可します。例:

type User struct {
gorm.Model
Username string
Orders []Order
}

type Order struct {
gorm.Model
UserID uint
Price float64
}

q := query.Use(db)
u := q.User
o := q.Order

// Preload Orders when find users
users, err := u.WithContext(ctx).Preload(u.Orders).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4);

users, err := u.WithContext(ctx).Preload(u.Orders).Preload(u.Profile).Preload(u.Role).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4); // has many
// SELECT * FROM profiles WHERE user_id IN (1,2,3,4); // has one
// SELECT * FROM roles WHERE id IN (4,5,6); // belongs to

すべてをプリロード

clause.Associationsは、作成/更新時にSelectと同様にPreloadと連携できます。これを使用して、すべてのアソシエーションをPreloadできます。
例:

type User struct {
gorm.Model
Name string
CompanyID uint
Company Company
Role Role
Orders []Order
}

users, err := u.WithContext(ctx).Preload(field.Associations).Find()

clause.Associationsはネストされたアソシエーションをプリロードしませんが、ネストされたプリローディングと一緒に使用できます。
例:

users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find()

すべてのアソシエーションにソフトデリートされたレコードを含めるには、リレーションスコープfield.RelationFieldUnscopedを使用します。例:

users, err := u.WithContext(ctx).Preload(field.Associations.Scopes(field.RelationFieldUnscoped)).Find()

選択によるプリロード

メソッドSelectで選択した列を指定します。外部キーは選択する必要があります。

type User struct {
gorm.Model
CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
}

type CreditCard struct {
gorm.Model
Number string
UserRefer uint
}

u := q.User
cc := q.CreditCard

// !!! Foregin key "cc.UserRefer" must be selected
users, err := u.WithContext(ctx).Where(c.ID.Eq(1)).Preload(u.CreditCards.Select(cc.Number, cc.UserRefer)).Find()
// SELECT * FROM `credit_cards` WHERE `credit_cards`.`customer_refer` = 1 AND `credit_cards`.`deleted_at` IS NULL
// SELECT * FROM `customers` WHERE `customers`.`id` = 1 AND `customers`.`deleted_at` IS NULL LIMIT 1

条件付きプリロード

GENは、条件付きでアソシエーションをプリロードできます。これはインライン条件と同様に機能します。

q := query.Use(db)
u := q.User
o := q.Order

// Preload Orders with conditions
users, err := u.WithContext(ctx).Preload(u.Orders.On(o.State.NotIn("cancelled")).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4) AND state NOT IN ('cancelled');

users, err := u.WithContext(ctx).Where(u.State.Eq("active")).Preload(u.Orders.On(o.State.NotIn("cancelled")).Find()
// SELECT * FROM users WHERE state = 'active';
// SELECT * FROM orders WHERE user_id IN (1,2) AND state NOT IN ('cancelled');

users, err := u.WithContext(ctx).Preload(u.Orders.Order(o.ID.Desc(), o.CreateTime).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2) Order By id DESC, create_time;

users, err := u.WithContext(ctx).Preload(u.Orders.On(o.State.Eq("on")).Order(o.ID.Desc()).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2) AND state = "on" Order By id DESC;

users, err := u.WithContext(ctx).Preload(u.Orders.Clauses(hints.UseIndex("idx_order_id"))).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2) USE INDEX (`idx_order_id`);

user, err := u.WithContext(ctx).Where(u.ID.Eq(1)).Preload(u.Orders.Offset(100).Limit(20)).Take()
// SELECT * FROM users WHERE `user_id` = 1 LIMIT 20 OFFSET 100;
// SELECT * FROM `users` WHERE `users`.`id` = 1 LIMIT 1

ネストされたプリローディング

GENはネストされたプリローディングをサポートしています。例:

db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users)

// Customize Preload conditions for `Orders`
// And GEN won't preload unmatched order's OrderItems then
db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users)

プラチナスポンサー

ゴールドスポンサー

プラチナスポンサー

ゴールドスポンサー