字段(Fields)
快速摘要
模式中的字段(或属性)是节点的属性。例如:一个 User 具有四个字段 age、name、username 和 created_at:

字段通过 Fields 方法从模式中返回。例如:
package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
// User schema.
type User struct {
ent.Schema
}
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("age"),
field.String("name"),
field.String("username").
Unique(),
field.Time("created_at").
Default(time.Now),
}
}
所有字段默认均为必填项,可通过 Optional 方法将其设置为可选项。
类型(Types)
以下是框架当前支持的类型:
- 所有的 Go 语言数字类型。例如
int,uint8,float64等。 boolstringtime.TimeUUID[]byte(仅限SQL)JSON(仅限SQL)Enum(仅限SQL)Other(仅限SQL)
package schema
import (
"time"
"net/url"
"github.com/google/uuid"
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
// User schema.
type User struct {
ent.Schema
}
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("age").
Positive(),
field.Float("rank").
Optional(),
field.Bool("active").
Default(false),
field.String("name").
Unique(),
field.Time("created_at").
Default(time.Now),
field.JSON("url", &url.URL{}).
Optional(),
field.JSON("strings", []string{}).
Optional(),
field.Enum("state").
Values("on", "off").
Optional(),
field.UUID("uuid", uuid.UUID{}).
Default(uuid.New),
}
}
若需了解每种类型与数据库中的类型如何映射,可以参考 迁移 章节。
ID 字段
id 字段是模式内置的,无需特意声明。在基于SQL的数据库中默认类型是 int (可以通过 代码生成选项 修改 ) 且在数据库中自增。
为使 id 字段在所有表中都唯一,可以在运行模式迁移时使用 WithGlobalUniqueID 选项。
如果需要不同以上的 id 字段配置,或 id 的值需要由应用程序在创建实体时提供(例如 UUID),可以覆盖内置 id 配置,如下:
// Fields of the Group.
func (Group) Fields() []ent.Field {
return []ent.Field{
field.Int("id").
StructTag(`json:"oid,omitempty"`),
}
}
// Fields of the Blob.
func (Blob) Fields() []ent.Field {
return []ent.Field{
field.UUID("id", uuid.UUID{}).
Default(uuid.New).
StorageKey("oid"),
}
}
// Fields of the Pet.
func (Pet) Fields() []ent.Field {
return []ent.Field{
field.String("id").
MaxLen(25).
NotEmpty().
Unique().
Immutable(),
}
}
如果你需要设置自定义的函数来生成 ID,可以使用 DefaultFunc 来指定函数,这会在资源每次创建时运行。查阅 相关问题 获取更多信息。
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int64("id").
DefaultFunc(func() int64 {
// An example of a dumb ID generator - use a production-ready alternative instead.
return time.Now().Unix() << 8 | atomic.AddInt64(&counter, 1) % 256
}),
}
}
数据库类型
每种数据库方言都有自己由GO语言类型到数据库类型的映射。例如 MySQL 方言在数据库中将 float64 映射为 double 列。因此可以通过 SchemaType 方法覆盖默认行为。
package schema
import (
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/schema/field"
)
// Card schema.
type Card struct {
ent.Schema
}
// Fields of the Card.
func (Card) Fields() []ent.Field {
return []ent.Field{
field.Float("amount").
SchemaType(map[string]string{
dialect.MySQL: "decimal(6,2)", // Override MySQL.
dialect.Postgres: "numeric", // Override Postgres.
}),
}
}
Go 语言类型
字段的默认类型都是 Go 语言基础类型。例如字符串字段类型是 string ,时间字段类型是 time.Time 。 GoType 方法可以将默认的 ent 类型覆盖为自定义类型。
自定义类型必须可以转化为 Go 语言基本类型,即实现了 ValueScanner 接口,或是一个 外部的 ValueScanner。此外,如果提供的类型实现了Validator接口且未设置任何验证器,则将使用该类型的验证器。
package schema
import (
"database/sql"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/schema/field"
"github.com/shopspring/decimal"
)
// Amount is a custom Go type that's convertible to the basic float64 type.
type Amount float64
// Card schema.
type Card struct {
ent.Schema
}
// Fields of the Card.
func (Card) Fields() []ent.Field {
return []ent.Field{
field.Float("amount").
GoType(Amount(0)),
field.String("name").
Optional().
// A ValueScanner type.
GoType(&sql.NullString{}),
field.Enum("role").
// A convertible type to string.
GoType(role.Role("")),
field.Float("decimal").
// A ValueScanner type mixed with SchemaType.
GoType(decimal.Decimal{}).
SchemaType(map[string]string{
dialect.MySQL: "decimal(6,2)",
dialect.Postgres: "numeric",
}),
}
}
外部的 ValueScanner
Ent 允许为基础和自定义的 Go 语言类型添加自定义 ValueScanner。这使得在不实现 ValueScanner 接口的情况下,既能使用标准模式字段,又能控制它们在数据库中的存储方式。此外,此选项允许用户使用未实现 ValueScanner 的 GoType,例如 *url.URL。
现阶段此选项只对文字和数据字段有效,其他类型在未来版本中将会扩展。
- TextMarshaller
- BinaryMarshaller
- Functions based
- Custom
实现了 encoding.TextMarshaller 和 encoding.TextUnmarshaller 接口的自定义 Go 语言类型可以使用 field.TextValueScanner 作为 ValueScanner。ValueScanner 调用 MarshalText 和 UnmarshalText 在数据库中读写字段值:
field.String("big_int").
GoType(&big.Int{}).
ValueScanner(field.TextValueScanner[*big.Int]{})
实现了 encoding.BinaryMarshaller 和 encoding.BinaryUnmarshaller 接口的自定义 Go 语言类型可以使用 field.BinaryValueScanner 作为 ValueScanner。ValueScanner 调用 MarshalBinary 和 UnmarshalBinary 在数据库中读写字段值:
field.String("url").
GoType(&url.URL{}).
ValueScanner(field.BinaryValueScanner[*url.URL]{})
field.ValueScannerFunc 允许设置两个函数写数据库的值:V 是 driver.Value, S 是 sql.Scanner :
field.String("encoded").
ValueScanner(field.ValueScannerFunc[string, *sql.NullString]{
V: func(s string) (driver.Value, error) {
return base64.StdEncoding.EncodeToString([]byte(s)), nil
},
S: func(ns *sql.NullString) (string, error) {
if !ns.Valid {
return "", nil
}
b, err := base64.StdEncoding.DecodeString(ns.String)
if err != nil {
return "", err
}
return string(b), nil
},
})
field.String("prefixed").
ValueScanner(PrefixedHex{
prefix: "0x",
})
// PrefixedHex is a custom type that implements the TypeValueScanner interface.
type PrefixedHex struct {
prefix string
}
// Value implements the TypeValueScanner.Value method.
func (p PrefixedHex) Value(s string) (driver.Value, error) {
return p.prefix + ":" + hex.EncodeToString([]byte(s)), nil
}
// ScanValue implements the TypeValueScanner.ScanValue method.
func (PrefixedHex) ScanValue() field.ValueScanner {
return &sql.NullString{}
}
// FromValue implements the TypeValueScanner.FromValue method.
func (p PrefixedHex) FromValue(v driver.Value) (string, error) {
s, ok := v.(*sql.NullString)
if !ok {
return "", fmt.Errorf("unexpected input for FromValue: %T", v)
}
if !s.Valid {
return "", nil
}
d, err := hex.DecodeString(strings.TrimPrefix(s.String, p.prefix+":"))
if err != nil {
return "", err
}
return string(d), nil
}
其他字段
其他表示不适合任何标准字段类型的字段。例如 Postgres 数据库的范围类型或地理空间类型。
package schema
import (
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/schema/field"
"github.com/jackc/pgtype"
)
// User schema.
type User struct {
ent.Schema
}
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Other("duration", &pgtype.Tstzrange{}).
SchemaType(map[string]string{
dialect.Postgres: "tstzrange",
}),
}
}
默认值
Non-unique 字段使用 Default 和 UpdateDefault 方法支持默认值。你也可以指明 DefaultFunc 替代其为自定义生成器。
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Time("created_at").
Default(time.Now),
field.Time("updated_at").
Default(time.Now).
UpdateDefault(time.Now),
field.String("name").
Default("unknown"),
field.String("cuid").
DefaultFunc(cuid.New),
field.JSON("dirs", []http.Dir{}).
Default([]http.Dir{"/tmp"}),
}
}
可通过 entsql.Annotation: 将 SQL 特定的字面量或表达式(如函数调用)添加到默认值配置中:
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
// Add a new field with CURRENT_TIMESTAMP
// as a default value to all previous rows.
field.Time("created_at").
Default(time.Now).
Annotations(
entsql.Default("CURRENT_TIMESTAMP"),
),
// Add a new field with a default value
// expression that works on all dialects.
field.String("field").
Optional().
Annotations(
entsql.DefaultExpr("lower(other_field)"),
),
// Add a new field with custom default value
// expression for each dialect.
field.String("default_exprs").
Optional().
Annotations(
entsql.DefaultExprs(map[string]string{
dialect.MySQL: "TO_BASE64('ent')",
dialect.SQLite: "hex('ent')",
dialect.Postgres: "md5('ent')",
}),
),
}
}
考虑到你的 DefaultFunc 也会返回错误,最好使用 模式钩子 进行处理。
参阅 相关问题 获取更多信息。
验证器
字段验证器是在模式中使用 Validate 方法定义的 func(T) error 类型的函数,在创建或更新实体之前应用与字段的值的校验。
字段验证器支持的字段类型为 string 和所有的数值类型。
package schema
import (
"errors"
"regexp"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
// Group schema.
type Group struct {
ent.Schema
}
// Fields of the group.
func (Group) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Match(regexp.MustCompile("[a-zA-Z_]+$")).
Validate(func(s string) error {
if strings.ToLower(s) == s {
return errors.New("group name must begin with uppercase")
}
return nil
}),
}
}
这是另外一个可复用验证器的示例:
import (
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema/field"
)
// MaxRuneCount validates the rune length of a string by using the unicode/utf8 package.
func MaxRuneCount(maxLen int) func(s string) error {
return func(s string) error {
if utf8.RuneCountInString(s) > maxLen {
return errors.New("value is more than the max length")
}
return nil
}
}
field.String("name").
// If using a SQL-database: change the underlying data type to varchar(10).
Annotations(entsql.Annotation{
Size: 10,
}).
Validate(MaxRuneCount(10))
field.String("nickname").
// If using a SQL-database: change the underlying data type to varchar(20).
Annotations(entsql.Annotation{
Size: 20,
}).
Validate(MaxRuneCount(20))
内置验证器
本框架为每种类型提供了少数内置验证器:
-
数值类型:
Positive()Negative()NonNegative()Min(i)- 验证值大于 i.Max(i)- 验证值小于 i.Range(i, j)- 验证值在 [i, j] 之间(闭区间).
-
stringMinLen(i)MaxLen(i)Match(regexp.Regexp)NotEmpty
-
[]byteMaxLen(i)MinLen(i)NotEmpty
可选字段(Optional)
可选字段是指在实体创建过程中非必需的字段,在数据库中将被设置为可空字段。
与边不同,字段默认是必需的,若要将其设为可选,应显式使用 Optional 方法进行设置。
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("required_name"),
field.String("optional_name").
Optional(),
}
}
空字段(Nillable)
有时你希望能够区别字段零值和 nil。例如数据库列中包含 0 或 NULL。Nillable 选项就是为解决这个问题。
如果你拥有一个 T 类型的 Optional 字段,将其设置为 Nillable 将会生成一个 *T 类型的字段结构。
因此如果数据库的此字段返回 NULL,这个字段就会是 nil。否则,它将包含指向实际值的指针。
例如以下模式:
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("required_name"),
field.String("optional_name").
Optional(),
field.String("nillable_name").
Optional().
Nillable(),
}
}
User 实体生成的结构如下:
package ent
// User entity.
type User struct {
RequiredName string `json:"required_name,omitempty"`
OptionalName string `json:"optional_name,omitempty"`
NillableName *string `json:"nillable_name,omitempty"`
}
Nillable 必填字段
在 JSON 序列化时字段若查询时未选择或提供值,Nillable字段同样可以避免零值。
例如一个 time.Time 字段:
// Fields of the task.
func (Task) Fields() []ent.Field {
return []ent.Field{
field.Time("created_at").
Default(time.Now),
field.Time("nillable_created_at").
Default(time.Now).
Nillable(),
}
}
生成的 Task 实体结构如下:
package ent
// Task entity.
type Task struct {
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
// NillableCreatedAt holds the value of the "nillable_created_at" field.
NillableCreatedAt *time.Time `json:"nillable_created_at,omitempty"`
}
json.Marshal 结果是:
b, _ := json.Marshal(Task{})
fmt.Printf("%s\n", b)
// {"created_at":"0001-01-01T00:00:00Z"}
now := time.Now()
b, _ = json.Marshal(Task{CreatedAt: now, NillableCreatedAt: &now})
fmt.Printf("%s\n", b)
// {"created_at":"2009-11-10T23:00:00Z","nillable_created_at":"2009-11-10T23:00:00Z"}
不可变字段(Immutable)
不可变字段是指仅能在实体创建时设置的字段。也就是说,实体的更新构建器不会为这些字段生成设置器。
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Time("created_at").
Default(time.Now).
Immutable(),
}
}
唯一值(Uniqueness)
字段可通过 Unique 方法定义为唯一。请注意,唯一字段不能设置默认值。
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("nickname").
Unique(),
}
}
评论(Comments)
可以通过使用 .Comment() 方法将评论添加到字段。评论在生成实体代码出现在字段之前。换行符通过转义序列 \n 实现支持。
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Default("John Doe").
Comment("Name of the user.\n If not specified, defaults to \"John Doe\"."),
}
}
已弃用字段(Deprecated Fields)
Deprecated 方法标识某个字段已被弃用。弃用字段默认在查询中不会被选则,并且在生成的代码中字段结构声明为 Deprecated。
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Deprecated("use `full_name` instead"),
}
}
存储名称(Storage Key)
可以通过 StorageKey 方法配置定义的存储名称。这会将其映射为SQL方言中的列名以及 Gremlin 中的属性名。
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
StorageKey("old_name"),
}
}
索引(Indexes)
索引可以定义在多个字段和某些类型的边。但请注意,此功能当前仅限于 SQL 功能。
更多请参阅 索引 部分。
结构体标签(Struct Tags)
可以使用 StructTag 将结构体标签添加到生成的实体。注意如果未提供此选项或选项中没有包含 json 标签,默认的具有字段名称的 json 标签会被添加。
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
StructTag(`gqlgen:"gql_name"`),
}
}
额外结构体字段
默认情况下, ent 通过 schema.Fields 方法配置生成带有字段的实体模型。
例如给定以下模式配置:
// User schema.
type User struct {
ent.Schema
}
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("age").
Optional().
Nillable(),
field.String("name").
StructTag(`gqlgen:"gql_name"`),
}
}
生成如下模式:
// User is the model entity for the User schema.
type User struct {
// Age holds the value of the "age" field.
Age *int `json:"age,omitempty"`
// Name holds the value of the "name" field.
Name string `json:"name,omitempty" gqlgen:"gql_name"`
}
为将额外的字段添加到生成的结构体中但是 不在数据库中存储 ,可以使用 外部模板,例如:
{{ define "model/fields/additional" }}
{{- if eq $.Name "User" }}
// StaticField defined by template.
StaticField string `json:"static,omitempty"`
{{- end }}
{{ end }}
生成如下模型:
// User is the model entity for the User schema.
type User struct {
// Age holds the value of the "age" field.
Age *int `json:"age,omitempty"`
// Name holds the value of the "name" field.
Name string `json:"name,omitempty" gqlgen:"gql_name"`
// StaticField defined by template.
StaticField string `json:"static,omitempty"`
}
敏感字段
字符串字段可以通过 Sensitive 方法定义为敏感字段。敏感字段不会被打印且在编码时会被忽略。
注意敏感字段不能有结构体标签。
// User schema.
type User struct {
ent.Schema
}
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("password").
Sensitive(),
}
}
枚举字段
Enum 构建器允许创建允许值列表的枚举字段。
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("first_name"),
field.String("last_name"),
field.Enum("size").
Values("big", "small"),
}
}
默认情况下,Ent 使用简单的字符串类型来表示 PostgreSQL 和 SQLite 中的枚举值。但在某些情况下,您可能希望使用数据库提供的原生枚举类型。更多信息请参阅 枚举迁移指南。
当使用自定义 GoType 时,必须能转化为基础的 string 类型或实现 ValueScanner 接口。
自定义 Go 语言类型同样要求实现 EnumValues 接口,以便告诉 Ent 给定的枚举值。
下面的示例展示了如何通过自定义可转为 string 的 Go 语言类型定义 Enum 字段。
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("first_name"),
field.String("last_name"),
// A convertible type to string.
field.Enum("shape").
GoType(property.Shape("")),
}
}
实现 EnumValues 接口。
package property
type Shape string
const (
Triangle Shape = "TRIANGLE"
Circle Shape = "CIRCLE"
)
// Values provides list valid values for Enum.
func (Shape) Values() (kinds []string) {
for _, s := range []Shape{Triangle, Circle} {
kinds = append(kinds, string(s))
}
return
}
下面的示例展示了如何通过无法转为 string 类型但是实现了 ValueScanner 接口的自定义 Go 语言类型定义 Enum 字段:
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("first_name"),
field.String("last_name"),
// Add conversion to and from string
field.Enum("level").
GoType(property.Level(0)),
}
}
同样实现 ValueScanner 接口。
package property
import "database/sql/driver"
type Level int
const (
Unknown Level = iota
Low
High
)
func (p Level) String() string {
switch p {
case Low:
return "LOW"
case High:
return "HIGH"
default:
return "UNKNOWN"
}
}
// Values provides list valid values for Enum.
func (Level) Values() []string {
return []string{Unknown.String(), Low.String(), High.String()}
}
// Value provides the DB a string from int.
func (p Level) Value() (driver.Value, error) {
return p.String(), nil
}
// Scan tells our code how to read the enum into our type.
func (p *Level) Scan(val any) error {
var s string
switch v := val.(type) {
case nil:
return nil
case string:
s = v
case []uint8:
s = string(v)
}
switch s {
case "LOW":
*p = Low
case "HIGH":
*p = High
default:
*p = Unknown
}
return nil
}
将他们结合到一起:
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("first_name"),
field.String("last_name"),
field.Enum("size").
Values("big", "small"),
// A convertible type to string.
field.Enum("shape").
GoType(property.Shape("")),
// Add conversion to and from string.
field.Enum("level").
GoType(property.Level(0)),
}
}
生成代码后的使用很简单:
client.User.Create().
SetFirstName("John").
SetLastName("Dow").
SetSize(user.SizeSmall).
SetShape(property.Triangle).
SetLevel(property.Low).
SaveX(context.Background())
john := client.User.Query().FirstX(context.Background())
fmt.Println(john)
// User(id=1, first_name=John, last_name=Dow, size=small, shape=TRIANGLE, level=LOW)
注解(Annotations)
Annotations 用于在代码生成过程中向字段对象附加任意元数据。模板扩展可以检索这些元数据并在其模板内部使用。
注意元数据对象必须能被序列化为原始 JSON 值(例如 struct、map 或 slice)。
// User schema.
type User struct {
ent.Schema
}
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Time("creation_date").
Annotations(entgql.Annotation{
OrderField: "CREATED_AT",
}),
}
}
参阅 [模板文档] (templates.md#annotations) 了解更多注解以及他们在模板中的使用。
命名规范
按惯例,字段名称应采用蛇形命名法 snake_case。由 ent 生成的对应结构体字段将遵循 Go 语言惯例,采用驼峰命名法 PascalCase。若需使用驼峰命名法 PascalCase ,可通过 StorageKey 或 StructTag 方法实现。