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来说指的是UserCreate
、UserDelete
、UserQuery
、UserUpdate
对象,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的更多相关文章
- ent orm笔记1---快速尝鲜
前几天看到消息Facebook孵化的ORM ent转为正式项目,出去好奇,简单体验了一下,使用上自己感觉比GORM好用,于是打算把官方的文档进行整理,也算是学习一下如何使用. 安装 ent orm 需 ...
- ent orm笔记2---schema使用(上)
在上一篇关于快速使用ent orm的笔记中,我们再最开始使用entc init User 创建schema,在ent orm 中的schema 其实就是数据库模型,在schema中我们可以通过Fiel ...
- ent orm笔记2---schema使用(下)
Indexes 索引 在前两篇的文章中,其实对于索引也有一些使用, 这里来详细看一下关于索引的使用 Indexes方法可以在一个或者多个字段上设置索引,以提高数据检索的速度或者定义数据的唯一性 在下面 ...
- Code Generation and T4 Text Templates
Code Generation and T4 Text Templates Code Generation and T4 Text Templates
- Object constraint language for code generation from activity models
一.基本信息 标题:Object Constraint Language for Code Generation from Activity Models 时间:2018 出版源:Informatio ...
- 如何在 PhpStorm 使用 Code Generation?
實務上開發專案時,有一些程式碼會不斷的出現,這時可靠 PhpStorm 的 Code Generation 幫我們產生這些 code snippet,除此之外,我們也可以將自己的 code snipp ...
- 【Spark】Spark性能优化之Whole-stage code generation
一.技术背景 Spark1.x版本中执行SQL语句,使用的是一种最经典,最流行的查询求职策略,该策略主要基于 Volcano Iterator Model(火山迭代模型).一个查询会包含多个Opera ...
- Orchard Core 文档翻译 (二)代码生成模板 Code Generation Templates
Code Generation Templates 翻译原文:https://www.cnblogs.com/Qbit/p/9746457.html转载请注明出处 Orchard Core Templ ...
- 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 ...
随机推荐
- x86架构: 硬件启动过程分析(附引导启动代码)
用户按下开机键,几秒的时间,都经历了啥? 1.cpu各个寄存器赋初始值,cs.base=0xffff0000, eip=0xfff0,其他寄存器都是0,这时cs:ip得到的物理地址:0xfffffff ...
- Vuex详细教程
1.认识Vuex 1.1Vuex是做什么的 官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的 ...
- Java课堂总结
通过重载函数,来实现对不同类型的参数运算.
- Java异常机制,自定义异常以及spring boot异常设计方案
异常机制: 异常概念 异常分类 异常的处理方法 自定义异常 springboot 的异常解决方案
- SQL关联查询
从2张或多张表中,取出有关联的数据 关联查询一共有几种情况: 内连接:INNER JOIN .CROSS JOIN (1)形式一 select 字段列表 from A表 inner join B表 o ...
- java_字节流、字符流的使用方法
字节流 字节输出流[OutputStream] java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地.它定义了字节输出流的基本共性功能方法. p ...
- java_数据类型转换、运算符
数据类型转换 Java程序中要求参与计算的数据,必须要保证数据类型一致,如果数据类型不一致将发生类型的转换. 1.1 自动转换 一个 int 类型变量和一个 byte 类型变量进行加法运算,运算结果, ...
- 微信小程序 progress 进度条 内部圆角及内部条渐变色
微信小程序progress进度条内部圆角及渐变色 <view class="progress-box"> <progress percent="80&q ...
- Vuex mapMutation的基本使用
mapMutation-store中的同步方法 import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default n ...
- SpringBoot --- 自定义 Starter
SpringBoot --- 自定义 Starter 创建 1.需要创建一个新的空工程 2.新的工程需要引入两个模块 一个Maven 模块 作为启动器 一个SpringBoot 模块 作为自动配置模块 ...