• ent orm笔记4---Code Generation


    在前面几篇文章中,我们经常使用的可能就是entc这个命令了,entc这个工具给带来了很多功能,这篇文章主要整理关于ent orm 中Code Generation

    之前的例子中有个知识点少整理了,就是关于如果我们想要看orm在执行过程中详细原生sql语句是可以开启Debug看到的,代码如下:

    client, err := ent.Open("mysql", "root:123456@tcp(10.211.55.3:3306)/graph_traversal?parseTime=True",ent.Debug())
    

    序言

    Initialize A New Schema

    通过类似如下命令可以生成Schema 模板:

    entc init User Pet
    

    init 将在ent/schema 目录下创建两个schema user.go 和 pet.go ,如果ent目录不存在,则会创建

    Generate Assets

    在添加了fields 和 edges 后,可以在项目的根目录运行entc generate 或者使用go generate 生成代码

    go generate ./ent
    

    Generate 命令生成以下内容:

    • 用于与graph 交互的Client 和Tx对象
    • schema 的CRUD生成器
    • 每个schema类型的Entity对象
    • 用于与构建交互的常量和断言
    • SQL方言的migrate 包

    Version Compatibility Between entc And ent

    这里主要是关于在项目中使用ent 的时候ent的版本要和entc的包的版本相同,并且项目中使用Go modules 进行包管理

    Code Generation Options

    要了解更多关于 codegen 选项的信息,entc generate -h :

    generate go code for the schema directory
    
    Usage:
      entc generate [flags] path
    
    Examples:
      entc generate ./ent/schema
      entc generate github.com/a8m/x
    
    Flags:
          --header string                           override codegen header
      -h, --help                                    help for generate
          --idtype [int int64 uint uint64 string]   type of the id field (default int)
          --storage string                          storage driver to support in codegen (default "sql")
          --target string                           target directory for codegen
          --template strings                        external templates to execute
    
    

    Storage

    entc 可以为 SQL 和 Gremlin 方言生成资产。

    External Templates

    接受要执行的外部 Go 模板。如果模板名称已经由 entc 定义,它将覆盖现有的名称。否则,它将把执行输出写入与模板同名的文件。Flag 格式支持如下文件、目录和 glob:

    entc generate --template <dir-path> --template glob="path/to/*.tmpl" ./ent/schema
    

    更多的信息和例子可以在外部模板文档中找到

    Use entc As A Package

    运行 entc 的另一个选项是将其作为一个包使用,如下所示:

    package main
    
    import (
        "log"
    
        "github.com/facebook/ent/entc"
        "github.com/facebook/ent/entc/gen"
        "github.com/facebook/ent/schema/field"
    )
    
    func main() {
        err := entc.Generate("./schema", &gen.Config{
            Header: "// Your Custom Header",
            IDType: &field.TypeInfo{Type: field.TypeInt},
        })
        if err != nil {
            log.Fatal("running ent codegen:", err)
        }
    }
    

    Schema Description

    如果想要得到我们定义的schema的描述信息,可以通过如下命令:

    entc describe ./ent/schema
    

    以之前的例子中执行效果如下:

    User:
            +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------------------+------------+
            | Field |  Type  | Unique | Optional | Nillable | Default | UpdateDefault | Immutable |       StructTag       | Validators |
            +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------------------+------------+
            | id    | <nil>  | false  | false    | false    | false   | false         | false     | json:"id,omitempty"   |          0 |
            | name  | string | false  | false    | false    | false   | false         | false     | json:"name,omitempty" |          0 |
            +-------+--------+--------+----------+----------+---------+---------------+-----------+-----------------------+------------+
            +-----------+------+---------+-----------+----------+--------+----------+
            |   Edge    | Type | Inverse |  BackRef  | Relation | Unique | Optional |
            +-----------+------+---------+-----------+----------+--------+----------+
            | followers | User | true    | following | M2M      | false  | true     |
            | following | User | false   |           | M2M      | false  | true     |
            +-----------+------+---------+-----------+----------+--------+----------+
    
    

    CRUD API

    Create A New Client

    MySQL

    package main
    
    import (
        "log"
    
        "<project>/ent"
    
        _ "github.com/go-sql-driver/mysql"
    )
    
    func main() {
        client, err := ent.Open("mysql", "<user>:<pass>@tcp(<host>:<port>)/<database>?parseTime=True")
        if err != nil {
            log.Fatal(err)
        }
        defer client.Close()
    }
    

    PostgreSQL

    package main
    
    import (
        "log"
    
        "<project>/ent"
    
        _ "github.com/lib/pq"
    )
    
    func main() {
        client, err := ent.Open("postgres","host=<host> port=<port> user=<user> dbname=<database> password=<pass>")
        if err != nil {
            log.Fatal(err)
        }
        defer client.Close()
    }
    

    SQLite

    package main
    
    import (
        "log"
    
        "<project>/ent"
    
        _ "github.com/mattn/go-sqlite3"
    )
    
    func main() {
        client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
        if err != nil {
            log.Fatal(err)
        }
        defer client.Close()
    }
    

    Gremlin (AWS Neptune)

    package main
    
    import (
        "log"
    
        "<project>/ent"
    )
    
    func main() {
        client, err := ent.Open("gremlin", "http://localhost:8182")
        if err != nil {
            log.Fatal(err)
        }
    }
    

    Create An Entity

    Save a user.

    a8m, err := client.User.    // UserClient.
        Create().               // User create builder.
        SetName("a8m").         // Set field value.
        SetNillableAge(age).    // Avoid nil checks.
        AddGroups(g1, g2).      // Add many edges.
        SetSpouse(nati).        // Set unique edge.
        Save(ctx)               // Create and return.
    

    SaveX a pet; Unlike Save, SaveX panics if an error occurs.

    pedro := client.Pet.    // PetClient.
        Create().           // Pet create builder.
        SetName("pedro").   // Set field value.
        SetOwner(a8m).      // Set owner (unique edge).
        SaveX(ctx)          // Create and return.
    

    Create Many

    Save a bulk of pets

    names := []string{"pedro", "xabi", "layla"}
    bulk := make([]*ent.PetCreate, len(names))
    for i, name := range names {
        bulk[i] = client.Pet.Create().SetName(name).SetOwner(a8m)
    }
    pets, err := client.Pet.CreateBulk(bulk...).Save(ctx)
    

    Update One

    更新一个从数据库返回的entity

    a8m, err = a8m.Update().    // User update builder.
        RemoveGroup(g2).        // Remove specific edge.
        ClearCard().            // Clear unique edge.
        SetAge(30).             // Set field value
        Save(ctx)               // Save and return.
    

    Update By ID

    pedro, err := client.Pet.   // PetClient.
        UpdateOneID(id).        // Pet update builder.
        SetName("pedro").       // Set field name.
        SetOwnerID(owner).      // Set unique edge, using id.
        Save(ctx)               // Save and return.
    

    Update Many

    以断言进行过滤

    n, err := client.User.          // UserClient.
        Update().                   // Pet update builder.
        Where(                      //
            user.Or(                // (age >= 30 OR name = "bar") 
                user.AgeEQ(30),     //
                user.Name("bar"),   // AND
            ),                      //  
            user.HasFollowers(),    // UserHasFollowers()  
        ).                          //
        SetName("foo").             // Set field name.
        Save(ctx)                   // exec and return.
    

    通过edge 断言进行查询

    n, err := client.User.          // UserClient.
        Update().                   // Pet update builder.
        Where(                      // 
            user.HasFriendsWith(    // UserHasFriendsWith (
                user.Or(            //   age = 20
                    user.Age(20),   //      OR
                    user.Age(30),   //   age = 30
                )                   // )
            ),                      //
        ).                          //
        SetName("a8m").             // Set field name.
        Save(ctx)                   // exec and return.
    

    Query The Graph

    获取所有用户的关注者

    users, err := client.User.      // UserClient.
        Query().                    // User query builder.
        Where(user.HasFollowers()). // filter only users with followers.
        All(ctx)                    // query and return.
    

    获取特定用户的所有跟随者; 从graph中的一个节点开始遍历

    users, err := a8m.
        QueryFollowers().
        All(ctx)
    

    获取所有宠物的名字

    names, err := client.Pet.
        Query().
        Select(pet.FieldName).
        Strings(ctx)
    

    获取所有宠物的名字和年龄

    var v []struct {
        Age  int    `json:"age"`
        Name string `json:"name"`
    }
    err := client.Pet.
        Query().
        Select(pet.FieldAge, pet.FieldName).
        Scan(ctx, &v)
    if err != nil {
        log.Fatal(err)
    }
    

    Delete One

    这个用于如果我们已经通过client查询到了一个entity,然后想要删除这条记录:

    err := client.User.
        DeleteOne(a8m).
        Exec(ctx)
    

    Delete by ID.

    err := client.User.
        DeleteOneID(id).
        Exec(ctx)
    

    Delete Many

    使用断言进行删除

    err := client.File.
        Delete().
        Where(file.UpdatedAtLT(date))
        Exec(ctx)
    

    Mutation

    通过 entc init 生成的每个schema 都有自己的mutaion,例如我们通过 entc init User Pet, 在通过go generate ./ent 生成的代码中有 ent/mutation.go

    在该文件中定义了:

    .....
    // UserMutation represents an operation that mutate the Users
    // nodes in the graph.
    type UserMutation struct {
    	config
    	op            Op
    	typ           string
    	id            *int
    	name          *string
    	age           *int
    	addage        *int
    	clearedFields map[string]struct{}
    	done          bool
    	oldValue      func(context.Context) (*User, error)
    }
    
    .....
    
    // PetMutation represents an operation that mutate the Pets
    // nodes in the graph.
    type PetMutation struct {
    	config
    	op            Op
    	typ           string
    	id            *int
    	name          *string
    	age           *int
    	addage        *int
    	clearedFields map[string]struct{}
    	done          bool
    	oldValue      func(context.Context) (*Pet, error)
    }
    

    例如,所有的User builders都共享相同的UserMutaion 对象,左右的builder 类型都继承通用的ent.Mutation接口.

    这里所说的 user builders,拿User schema来说指的是UserCreateUserDeleteUserQueryUserUpdate 对象,go generate 生成的代码中,我们可以到

    ./ent/user_create.go、./ent/user_delete.go、./ent/user_query.go、./ent/user_update.go文件中看到如下定义:

    // ./ent/user_create.go
    
    // UserCreate is the builder for creating a User entity.
    type UserCreate struct {
    	config
    	mutation *UserMutation
    	hooks    []Hook
    }
    
    
    //./ent/user_delete.go
    // UserDelete is the builder for deleting a User entity.
    type UserDelete struct {
    	config
    	hooks      []Hook
    	mutation   *UserMutation
    	predicates []predicate.User
    }
    
    // ./ent/user_query.go
    // UserQuery is the builder for querying User entities.
    type UserQuery struct {
    	config
    	limit      *int
    	offset     *int
    	order      []OrderFunc
    	unique     []string
    	predicates []predicate.User
    	// intermediate query (i.e. traversal path).
    	sql  *sql.Selector
    	path func(context.Context) (*sql.Selector, error)
    }
    
    // ./ent/user_update.go
    // UserUpdate is the builder for updating User entities.
    type UserUpdate struct {
    	config
    	hooks      []Hook
    	mutation   *UserMutation
    	predicates []predicate.User
    }
    

    在下面的例子中,ent.UserCreate 和 ent.UserUpdate 都使用一个通用的方法对age 和name 列进行操作:

    package main
    
    import (
       "context"
       "log"
    
       _ "github.com/go-sql-driver/mysql"
       "github.com/peanut-cc/ent_orm_notes/aboutMutaion/ent"
    )
    
    func main() {
       client, err := ent.Open("mysql", "root:123456@tcp(10.211.55.3:3306)/aboutMutaion?parseTime=True")
       if err != nil {
          log.Fatal(err)
       }
       defer client.Close()
       ctx := context.Background()
       // run the auto migration tool
       if err := client.Schema.Create(ctx); err != nil {
          log.Fatalf("failed creating schema resources:%v", err)
       }
       Do(ctx, client)
    }
    
    func Do(ctx context.Context, client *ent.Client) {
       creator := client.User.Create()
       SetAgeName(creator.Mutation())
       creator.SaveX(ctx)
       updater := client.User.UpdateOneID(1)
       SetAgeName(updater.Mutation())
       updater.SaveX(ctx)
    }
    
    // SetAgeName sets the age and the name for any mutation.
    func SetAgeName(m *ent.UserMutation) {
       m.SetAge(32)
       m.SetName("Ariel")
    }
    

    在某些情况下,你希望对多个不同的类型应用同一个方法,对于这种情况,要么使用通用的ent.Mutation 接口,或者自己实现一个接口,代码如下:

    func Do2(ctx context.Context, client *ent.Client) {
       creator1 := client.User.Create().SetAge(18)
       SetName(creator1.Mutation(), "a8m")
       creator1.SaveX(ctx)
       creator2 := client.Pet.Create().SetAge(16)
       SetName(creator2.Mutation(), "pedro")
       creator2.SaveX(ctx)
    }
    
    // SetNamer wraps the 2 methods for getting
    // and setting the "name" field in mutations.
    type SetNamer interface {
       SetName(string)
       Name() (string, bool)
    }
    
    func SetName(m SetNamer, name string) {
       if _, exist := m.Name(); !exist {
          m.SetName(name)
       }
    }
    

    Graph Traversal

    在这个部分的例子中会使用如下的Graph

    er-traversal-graph

    er-traversal-graph-gopher

    上面的遍历从一个 Group 实体开始,继续到它的 admin (edge) ,继续到它的朋友(edge) ,获取他们的宠物(edge) ,获取每个宠物的朋友(edge) ,并请求它们的主人

    func Traverse(ctx context.Context, client *ent.Client) error {
        owner, err := client.Group.         // GroupClient.
            Query().                        // Query builder.
            Where(group.Name("Github")).    // Filter only Github group (only 1).
            QueryAdmin().                   // Getting Dan.
            QueryFriends().                 // Getting Dan's friends: [Ariel].
            QueryPets().                    // Their pets: [Pedro, Xabi].
            QueryFriends().                 // Pedro's friends: [Coco], Xabi's friends: [].
            QueryOwner().                   // Coco's owner: Alex.
            Only(ctx)                       // Expect only one entity to return in the query.
        if err != nil {
            return fmt.Errorf("failed querying the owner: %v", err)
        }
        fmt.Println(owner)
        // Output:
        // User(id=3, age=37, name=Alex)
        return nil
    }
    

    下面的遍历如何?

    er-traversal-graph-gopher-query

    我们希望得到所有宠物(entities)的所有者(edge)是朋友(edge)的一些群管理员(edge)。

    func Traverse2(ctx context.Context, client *ent.Client) error {
    	pets, err := client.Pet.
    		Query().
    		Where(
    			pet.HasOwnerWith(
    				user.HasFriendsWith(
    					user.HasManage(),
    				),
    			),
    		).
    		All(ctx)
    	if err != nil {
    		return fmt.Errorf("failed querying the pets: %v", err)
    	}
    	fmt.Println(pets)
    	// Output:
    	// [Pet(id=1, name=Pedro) Pet(id=2, name=Xabi)]
    	return nil
    }
    

    上面的查询中,查询所有的宠物,条件是: 宠物要有主人,同时宠物的主人是要有朋友,同时该主人还要属于管理员

    Eager Loading

    ent 支持通过它们的edges 查询,并将关联的entities 添加到返回的对象中

    通过下面的例子理解:

    er-group-users

    查询上面关系中所有用户和它们的宠物,代码如下:

    func edgerLoading(ctx context.Context, client *ent.Client) {
       users, err := client.User.Query().WithPets().All(ctx)
       if err != nil {
          log.Fatalf("user query failed:%v", err)
       }
       log.Println(users)
       for _, u := range users {
          for _, p := range u.Edges.Pets {
             log.Printf("user (%v) -- > Pet (%v)
    ", u.Name, p.Name)
          }
       }
    
    }
    

    完整的代码在:https://github.com/peanut-cc/ent_orm_notes/graph_traversal

    查询的结果如下:

    2020/09/01 20:09:07 [User(id=1, age=29, name=Dan) User(id=2, age=30, name=Ariel) User(id=3, age=37, name=Alex) User(id=4, age=18, name=peanut)]
    2020/09/01 20:09:07 user (Ariel) -- > Pet (Pedro)
    2020/09/01 20:09:07 user (Ariel) -- > Pet (Xabi)
    2020/09/01 20:09:07 user (Alex) -- > Pet (Coco)
    
    

    预加载允许查询多个关联,包括嵌套关联,还可以过滤,排序或限制查询结果,例如:

    func edgerLoading2(ctx context.Context, client *ent.Client) {
       users, err := client.User.
          Query().
          Where(
             user.AgeGT(18),
          ).
          WithPets().
          WithGroups(func(q *ent.GroupQuery) {
             q.Limit(5)
             q.WithUsers().Limit(5)
          }).All(ctx)
       if err != nil {
          log.Fatalf("user query failed:%v", err)
       }
       log.Println(users)
       for _, u := range users {
          for _, p := range u.Edges.Pets {
             log.Printf("user (%v) --> Pet (%v)
    ", u.Name, p.Name)
          }
          for _, g := range u.Edges.Groups {
             log.Printf("user (%v) -- Group (%v)
    ", u.Name, g.Name)
          }
       }
    
    }
    

    每个query-builder都有一个方法列表,其形式为 With<E>(...func(<N>Query))

    <E>代表边缘名称(像WithGroups) ,< N> 代表边缘类型(像GroupQuery)。

    注意,只有 SQL 方言支持这个特性

    Aggregation

    Group By

    按所有用户的姓名和年龄字段分组,并计算其总年龄。

    package main
    
    import (
    	"context"
    	"log"
    
    	"github.com/peanut-cc/ent_orm_notes/groupBy/ent/user"
    
    	_ "github.com/go-sql-driver/mysql"
    
    	"github.com/peanut-cc/ent_orm_notes/groupBy/ent"
    )
    
    func main() {
    	client, err := ent.Open("mysql", "root:123456@tcp(10.211.55.3:3306)/groupBy?parseTime=True",
    		ent.Debug())
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer client.Close()
    	ctx := context.Background()
    	// run the auto migration tool
    	if err := client.Schema.Create(ctx); err != nil {
    		log.Fatalf("failed creating schema resources:%v", err)
    	}
    	GenData(ctx, client)
    	Do(ctx, client)
    
    }
    
    func GenData(ctx context.Context, client *ent.Client) {
    	client.User.Create().SetName("peanut").SetAge(18).SaveX(ctx)
    	client.User.Create().SetName("jack").SetAge(20).SaveX(ctx)
    	client.User.Create().SetName("steve").SetAge(22).SaveX(ctx)
    	client.User.Create().SetName("peanut-cc").SetAge(18).SaveX(ctx)
    	client.User.Create().SetName("jack-dd").SetAge(18).SaveX(ctx)
    }
    
    func Do(ctx context.Context, client *ent.Client) {
    	var v []struct {
    		Name  string `json:"name"`
    		Age   int    `json:"age"`
    		Sum   int    `json:"sum"`
    		Count int    `json:"count"`
    	}
    	client.User.
    		Query().
    		GroupBy(
    			user.FieldName, user.FieldAge,
    		).
    		Aggregate(
    			ent.Count(),
    			ent.Sum(user.FieldAge),
    		).
    		ScanX(ctx, &v)
    	log.Println(v)
    
    }
    

    按一个字段分组,例子如下:

    func Do2(ctx context.Context, client *ent.Client) {
    	names := client.User.Query().GroupBy(user.FieldName).StringsX(ctx)
    	log.Println(names)
    }
    

    Predicates

    Field Predicates

    • Bool:
      • =, !=
    • Numberic:
      • =, !=, >, <, >=, <=,
      • IN, NOT IN
    • Time:
      • =, !=, >, <, >=, <=
      • IN, NOT IN
    • String:
      • =, !=, >, <, >=, <=
      • IN, NOT IN
      • Contains, HasPrefix, HasSuffix
      • ContainsFold, EqualFold (SQL specific)
    • Optional fields:
      • IsNil, NotNil

    Edge Predicates

    HasEdge 例如,查询所有宠物的所有者,使用:

     client.Pet.
          Query().
          Where(pet.HasOwner()).
          All(ctx)
    

    HasEdgeWith

     client.Pet.
          Query().
          Where(pet.HasOwnerWith(user.Name("a8m"))).
          All(ctx)
    

    Negation (NOT)

    client.Pet.
        Query().
        Where(pet.Not(pet.NameHasPrefix("Ari"))).
        All(ctx)
    

    Disjunction (OR)

    client.Pet.
        Query().
        Where(
            pet.Or(
                pet.HasOwner(),
                pet.Not(pet.HasFriends()),
            )
        ).
        All(ctx)
    

    Conjunction (AND)

    client.Pet.
        Query().
        Where(
            pet.And(
                pet.HasOwner(),
                pet.Not(pet.HasFriends()),
            )
        ).
        All(ctx)
    

    Custom Predicates

    如果想编写自己的特定于方言的逻辑,Custom predicates可能很有用。

    pets := client.Pet.
        Query().
        Where(predicate.Pet(func(s *sql.Selector) {
            s.Where(sql.InInts(pet.OwnerColumn, 1, 2, 3))
        })).
        AllX(ctx)
    

    Paging And Ordering

    Limit

    将查询结果限制为 n 个实体。

    users, err := client.User.
        Query().
        Limit(n).
        All(ctx)
    

    Offset

    设置从查询返回的第一个最大数量。

    users, err := client.User.
        Query().
        Offset(10).
        All(ctx)
    

    Ordering

    Order 返回按一个或多个字段的值排序的实体。

    users, err := client.User.Query().
        Order(ent.Asc(user.FieldName)).
        All(ctx)
    

    延伸阅读

  • 相关阅读:
    EffectiveC++ 第4章 设计与声明
    EffectiveC++ 第3章 资源管理
    EffectiveC++ 第2章 构造/析构/赋值运算
    EffectiveC++ 第1章 让自己习惯C++
    C++实现离散数学的关系类,支持传递闭包运算
    Vi编辑器入门
    如何查看jdk的版本是32位还是64位
    跨域服务调用基本概念及解决方法
    解决MyEclipse不编译的方法
    网上拒绝复制方法解决
  • 原文地址:https://www.cnblogs.com/zhaof/p/13599609.html
Copyright © 2020-2023  润新知