在前面几篇文章中,我们经常使用的可能就是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

上面的遍历从一个 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
}

下面的遍历如何?

我们希望得到所有宠物(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 添加到返回的对象中

通过下面的例子理解:

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

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)\n", 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)\n", u.Name, p.Name)
}
for _, g := range u.Edges.Groups {
log.Printf("user (%v) -- Group (%v)\n", 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)

延伸阅读

ent orm笔记4---Code Generation的更多相关文章

  1. ent orm笔记1---快速尝鲜

    前几天看到消息Facebook孵化的ORM ent转为正式项目,出去好奇,简单体验了一下,使用上自己感觉比GORM好用,于是打算把官方的文档进行整理,也算是学习一下如何使用. 安装 ent orm 需 ...

  2. ent orm笔记2---schema使用(上)

    在上一篇关于快速使用ent orm的笔记中,我们再最开始使用entc init User 创建schema,在ent orm 中的schema 其实就是数据库模型,在schema中我们可以通过Fiel ...

  3. ent orm笔记2---schema使用(下)

    Indexes 索引 在前两篇的文章中,其实对于索引也有一些使用, 这里来详细看一下关于索引的使用 Indexes方法可以在一个或者多个字段上设置索引,以提高数据检索的速度或者定义数据的唯一性 在下面 ...

  4. Code Generation and T4 Text Templates

    Code Generation and T4 Text Templates Code Generation and T4 Text Templates

  5. Object constraint language for code generation from activity models

    一.基本信息 标题:Object Constraint Language for Code Generation from Activity Models 时间:2018 出版源:Informatio ...

  6. 如何在 PhpStorm 使用 Code Generation?

    實務上開發專案時,有一些程式碼會不斷的出現,這時可靠 PhpStorm 的 Code Generation 幫我們產生這些 code snippet,除此之外,我們也可以將自己的 code snipp ...

  7. 【Spark】Spark性能优化之Whole-stage code generation

    一.技术背景 Spark1.x版本中执行SQL语句,使用的是一种最经典,最流行的查询求职策略,该策略主要基于 Volcano Iterator Model(火山迭代模型).一个查询会包含多个Opera ...

  8. Orchard Core 文档翻译 (二)代码生成模板 Code Generation Templates

    Code Generation Templates 翻译原文:https://www.cnblogs.com/Qbit/p/9746457.html转载请注明出处 Orchard Core Templ ...

  9. Spark SQL includes a cost-based optimizer, columnar storage and code generation to make queries fast.

    https://spark.apache.org/sql/ Performance & Scalability Spark SQL includes a cost-based optimize ...

随机推荐

  1. x86架构: 硬件启动过程分析(附引导启动代码)

    用户按下开机键,几秒的时间,都经历了啥? 1.cpu各个寄存器赋初始值,cs.base=0xffff0000, eip=0xfff0,其他寄存器都是0,这时cs:ip得到的物理地址:0xfffffff ...

  2. Vuex详细教程

    1.认识Vuex 1.1Vuex是做什么的 官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的 ...

  3. Java课堂总结

    通过重载函数,来实现对不同类型的参数运算.

  4. Java异常机制,自定义异常以及spring boot异常设计方案

    异常机制: 异常概念 异常分类 异常的处理方法 自定义异常 springboot 的异常解决方案

  5. SQL关联查询

    从2张或多张表中,取出有关联的数据 关联查询一共有几种情况: 内连接:INNER JOIN .CROSS JOIN (1)形式一 select 字段列表 from A表 inner join B表 o ...

  6. java_字节流、字符流的使用方法

    字节流 字节输出流[OutputStream] java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地.它定义了字节输出流的基本共性功能方法. p ...

  7. java_数据类型转换、运算符

    数据类型转换 Java程序中要求参与计算的数据,必须要保证数据类型一致,如果数据类型不一致将发生类型的转换. 1.1 自动转换 一个 int 类型变量和一个 byte 类型变量进行加法运算,运算结果, ...

  8. 微信小程序 progress 进度条 内部圆角及内部条渐变色

    微信小程序progress进度条内部圆角及渐变色 <view class="progress-box"> <progress percent="80&q ...

  9. Vuex mapMutation的基本使用

    mapMutation-store中的同步方法 import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default n ...

  10. SpringBoot --- 自定义 Starter

    SpringBoot --- 自定义 Starter 创建 1.需要创建一个新的空工程 2.新的工程需要引入两个模块 一个Maven 模块 作为启动器 一个SpringBoot 模块 作为自动配置模块 ...