字段

概述

Schema 中的字段(或属性)是节点的属性。 例如:Useragename, usernamecreated_at 4个字段。

re-fields-properties

用 schema 的 Fields 方法可返回这些字段。 如:

  1. package schema
  2. import (
  3. "time"
  4. "entgo.io/ent"
  5. "entgo.io/ent/schema/field"
  6. )
  7. // User schema.
  8. type User struct {
  9. ent.Schema
  10. }
  11. // Fields of the user.
  12. func (User) Fields() []ent.Field {
  13. return []ent.Field{
  14. field.Int("age"),
  15. field.String("name"),
  16. field.String("username").
  17. Unique(),
  18. field.Time("created_at").
  19. Default(time.Now),
  20. }
  21. }

默认所有字段都是必需的,可使用 Optional 方法设置为可选字段。

类型

目前框架支持以下数据类型:

  • Go中所有数值类型。 如 intuint8float64
  • bool 布尔型
  • string 字符串
  • time.Time 时间类型
  • UUID
  • []byte (仅限SQL)。
  • JSON(仅限SQL)。
  • Enum(仅限SQL)。
  • 其它类型 (仅限SQL)
  1. package schema
  2. import (
  3. "time"
  4. "net/url"
  5. "github.com/google/uuid"
  6. "entgo.io/ent"
  7. "entgo.io/ent/schema/field"
  8. )
  9. // User schema.
  10. type User struct {
  11. ent.Schema
  12. }
  13. // Fields of the user.
  14. func (User) Fields() []ent.Field {
  15. return []ent.Field{
  16. field.Int("age").
  17. Positive(),
  18. field.Float("rank").
  19. Optional(),
  20. field.Bool("active").
  21. Default(false),
  22. field.String("name").
  23. Unique(),
  24. field.Time("created_at").
  25. Default(time.Now),
  26. field.JSON("url", &url.URL{}).
  27. Optional(),
  28. field.JSON("strings", []string{}).
  29. Optional(),
  30. field.Enum("state").
  31. Values("on", "off").
  32. Optional(),
  33. field.UUID("uuid", uuid.UUID{}).
  34. Default(uuid.New),
  35. }
  36. }

想要了解更多关于每种类型是如何映射到数据库类型的,请移步 数据迁移 章节。

ID 字段

id 字段内置于架构中,无需声明。 在基于 SQL 的数据库中,它的类型默认为 int (可以使用 代码生成设置 更改)并自动递增。

如果想要配置 id 字段在所有表中唯一,可以在运行 schema 迁移时使用 WithGlobalUniqueID 选项实现。

如果需要对 id 字段进行其他配置,或者要使用由应用程序在实体创建时提供的 id (例如UUID),可以覆盖内置 id 配置。 如:

  1. // Fields of the Group.
  2. func (Group) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.Int("id").
  5. StructTag(`json:"oid,omitempty"`),
  6. }
  7. }
  8. // Fields of the Blob.
  9. func (Blob) Fields() []ent.Field {
  10. return []ent.Field{
  11. field.UUID("id", uuid.UUID{}).
  12. Default(uuid.New).
  13. StorageKey("oid"),
  14. }
  15. }
  16. // Fields of the Pet.
  17. func (Pet) Fields() []ent.Field {
  18. return []ent.Field{
  19. field.String("id").
  20. MaxLen(25).
  21. NotEmpty().
  22. Unique().
  23. Immutable(),
  24. }
  25. }

如果你需要设置一个自定义函数来生成 ID, 使用 DefaultFunc 方法来指定一个函数,每次ID将由此函数生成。 更多信息请参阅 相关常见问题

  1. // Fields of the User.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.Int64("id").
  5. DefaultFunc(func() int64 {
  6. // An example of a dumb ID generator - use a production-ready alternative instead.
  7. return time.Now().Unix() << 8 | atomic.AddInt64(&counter, 1) % 256
  8. }),
  9. }
  10. }

数据库字段类型

每个数据库方言都有自己Go类型与数据库类型的映射。 例如,MySQL 方言将Go类型为 float64 的字段创建为 double 的数据库字段。 当然,我们也可以通过 SchemaType 方法来重写默认的类型映射。

  1. package schema
  2. import (
  3. "entgo.io/ent"
  4. "entgo.io/ent/dialect"
  5. "entgo.io/ent/schema/field"
  6. )
  7. // Card schema.
  8. type Card struct {
  9. ent.Schema
  10. }
  11. // Fields of the Card.
  12. func (Card) Fields() []ent.Field {
  13. return []ent.Field{
  14. field.Float("amount").
  15. SchemaType(map[string]string{
  16. dialect.MySQL: "decimal(6,2)", // Override MySQL.
  17. dialect.Postgres: "numeric", // Override Postgres.
  18. }),
  19. }
  20. }

Go 类型

字段的默认类型是基本的 Go 类型。 例如,对于字符串字段,类型是 string, 对于时间字段,类型是 time.TimeGoType 方法提供了以自定义类型覆盖 默认类型的选项。

自定义类型必须是可转换为Go基本类型,或者实现 ValueScanner 接口的类型。

  1. package schema
  2. import (
  3. "database/sql"
  4. "entgo.io/ent"
  5. "entgo.io/ent/dialect"
  6. "entgo.io/ent/schema/field"
  7. "github.com/shopspring/decimal"
  8. )
  9. // Amount is a custom Go type that's convertible to the basic float64 type.
  10. type Amount float64
  11. // Card schema.
  12. type Card struct {
  13. ent.Schema
  14. }
  15. // Fields of the Card.
  16. func (Card) Fields() []ent.Field {
  17. return []ent.Field{
  18. field.Float("amount").
  19. GoType(Amount(0)),
  20. field.String("name").
  21. Optional().
  22. // A ValueScanner type.
  23. GoType(&sql.NullString{}),
  24. field.Enum("role").
  25. // A convertible type to string.
  26. GoType(role.Role("")),
  27. field.Float("decimal").
  28. // A ValueScanner type mixed with SchemaType.
  29. GoType(decimal.Decimal{}).
  30. SchemaType(map[string]string{
  31. dialect.MySQL: "decimal(6,2)",
  32. dialect.Postgres: "numeric",
  33. }),
  34. }
  35. }

其它字段

Other 代表一个不适合任何标准字段类型的字段。 示例为 Postgres 中的 Rage 类型或 Geospatial 类型

  1. package schema
  2. import (
  3. "entgo.io/ent"
  4. "entgo.io/ent/dialect"
  5. "entgo.io/ent/schema/field"
  6. "github.com/jackc/pgtype"
  7. )
  8. // User schema.
  9. type User struct {
  10. ent.Schema
  11. }
  12. // Fields of the User.
  13. func (User) Fields() []ent.Field {
  14. return []ent.Field{
  15. field.Other("duration", &pgtype.Tstzrange{}).
  16. SchemaType(map[string]string{
  17. dialect.Postgres: "tstzrange",
  18. }),
  19. }
  20. }

默认值

非唯一 字段可使用 DefaultUpdateDefault 方法为其设置默认值。 你也可以指定 DefaultFunc 方法来自定义默认值生成。

  1. // Fields of the User.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.Time("created_at").
  5. Default(time.Now),
  6. field.Time("updated_at").
  7. Default(time.Now).
  8. UpdateDefault(time.Now),
  9. field.String("name").
  10. Default("unknown"),
  11. field.String("cuid").
  12. DefaultFunc(cuid.New),
  13. field.JSON("dirs", []http.Dir{}).
  14. Default([]http.Dir{"/tmp"}),
  15. }
  16. }

可以通过 entsql.Annotation 将像函数调用的SQL特定表达式添加到默认值配置中:

  1. // Fields of the User.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. // Add a new field with CURRENT_TIMESTAMP
  5. // as a default value to all previous rows.
  6. field.Time("created_at").
  7. Default(time.Now).
  8. Annotations(&entsql.Annotation{
  9. Default: "CURRENT_TIMESTAMP",
  10. }),
  11. }
  12. }

为避免你指定的 DefaultFunc 方法也返回了一个错误,最好使用 schema-hooks 处理它。 更多信息请参阅 相关常见问题

校验器

字段校验器是一个 func(T) error 类型的函数,定义在 schema 的 Validate 方法中,字段在创建或更新前会执行此方法。

支持 string 类型和所有数值类型。

  1. package schema
  2. import (
  3. "errors"
  4. "regexp"
  5. "strings"
  6. "time"
  7. "entgo.io/ent"
  8. "entgo.io/ent/schema/field"
  9. )
  10. // Group schema.
  11. type Group struct {
  12. ent.Schema
  13. }
  14. // Fields of the group.
  15. func (Group) Fields() []ent.Field {
  16. return []ent.Field{
  17. field.String("name").
  18. Match(regexp.MustCompile("[a-zA-Z_]+$")).
  19. Validate(func(s string) error {
  20. if strings.ToLower(s) == s {
  21. return errors.New("group name must begin with uppercase")
  22. }
  23. return nil
  24. }),
  25. }
  26. }

又如:编写一个可复用的校验器

  1. import (
  2. "entgo.io/ent/dialect/entsql"
  3. "entgo.io/ent/schema/field"
  4. )
  5. // MaxRuneCount validates the rune length of a string by using the unicode/utf8 package.
  6. func MaxRuneCount(maxLen int) func(s string) error {
  7. return func(s string) error {
  8. if utf8.RuneCountInString(s) > maxLen {
  9. return errors.New("value is more than the max length")
  10. }
  11. return nil
  12. }
  13. }
  14. field.String("name").
  15. // If using a SQL-database: change the underlying data type to varchar(10).
  16. Annotations(entsql.Annotation{
  17. Size: 10,
  18. }).
  19. Validate(MaxRuneCount(10))
  20. field.String("nickname").
  21. // If using a SQL-database: change the underlying data type to varchar(20).
  22. Annotations(entsql.Annotation{
  23. Size: 20,
  24. }).
  25. Validate(MaxRuneCount(20))

内置校验器

框架为每个类型提供了几个内置的验证器:

  • 数值类型:

    • Positive()
    • Negative()
    • NonNegative()
    • Min(i) - 验证给定的值 > i。
    • Max(i) - 验证给定的值 < i。
    • Range(i, j) - 验证给定值在 [i, j] 之间。
  • string 字符串

    • MinLen(i)
    • MaxLen(i)
    • Match(regexp.Regexp)
    • NotEmpty
  • []byte

    • MaxLen(i)
    • MinLen(i)
    • NotEmpty

Optional 可选项

可选字段为创建时非必须的字段,在数据库中被设置为 null。 和 edges 不同,字段默认都为必需字段,可通过 Optional 方法显示的设为可选字段。

  1. // Fields of the user.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.String("required_name"),
  5. field.String("optional_name").
  6. Optional(),
  7. }
  8. }

Nillable 空值

Sometimes you want to be able to distinguish between the zero value of fields and nil. For example, if the database column contains 0 or NULL. The Nillable option exists exactly for this.

如果你有一个类型为 T可选字段,设置为 Nillable 后,将生成一个类型为 *T 的结构体字段。 因此,如果数据库返回 NULL 字段, 结构体字段将为 nil 值。 Otherwise, it will contain a pointer to the actual value.

例如,在这个 schema 中:

  1. // Fields of the user.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.String("required_name"),
  5. field.String("optional_name").
  6. Optional(),
  7. field.String("nillable_name").
  8. Optional().
  9. Nillable(),
  10. }
  11. }

User 生成的结构体如下:

ent/user.go

  1. package ent
  2. // User entity.
  3. type User struct {
  4. RequiredName string `json:"required_name,omitempty"`
  5. OptionalName string `json:"optional_name,omitempty"`
  6. NillableName *string `json:"nillable_name,omitempty"`
  7. }

Nillable required fields

Nillable fields are also helpful for avoiding zero values in JSON marshaling for fields that have not been Selected in the query. For example, a time.Time field.

  1. // Fields of the task.
  2. func (Task) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.Time("created_at").
  5. Default(time.Now),
  6. field.Time("nillable_created_at").
  7. Default(time.Now).
  8. Nillable(),
  9. }
  10. }

The generated struct for the Task entity will be as follows:

ent/task.go

  1. package ent
  2. // Task entity.
  3. type Task struct {
  4. // CreatedAt holds the value of the "created_at" field.
  5. CreatedAt time.Time `json:"created_at,omitempty"`
  6. // NillableCreatedAt holds the value of the "nillable_created_at" field.
  7. NillableCreatedAt *time.Time `json:"nillable_created_at,omitempty"`
  8. }

And the result of json.Marshal is:

  1. b, _ := json.Marshal(Task{})
  2. fmt.Printf("%s\n", b)
  3. // {"created_at":"0001-01-01T00:00:00Z"}
  4. now := time.Now()
  5. b, _ = json.Marshal(Task{CreatedAt: now, NillableCreatedAt: &now})
  6. fmt.Printf("%s\n", b)
  7. // {"created_at":"2009-11-10T23:00:00Z","nillable_created_at":"2009-11-10T23:00:00Z"}

Immutable 不可变的

Immutable fields are fields that can be set only in the creation of the entity. i.e., no setters will be generated for the update builders of the entity.

  1. // Fields of the user.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.Time("created_at").
  5. Default(time.Now).
  6. Immutable(),
  7. }
  8. }

唯一键

Fields can be defined as unique using the Unique method. Note that unique fields cannot have default values.

  1. // Fields of the user.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.String("nickname").
  5. Unique(),
  6. }
  7. }

Comments

A comment can be added to a field using the .Comment() method. This comment appears before the field in the generated entity code. Newlines are supported using the \n escape sequence.

  1. // Fields of the user.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.String("name").
  5. Default("John Doe").
  6. Comment("Name of the user.\n If not specified, defaults to \"John Doe\"."),
  7. }
  8. }

Storage Key

Custom storage name can be configured using the StorageKey method. It’s mapped to a column name in SQL dialects and to property name in Gremlin.

  1. // Fields of the user.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.String("name").
  5. StorageKey("old_name"),
  6. }
  7. }

Indexes

Indexes can be defined on multi fields and some types of edges as well. However, you should note, that this is currently an SQL-only feature.

Read more about this in the Indexes section.

Struct Tags

Custom struct tags can be added to the generated entities using the StructTag method. Note that if this option was not provided, or provided and did not contain the json tag, the default json tag will be added with the field name.

  1. // Fields of the user.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.String("name").
  5. StructTag(`gqlgen:"gql_name"`),
  6. }
  7. }

Additional Struct Fields

By default, ent generates the entity model with fields that are configured in the schema.Fields method. For example, given this schema configuration:

  1. // User schema.
  2. type User struct {
  3. ent.Schema
  4. }
  5. // Fields of the user.
  6. func (User) Fields() []ent.Field {
  7. return []ent.Field{
  8. field.Int("age").
  9. Optional().
  10. Nillable(),
  11. field.String("name").
  12. StructTag(`gqlgen:"gql_name"`),
  13. }
  14. }

The generated model will be as follows:

  1. // User is the model entity for the User schema.
  2. type User struct {
  3. // Age holds the value of the "age" field.
  4. Age *int `json:"age,omitempty"`
  5. // Name holds the value of the "name" field.
  6. Name string `json:"name,omitempty" gqlgen:"gql_name"`
  7. }

In order to add additional fields to the generated struct that are not stored in the database, use external templates. For example:

  1. {{ define "model/fields/additional" }}
  2. {{- if eq $.Name "User" }}
  3. // StaticField defined by template.
  4. StaticField string `json:"static,omitempty"`
  5. {{- end }}
  6. {{ end }}

The generated model will be as follows:

  1. // User is the model entity for the User schema.
  2. type User struct {
  3. // Age holds the value of the "age" field.
  4. Age *int `json:"age,omitempty"`
  5. // Name holds the value of the "name" field.
  6. Name string `json:"name,omitempty" gqlgen:"gql_name"`
  7. // StaticField defined by template.
  8. StaticField string `json:"static,omitempty"`
  9. }

Sensitive Fields

String fields can be defined as sensitive using the Sensitive method. Sensitive fields won’t be printed and they will be omitted when encoding.

Note that sensitive fields cannot have struct tags.

  1. // User schema.
  2. type User struct {
  3. ent.Schema
  4. }
  5. // Fields of the user.
  6. func (User) Fields() []ent.Field {
  7. return []ent.Field{
  8. field.String("password").
  9. Sensitive(),
  10. }
  11. }

Enum Fields

The Enum builder allows creating enum fields with a list of permitted values.

  1. // Fields of the User.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.String("first_name"),
  5. field.String("last_name"),
  6. field.Enum("size").
  7. Values("big", "small"),
  8. }
  9. }

When a custom GoType is being used, it is must be convertible to the basic string type or it needs to implement the ValueScanner interface.

The EnumValues interface is also required by the custom Go type to tell Ent what are the permitted values of the enum.

The following example shows how to define an Enum field with a custom Go type that is convertible to string:

  1. // Fields of the User.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.String("first_name"),
  5. field.String("last_name"),
  6. // A convertible type to string.
  7. field.Enum("shape").
  8. GoType(property.Shape("")),
  9. }
  10. }

Implement the EnumValues interface.

  1. package property
  2. type Shape string
  3. const (
  4. Triangle Shape = "TRIANGLE"
  5. Circle Shape = "CIRCLE"
  6. )
  7. // Values provides list valid values for Enum.
  8. func (Shape) Values() (kinds []string) {
  9. for _, s := range []Shape{Triangle, Circle} {
  10. kinds = append(kinds, string(s))
  11. }
  12. return
  13. }

The following example shows how to define an Enum field with a custom Go type that is not convertible to string, but it implements the ValueScanner interface:

  1. // Fields of the User.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.String("first_name"),
  5. field.String("last_name"),
  6. // Add conversion to and from string
  7. field.Enum("level").
  8. GoType(property.Level(0)),
  9. }
  10. }

Implement also the ValueScanner interface.

  1. package property
  2. import "database/sql/driver"
  3. type Level int
  4. const (
  5. Unknown Level = iota
  6. Low
  7. High
  8. )
  9. func (p Level) String() string {
  10. switch p {
  11. case Low:
  12. return "LOW"
  13. case High:
  14. return "HIGH"
  15. default:
  16. return "UNKNOWN"
  17. }
  18. }
  19. // Values provides list valid values for Enum.
  20. func (Level) Values() []string {
  21. return []string{Unknown.String(), Low.String(), High.String()}
  22. }
  23. // Value provides the DB a string from int.
  24. func (p Level) Value() (driver.Value, error) {
  25. return p.String(), nil
  26. }
  27. // Scan tells our code how to read the enum into our type.
  28. func (p *Level) Scan(val any) error {
  29. var s string
  30. switch v := val.(type) {
  31. case nil:
  32. return nil
  33. case string:
  34. s = v
  35. case []uint8:
  36. s = string(v)
  37. }
  38. switch s {
  39. case "LOW":
  40. *p = Low
  41. case "HIGH":
  42. *p = High
  43. default:
  44. *p = Unknown
  45. }
  46. return nil
  47. }

Combining it all together:

  1. // Fields of the User.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.String("first_name"),
  5. field.String("last_name"),
  6. field.Enum("size").
  7. Values("big", "small"),
  8. // A convertible type to string.
  9. field.Enum("shape").
  10. GoType(property.Shape("")),
  11. // Add conversion to and from string.
  12. field.Enum("level").
  13. GoType(property.Level(0)),
  14. }
  15. }

After code generation usage is trivial:

  1. client.User.Create().
  2. SetFirstName("John").
  3. SetLastName("Dow").
  4. SetSize(user.SizeSmall).
  5. SetShape(property.Triangle).
  6. SetLevel(property.Low).
  7. SaveX(context.Background())
  8. john := client.User.Query().FirstX(context.Background())
  9. fmt.Println(john)
  10. // User(id=1, first_name=John, last_name=Dow, size=small, shape=TRIANGLE, level=LOW)

Annotations

Annotations is used to attach arbitrary metadata to the field object in code generation. Template extensions can retrieve this metadata and use it inside their templates.

Note that the metadata object must be serializable to a JSON raw value (e.g. struct, map or slice).

  1. // User schema.
  2. type User struct {
  3. ent.Schema
  4. }
  5. // Fields of the user.
  6. func (User) Fields() []ent.Field {
  7. return []ent.Field{
  8. field.Time("creation_date").
  9. Annotations(entgql.Annotation{
  10. OrderField: "CREATED_AT",
  11. }),
  12. }
  13. }

Read more about annotations and their usage in templates in the template doc.

Naming Convention

By convention field names should use snake_case. The corresponding struct fields generated by ent will follow the Go convention of using PascalCase. In cases where PascalCase is desired, you can do so with the StorageKey or StructTag methods.