快速指南

    • 简单地使用数据库结构作为图结构。
    • 使用Go代码定义结构。
    • 基于代码生成的静态类型。
    • 容易地进行数据库查询和图遍历。
    • 容易地使用Go模板扩展和自定义。

    如果您的项目位于 GOPATH 之外或者不熟悉 GOPATH, 您可以像这样使用 :

    安装

    在安装 ent 代码生成工具后,您应该在 PATH 中看到它。 如果您没看到,则应该运行命令:go run entgo.io/ent/cmd/ent <command>

    创建你的第一个结构

    转到项目的根目录并运行:

    1. go run entgo.io/ent/cmd/ent init User

    此命令将生成结构 User<project>/ent/schema/ 目录内:

    <project>/ent/schema/user.go

    1. package schema
    2. import "entgo.io/ent"
    3. // User holds the schema definition for the User entity.
    4. type User struct {
    5. ent.Schema
    6. }
    7. // Fields of the User.
    8. func (User) Fields() []ent.Field {
    9. return nil
    10. }
    11. // Edges of the User.
    12. func (User) Edges() []ent.Edge {
    13. return nil
    14. }

    将 2 个字段添加到 User 结构:

    <project>/ent/schema/user.go

    1. package schema
    2. import (
    3. "entgo.io/ent"
    4. "entgo.io/ent/schema/field"
    5. )
    6. // Fields of the User.
    7. func (User) Fields() []ent.Field {
    8. return []ent.Field{
    9. field.Int("age").
    10. Positive(),
    11. field.String("name").
    12. Default("unknown"),
    13. }
    14. }

    在项目根目录中运行 go generate 如下:

    1. go generate ./ent

    这将创建以下文件:

    1. ent
    2. ├── client.go
    3. ├── config.go
    4. ├── context.go
    5. ├── ent.go
    6. ├── generate.go
    7. ├── mutation.go
    8. ... truncated
    9. ├── schema
    10. └── user.go
    11. ├── tx.go
    12. ├── user
    13. ├── user.go
    14. └── where.go
    15. ├── user.go
    16. ├── user_create.go
    17. ├── user_delete.go
    18. ├── user_query.go
    19. └── user_update.go

    创建一个 ent.Client。 作为例子,我们将使用 SQLite3。

    <project>/start/start.go

    1. package main
    2. import (
    3. "context"
    4. "log"
    5. "<project>/ent"
    6. _ "github.com/mattn/go-sqlite3"
    7. )
    8. func main() {
    9. client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
    10. if err != nil {
    11. log.Fatalf("failed opening connection to sqlite: %v", err)
    12. }
    13. defer client.Close()
    14. // Run the auto migration tool.
    15. if err := client.Schema.Create(context.Background()); err != nil {
    16. log.Fatalf("failed creating schema resources: %v", err)
    17. }
    18. }

    现在,我们做好了创建用户的准备。 让我们调用 CreateUser 函数,比如:

    <project>/start/start.go

    查询你的实体

    ent 为每个实体结构生成一个package,包含其条件、默认值、验证器、有关存储元素 (列名、主键等) 的额外信息。

    <project>/start/start.go

    1. package main
    2. import (
    3. "log"
    4. "<project>/ent"
    5. "<project>/ent/user"
    6. )
    7. func QueryUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
    8. u, err := client.User.
    9. Query().
    10. Where(user.Name("a8m")).
    11. // `Only` 在 找不到用户 或 找到多于一个用户 时报错,
    12. Only(ctx)
    13. if err != nil {
    14. return nil, fmt.Errorf("failed querying user: %w", err)
    15. }
    16. log.Println("user returned: ", u)
    17. return u, nil
    18. }

    添加你的第一条边 (关系)

    1. go run entgo.io/ent/cmd/ent init Car Group

    然后我们手动添加其他字段:

    <project>/ent/schema/car.go

    1. // Fields of the Car.
    2. func (Car) Fields() []ent.Field {
    3. return []ent.Field{
    4. field.String("model"),
    5. field.Time("registered_at"),
    6. }
    7. }

    <project>/ent/schema/group.go

    1. func (Group) Fields() []ent.Field {
    2. return []ent.Field{
    3. field.String("name").
    4. // 使用正则表达式校验 group name
    5. Match(regexp.MustCompile("[a-zA-Z_]+$")),
    6. }

    让我们来定义第一个关系。 从 UserCar 的关系,定义了一个用户可以拥有1辆或多辆汽车,但每辆汽车只有一个车主(一对多关系)。

    er-user-cars

    让我们将 "cars" 关系添加到 User 结构中,并运行 go generate ./ent

    <project>/ent/schema/user.go

    1. // Edges of the User.
    2. func (User) Edges() []ent.Edge {
    3. return []ent.Edge{
    4. edge.To("cars", Car.Type),
    5. }
    6. }

    作为示例,我们创建2辆汽车并将它们添加到某个用户。

    <project>/start/start.go

    1. import (
    2. "<project>/ent"
    3. "<project>/ent/car"
    4. "<project>/ent/user"
    5. )
    6. func CreateCars(ctx context.Context, client *ent.Client) (*ent.User, error) {
    7. // 创建一辆车型为 "Tesla" 的汽车.
    8. tesla, err := client.Car.
    9. Create().
    10. SetModel("Tesla").
    11. SetRegisteredAt(time.Now()).
    12. Save(ctx)
    13. if err != nil {
    14. return nil, fmt.Errorf("failed creating car: %w", err)
    15. }
    16. log.Println("car was created: ", tesla)
    17. // 创建一辆车型为 "Ford" 的汽车.
    18. ford, err := client.Car.
    19. Create().
    20. SetModel("Ford").
    21. SetRegisteredAt(time.Now()).
    22. Save(ctx)
    23. if err != nil {
    24. return nil, fmt.Errorf("failed creating car: %w", err)
    25. }
    26. log.Println("car was created: ", ford)
    27. // 新建一个用户,将两辆车添加到他的名下
    28. a8m, err := client.User.
    29. Create().
    30. SetAge(30).
    31. SetName("a8m").
    32. AddCars(tesla, ford).
    33. Save(ctx)
    34. if err != nil {
    35. return nil, fmt.Errorf("failed creating user: %w", err)
    36. }
    37. log.Println("user was created: ", a8m)
    38. return a8m, nil
    39. }

    想要查询 cars 关系怎么办? 请参考以下:

    <project>/start/start.go

    1. import (
    2. "log"
    3. "<project>/ent"
    4. "<project>/ent/car"
    5. )
    6. func QueryCars(ctx context.Context, a8m *ent.User) error {
    7. cars, err := a8m.QueryCars().All(ctx)
    8. if err != nil {
    9. return fmt.Errorf("failed querying user cars: %w", err)
    10. }
    11. log.Println("returned cars:", cars)
    12. // What about filtering specific cars.
    13. ford, err := a8m.QueryCars().
    14. Where(car.Model("Ford")).
    15. Only(ctx)
    16. if err != nil {
    17. return fmt.Errorf("failed querying user cars: %w", err)
    18. }
    19. log.Println(ford)
    20. return nil
    21. }

    假定我们有一个 Car 对象,我们想要得到它的所有者;即这辆汽车所属的用户。 为此,我们有另一种“逆向”的边,通过 edge.From 函数定义。

    在上图中新建的边是隐性的,以强调我们不会在数据库中创建另一个关联。 它只是真正边(关系) 的回溯。

    让我们把一个名为 owner 的逆向边添加到 Car 的结构中, 在 User 结构中引用它到 cars 关系 然后运行 go generate ./ent

    <project>/ent/schema/car.go

    <project>/start/start.go

    1. import (
    2. "fmt"
    3. "log"
    4. "<project>/ent"
    5. "<project>/ent/user"
    6. )
    7. func QueryCarUsers(ctx context.Context, a8m *ent.User) error {
    8. cars, err := a8m.QueryCars().All(ctx)
    9. if err != nil {
    10. return fmt.Errorf("failed querying user cars: %w", err)
    11. }
    12. // Query the inverse edge.
    13. for _, ca := range cars {
    14. owner, err := ca.QueryOwner().Only(ctx)
    15. if err != nil {
    16. return fmt.Errorf("failed querying car %q owner: %w", ca.Model, err)
    17. }
    18. log.Printf("car %q owner: %q\n", ca.Model, owner.Name)
    19. }
    20. return nil
    21. }

    创建你的第二条边

    继续我们的例子,在用户和组之间创建一个M2M(多对多)关系。

    er-group-users

    正如您所看到的,每个组实体可以拥有许多用户,而一个用户可以关联到多个组。 一个简单的 “多对多 “关系。 在上面的插图中,Group结构是users关系的所有者, 而User实体对这个关系有一个名为groups的反向引用。 让我们在结构中定义这种关系。

    <project>/ent/schema/group.go

    1. // Edges of the Group.
    2. func (Group) Edges() []ent.Edge {
    3. return []ent.Edge{
    4. edge.To("users", User.Type),
    5. }
    6. }

    <project>/ent/schema/user.go

    1. // Edges of the User.
    2. func (User) Edges() []ent.Edge {
    3. return []ent.Edge{
    4. edge.To("cars", Car.Type),
    5. // and reference it to the "users" edge (in Group schema)
    6. // explicitly using the `Ref` method.
    7. edge.From("groups", Group.Type).
    8. Ref("users"),
    9. }

    我们在schema目录上运行ent来重新生成资源文件。

    1. go generate ./ent

    进行第一次图遍历

    为了进行我们的第一次图遍历,我们需要生成一些数据(节点和边,或者说,实体和关系)。 让我们创建如下图所示的框架:

    <project>/start/start.go

    1. func CreateGraph(ctx context.Context, client *ent.Client) error {
    2. // First, create the users.
    3. a8m, err := client.User.
    4. Create().
    5. SetAge(30).
    6. SetName("Ariel").
    7. Save(ctx)
    8. if err != nil {
    9. return err
    10. }
    11. neta, err := client.User.
    12. Create().
    13. SetAge(28).
    14. SetName("Neta").
    15. Save(ctx)
    16. if err != nil {
    17. return err
    18. }
    19. // Then, create the cars, and attach them to the users in the creation.
    20. err = client.Car.
    21. Create().
    22. SetModel("Tesla").
    23. SetRegisteredAt(time.Now()). // ignore the time in the graph.
    24. SetOwner(a8m). // attach this graph to Ariel.
    25. Exec(ctx)
    26. if err != nil {
    27. return err
    28. }
    29. err = client.Car.
    30. Create().
    31. SetModel("Mazda").
    32. SetRegisteredAt(time.Now()). // ignore the time in the graph.
    33. SetOwner(a8m). // attach this graph to Ariel.
    34. Exec(ctx)
    35. if err != nil {
    36. return err
    37. }
    38. err = client.Car.
    39. Create().
    40. SetModel("Ford").
    41. SetRegisteredAt(time.Now()). // ignore the time in the graph.
    42. SetOwner(neta). // attach this graph to Neta.
    43. Exec(ctx)
    44. if err != nil {
    45. return err
    46. }
    47. // Create the groups, and add their users in the creation.
    48. err = client.Group.
    49. Create().
    50. SetName("GitLab").
    51. AddUsers(neta, a8m).
    52. Exec(ctx)
    53. if err != nil {
    54. return err
    55. }
    56. err = client.Group.
    57. Create().
    58. SetName("GitHub").
    59. AddUsers(a8m).
    60. Exec(ctx)
    61. if err != nil {
    62. return err
    63. }
    64. log.Println("The graph was created successfully")
    65. return nil
    66. }

    现在我们有一个含数据的图,我们可以对它运行一些查询:

    1. 获取名为 “GitHub” 的群组内所有用户的汽车。 <project>/start/start.go

      1. import (
      2. "log"
      3. "<project>/ent"
      4. "<project>/ent/group"
      5. )
      6. func QueryGithub(ctx context.Context, client *ent.Client) error {
      7. cars, err := client.Group.
      8. Query().
      9. Where(group.Name("GitHub")). // (Group(Name=GitHub),)
      10. QueryUsers(). // (User(Name=Ariel, Age=30),)
      11. QueryCars(). // (Car(Model=Tesla, RegisteredAt=<Time>), Car(Model=Mazda, RegisteredAt=<Time>),)
      12. All(ctx)
      13. if err != nil {
      14. return fmt.Errorf("failed getting cars: %w", err)
      15. }
      16. log.Println("cars returned:", cars)
      17. // Output: (Car(Model=Tesla, RegisteredAt=<Time>), Car(Model=Mazda, RegisteredAt=<Time>),)
      18. return nil
      19. }
    2. 修改上面的查询,从用户 Ariel 开始遍历。

      <project>/start/start.go

      1. import (
      2. "log"
      3. "<project>/ent"
      4. "<project>/ent/car"
      5. )
      6. func QueryArielCars(ctx context.Context, client *ent.Client) error {
      7. // Get "Ariel" from previous steps.
      8. a8m := client.User.
      9. Query().
      10. Where(
      11. user.HasCars(),
      12. user.Name("Ariel"),
      13. ).
      14. OnlyX(ctx)
      15. cars, err := a8m. // Get the groups, that a8m is connected to:
      16. QueryGroups(). // (Group(Name=GitHub), Group(Name=GitLab),)
      17. QueryUsers(). // (User(Name=Ariel, Age=30), User(Name=Neta, Age=28),)
      18. QueryCars(). //
      19. Where( //
      20. car.Not( // Get Neta and Ariel cars, but filter out
      21. car.Model("Mazda"), // those who named "Mazda"
      22. ), //
      23. ). //
      24. All(ctx)
      25. if err != nil {
      26. return fmt.Errorf("failed getting cars: %w", err)
      27. }
      28. log.Println("cars returned:", cars)
      29. // Output: (Car(Model=Tesla, RegisteredAt=<Time>), Car(Model=Ford, RegisteredAt=<Time>),)
      30. return nil