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
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.
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
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 }
NOTEfield.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
// 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).