跳到主要内容

特性开关

此框架提供了一系列代码生成特性,可以自行选择使用。

用法

特性开关可以通过 CLI 标志或作为参数提供给 gen 包。

CLI

go run -mod=mod entgo.io/ent/cmd/ent generate --feature privacy,entql ./ent/schema

Go

// +build ignore

package main

import (
"log"
"text/template"

"entgo.io/ent/entc"
"entgo.io/ent/entc/gen"
)

func main() {
err := entc.Generate("./schema", &gen.Config{
Features: []gen.Feature{
gen.FeaturePrivacy,
gen.FeatureEntQL,
},
Templates: []*gen.Template{
gen.MustParse(gen.NewTemplate("static").
Funcs(template.FuncMap{"title": strings.ToTitle}).
ParseFiles("template/static.tmpl")),
},
})
if err != nil {
log.Fatalf("running ent codegen: %v", err)
}
}

特性列表

Auto-Solve Merge Conflicts

The schema/snapshot option tells entc (ent codegen) to store a snapshot of the latest schema in an internal package, and use it to automatically solve merge conflicts when user's schema can't be built.

This option can be added to a project using the --feature schema/snapshot flag, but please see ent/ent/issues/852 to get more context about it.

Privacy Layer

The privacy layer allows configuring privacy policy for queries and mutations of entities in the database.

This option can be added to a project using the --feature privacy flag, and you can learn more about in the privacy documentation.

EntQL Filtering

The entql option provides a generic and dynamic filtering capability at runtime for the different query builders.

This option can be added to a project using the --feature entql flag, and you can learn more about in the privacy documentation.

Named Edges

The namedges option provides an API for preloading edges with custom names.

This option can be added to a project using the --feature namedges flag, and you can learn more about in the Eager Loading documentation.

Bidirectional Edge Refs

The bidiedges option guides Ent to set two-way references when eager-loading (O2M/O2O) edges.

This option can be added to a project using the --feature bidiedges flag.

Users that use the standard encoding/json.MarshalJSON should detach the circular references before calling json.Marshal. :::

Schema Config

The sql/schemaconfig option lets you pass alternate SQL database names to models. This is useful when your models don't all live under one database and are spread out across different schemas.

This option can be added to a project using the --feature sql/schemaconfig flag. Once you generate the code, you can now use a new option as such:

c, err := ent.Open(dialect, conn, ent.AlternateSchema(ent.SchemaConfig{
User: "usersdb",
Car: "carsdb",
}))
c.User.Query().All(ctx) // SELECT * FROM `usersdb`.`users`
c.Car.Query().All(ctx) // SELECT * FROM `carsdb`.`cars`

Row-level Locks

The sql/lock option lets configure row-level locking using the SQL SELECT ... FOR {UPDATE | SHARE} syntax.

This option can be added to a project using the --feature sql/lock flag.

tx, err := client.Tx(ctx)
if err != nil {
log.Fatal(err)
}

tx.Pet.Query().
Where(pet.Name(name)).
ForUpdate().
Only(ctx)

tx.Pet.Query().
Where(pet.ID(id)).
ForShare(
sql.WithLockTables(pet.Table),
sql.WithLockAction(sql.NoWait),
).
Only(ctx)

Custom SQL Modifiers

The sql/modifier option lets add custom SQL modifiers to the builders and mutate the statements before they are executed.

This option can be added to a project using the --feature sql/modifier flag.

Modify Example 1

client.Pet.
Query().
Modify(func(s *sql.Selector) {
s.Select("SUM(LENGTH(name))")
}).
IntX(ctx)

The above code will produce the following SQL query:

SELECT SUM(LENGTH(name)) FROM `pet`

Select and Scan Dynamic Values

If you work with SQL modifiers and need to scan dynamic values not present in your Ent schema definition, such as aggregation or custom ordering, you can apply AppendSelect/AppendSelectAs to the sql.Selector. You can later access their values using the Value method defined on each entity:

const as = "name_length"

// Query the entity with the dynamic value.
p := client.Pet.Query().
Modify(func(s *sql.Selector) {
s.AppendSelectAs("LENGTH(name)", as)
}).
FirstX(ctx)

// Read the value from the entity.
n, err := p.Value(as)
if err != nil {
log.Fatal(err)
}
fmt.Println("Name length: %d == %d", n, len(p.Name))

Modify Example 2

var p1 []struct {
ent.Pet
NameLength int `sql:"length"`
}

client.Pet.Query().
Order(ent.Asc(pet.FieldID)).
Modify(func(s *sql.Selector) {
s.AppendSelect("LENGTH(name)")
}).
ScanX(ctx, &p1)

The above code will produce the following SQL query:

SELECT `pet`.*, LENGTH(name) FROM `pet` ORDER BY `pet`.`id` ASC

Modify Example 3

var v []struct {
Count int `json:"count"`
Price int `json:"price"`
CreatedAt time.Time `json:"created_at"`
}

client.User.
Query().
Where(
user.CreatedAtGT(x),
user.CreatedAtLT(y),
).
Modify(func(s *sql.Selector) {
s.Select(
sql.As(sql.Count("*"), "count"),
sql.As(sql.Sum("price"), "price"),
sql.As("DATE(created_at)", "created_at"),
).
GroupBy("DATE(created_at)").
OrderBy(sql.Desc("DATE(created_at)"))
}).
ScanX(ctx, &v)

The above code will produce the following SQL query:

SELECT
COUNT(*) AS `count`,
SUM(`price`) AS `price`,
DATE(created_at) AS `created_at`
FROM
`users`
WHERE
`created_at` > x AND `created_at` < y
GROUP BY
DATE(created_at)
ORDER BY
DATE(created_at) DESC

Modify Example 4

var gs []struct {
ent.Group
UsersCount int `sql:"users_count"`
}

client.Group.Query().
Order(ent.Asc(group.FieldID)).
Modify(func(s *sql.Selector) {
t := sql.Table(group.UsersTable)
s.LeftJoin(t).
On(
s.C(group.FieldID),
t.C(group.UsersPrimaryKey[1]),
).
// Append the "users_count" column to the selected columns.
AppendSelect(
sql.As(sql.Count(t.C(group.UsersPrimaryKey[1])), "users_count"),
).
GroupBy(s.C(group.FieldID))
}).
ScanX(ctx, &gs)

The above code will produce the following SQL query:

SELECT
`groups`.*,
COUNT(`t1`.`group_id`) AS `users_count`
FROM
`groups` LEFT JOIN `user_groups` AS `t1`
ON
`groups`.`id` = `t1`.`group_id`
GROUP BY
`groups`.`id`
ORDER BY
`groups`.`id` ASC

Modify Example 5

client.User.Update().
Modify(func(s *sql.UpdateBuilder) {
s.Set(user.FieldName, sql.Expr(fmt.Sprintf("UPPER(%s)", user.FieldName)))
}).
ExecX(ctx)

The above code will produce the following SQL query:

UPDATE `users` SET `name` = UPPER(`name`)

Modify Example 6

client.User.Update().
Modify(func(u *sql.UpdateBuilder) {
u.Set(user.FieldID, sql.ExprFunc(func(b *sql.Builder) {
b.Ident(user.FieldID).WriteOp(sql.OpAdd).Arg(1)
}))
u.OrderBy(sql.Desc(user.FieldID))
}).
ExecX(ctx)

The above code will produce the following SQL query:

UPDATE `users` SET `id` = `id` + 1 ORDER BY `id` DESC

Modify Example 7

Append elements to the values array in a JSON column:

client.User.Update().
Modify(func(u *sql.UpdateBuilder) {
sqljson.Append(u, user.FieldTags, []string{"tag1", "tag2"}, sqljson.Path("values"))
}).
ExecX(ctx)

The above code will produce the following SQL query:

UPDATE `users` SET `tags` = CASE
WHEN (JSON_TYPE(JSON_EXTRACT(`tags`, '$.values')) IS NULL OR JSON_TYPE(JSON_EXTRACT(`tags`, '$.values')) = 'NULL')
THEN JSON_SET(`tags`, '$.values', JSON_ARRAY(?, ?))
ELSE JSON_ARRAY_APPEND(`tags`, '$.values', ?, '$.values', ?) END
WHERE `id` = ?

SQL Raw API

The sql/execquery option allows executing statements using the ExecContext/QueryContext methods of the underlying driver. For full documentation, see: DB.ExecContext, and DB.QueryContext.

// From ent.Client.
if _, err := client.ExecContext(ctx, "TRUNCATE t1"); err != nil {
return err
}

// From ent.Tx.
tx, err := client.Tx(ctx)
if err != nil {
return err
}
if err := tx.User.Create().Exec(ctx); err != nil {
return err
}
if _, err := tx.ExecContext("SAVEPOINT user_created"); err != nil {
return err
}
// ...

Upsert

The sql/upsert option lets configure upsert and bulk-upsert logic using the SQL ON CONFLICT / ON DUPLICATE KEY syntax. For full documentation, go to the Upsert API.

This option can be added to a project using the --feature sql/upsert flag.

// Use the new values that were set on create.
id, err := client.User.
Create().
SetAge(30).
SetName("Ariel").
OnConflict().
UpdateNewValues().
ID(ctx)

// In PostgreSQL, the conflict target is required.
err := client.User.
Create().
SetAge(30).
SetName("Ariel").
OnConflictColumns(user.FieldName).
UpdateNewValues().
Exec(ctx)

// Bulk upsert is also supported.
client.User.
CreateBulk(builders...).
OnConflict(
sql.ConflictWhere(...),
sql.UpdateWhere(...),
).
UpdateNewValues().
Exec(ctx)

// INSERT INTO "users" (...) VALUES ... ON CONFLICT WHERE ... DO UPDATE SET ... WHERE ...