字段

概述

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. }
  14. }

可以通过 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 空值

有时您希望能将 0 值和nil区别开来; 例如,数据库列中存0NULL. 那么 Nillable 就派上用场了。

如果你有一个类型为 T可选字段,设置为 Nillable 后,将生成一个类型为 *T 的结构体字段。 因此,如果数据库返回 NULL 字段, 结构体字段将为 nil 值。 否则,它将包含一个指向实际数据的指针。

例如,在这个 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 生成的结构体如下:

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

Immutable 不可变的

不可变字段只能在创建实体时设置。 即:不会为实体生成任何更新方法。

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

唯一键

字段可以使用 Unique 方法定义为唯一字段。 注意:唯一字段不能有默认值。

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

存储键名

可以使用 StorageKey 方法自定义数据库中的字段名称。 在 SQL 中为字段名,在 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. }

索引

索引可以在多字段和某些类型的 edges 上定义. 注意:目前只有 SQL 类型的数据库支持此功能。

更多请参阅 索引 章节。

结构体标记(tags)

可以使用 StructTag 方法将自定义结构标签添加到生成的实体中。 注意:如果此选择未设置,或者未包含 json 标签,那么默认的 json 标签将被设置为字段名。

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

附加元数据字段

默认情况下, entschema.Fields 方法配置的字段生成实体模型。 例如,给定此schema配置:

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

生成的模型如下:

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

向生成的结构体中添加的字段是未在数据库中定义的, 使用 external templates。 例如:

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

生成的模型如下:

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

敏感字段

字符串(String)字段归为敏感信息可借助Sensitive方法实现。 敏感字段不会被打印输出并在生成模型时被忽略。

注意敏感字段不能有结构体标签。

  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 生成器可创建包含允许值列表的枚举字段。

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

当使用自定义的 GoType 时, 必须能转换为 string 类型或者实现 ValueScanner 接口。

同时 EnumValues 接口需要自定义的Go类型告诉Ent被允许的枚举值有哪些。

以下示例展示了如何将Enum字段使用的自定义Go类型转换到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. }

实现EnumValues 接口。

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

以下示例展示了如何使用自定义Go类型定义一个Enum字段 并且不转换到 string,但是它实现了ValueScanner 接口:

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

同时实现 ValueScanner 接口。

  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 interface{}) 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. }

整合到一起:

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

之后的使用也轻而易举:

  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用于在生成代码时将任意的元数据附加到字段对象。 模板扩展可以检索此元数据并直接使用。

注意元数据必须能序列化为JSON原始值(如struct,map或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. }

模板章节了解更多关于注解及其使用的相关信息。

命名规范

根据惯例字段的命名应使用snake_case(蛇形命名法)。 由ent生成相应的结构体字段使用是Go规范的PascalCase(帕斯卡命名法)。 在需要PascalCase 时,您可以借助StorageKeyStructTag方法实现。