Associations

GEN will auto-save associations as GORM do. The relationships (BelongsTo/HasOne/HasMany/Many2Many) reuse GORM’s tag.
This feature only support exist model for now.

Relation

There are 4 kind of relationship.

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
)

Relate to exist model

package model

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

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

GEN will detect model’s associations:

// 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{
...
}

Relate to table in database

The association have to be specified by 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, customer)

GEN will generate models with associated field:

// 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"`
}

If associated model already exists, gen.FieldRelateModel can help you build associations between them.

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(customer)

Relate Config

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 GormTag // related field's GORM tag, e.g. field.GormTag{"foreignKey": []string{"CustomerRefer"}}
Tag Tag // extra struct tags appended to the generated field
OverwriteTag Tag // when non-nil, replaces all generated tags
}

Without RelatePointer/RelateSlice/RelateSlicePointer, the field shape follows
the relationship: HasMany and Many2Many default to slices, HasOne/BelongsTo
to plain values.

Operation

Skip Auto Create/Update

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 ("modi");

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

Method Field will join a serious field name with ‘’, for example: u.BillingAddress.Field("Address1", "Street")
equals to BillingAddress.Address1.Street

Find Associations

Find matched associations

u := query.Use(db).User

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

Find associations with conditions

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

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

Append Associations

Append new associations for many to many, has many, replace current association for has one, belongs to

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"})

Replace Associations

Replace current associations with new ones

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

Delete Associations

Remove the relationship between source & arguments if exists, only delete the reference, won’t delete those objects from
DB.

u := query.Use(db).User

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

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

Clear Associations

Remove all reference between source & association, won’t delete those associations

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

Count Associations

Return the count of current associations

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

Delete with Select

You are allowed to delete selected has one/has many/many2many relations with Select when deleting records, for
example:

u := query.Use(db).User

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

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

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

Association operations run through Model(...): Find, Append, Replace,
Delete, Clear, Count — each also has an Unscoped() variant; before Model,
the relation chain additionally accepts Where, WithContext, Session and
Unscoped.

Preloading

This feature only support exist model for now.

Preload

GEN allows eager loading relations in other SQL with Preload, for example:

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

Preload All

field.Associations can work with Preload similar like Select when creating/updating, you can use it to Preload
all associations, for example:

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

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

field.Associations won’t preload nested associations, but you can use it with Nested Preloading
together, e.g:

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

To include soft deleted records in all associations use relation scope field.RelationFieldUnscoped, e.g:

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

NOTE field.Associations (a RelationField, for Preload) and
field.AssociationFields (an expression, for Select/Omit) are two different
helpers. Preload(field.Associations) preloads every first-level association;
Select(field.AssociationFields) / Omit(field.AssociationFields) include or
skip associations when creating/updating/deleting.

Preload with select

Specify selected columns with method Select. Foreign key must be selected.

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

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

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

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

Preload with conditions

GEN allows Preload associations with conditions, it works similar to Inline Conditions.

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

Nested Preloading

GEN supports nested preloading, for example:

u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find()

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

Relation Modifiers

Association fields (u.Orders, u.Account, …) are field.RelationField values.
Besides the modifiers shown above, a relation field supports the query-shaped
modifiers you already know from the chain API:

u.WithContext(ctx).
Preload(u.Orders.
On(o.State.Eq("paid")). // conditions on the association query
Select(o.ID, o.UserID, o.Amount). // selected columns (keep the FK!)
Order(o.Amount.Desc()). // ordering
Clauses(hints.UseIndex("idx_order_user")). // hint clauses
Offset(100).Limit(20). // pagination
Scopes(field.RelationFieldUnscoped), // include soft-deleted rows
).
Find()

Join/LeftJoin/RightJoin on a relation field attach an extra JOIN to the
association query
(Join(table, on...) — the joined table plus ON conditions):
the association is still loaded by GORM’s second query, but that query gains the
JOIN (useful for join tables or filtering on a joined table):

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

// preloaded Orders are loaded with a LEFT JOIN on order_items
users, err := u.WithContext(ctx).
Preload(u.Orders.LeftJoin(oi, oi.OrderID.EqCol(o.ID))).
Find()

Separately, the DAO-level Joins method performs single-query (single JOIN)
eager loading
of one-to-one relations — it does not go through Preload:

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

// load the has-one Account in the same SELECT via JOIN, with a condition
user, err := u.WithContext(ctx).
Joins(u.Account.On(a.Name.Eq("modi-account"))).
Take()

Like GORM’s Joins preloading, join-based loading suits one-to-one relations
(has one / belongs to); for has-many and many2many associations use Preload
(with the relation modifiers above, including relation Join when the
association query needs extra tables).

Platinum Sponsors

Become a Sponsor!

Platinum Sponsors

Become a Sponsor!