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

    LINK :SP839 星屑幻想 取自 OJ 的名称 小事情...题目大意还是要说的这道题比较有意思,想了一段时间. 给你一张图 这张图给答案带来的贡献是每条边上两个点值得异或 一些点的值已经被确定 ...

  2. Python机器学习——预测分析核心算法PDF高清完整版免费下载|百度云盘|Python基础教程免费电子书

    点击获取提取码:7qi1 在学习和研究机器学习的时候,面临令人眼花缭乱的算法,机器学习新手往往会不知所措.本书从算法和Python语言实现的角度,帮助读者认识机器学习. 本书专注于两类核心的" ...

  3. [转]Java 逃逸分析

    作者:栈长  公众号:Java技术栈 记得几年前有一次栈长去面试,问到了这么一个问题:Java中的对象都是在堆中分配吗?说明为什么! 当时我被问得一脸蒙逼,瞬间被秒杀得体无完肤,当时我压根就不知道他在 ...

  4. 【BZOJ1471】不相交路径 题解(拓扑排序+动态规划+容斥原理)

    题目描述 在有向无环图上给你两个起点和终点分别为$a,b,c,d$.问有几种路径方案使得能从$a$走到$b$的同时能从$c$走到$d$,且两个路径没有交点. $1\leq n\leq 200,1\le ...

  5. c++日志工具spdLog

    c++日志工具spdLog简单使用示例代码 spdlog直接引用头文件就可以使用,这一点还是比较方便的,也是刚入门使用,下面是在源码的示例代码基础上修改测试的代码: #include <cstd ...

  6. Python的10个神奇的技巧

    尽管从表面上看,Python似乎是任何人都可以学习的一种简单语言,但确实如此,许多人可能惊讶地知道一个人可以熟练掌握该语言. Python是其中的一门很容易学习的东西,但可能很难掌握. 在Python ...

  7. JS笔记 语法

    javascript概述 简称为JS,是一款能够运行在JS解释器.引擎中的脚本语言 JS解释器.引擎 JS的运行环境 1.独立安装的js解释器 -nodeJS 2.嵌入在浏览器中的js解释器 JS基于 ...

  8. Linux 中文编码

  9. 数据结构C++实现邻接矩阵存储图

    定义邻接矩阵存储的图类.[实验要求] 1. 创建一个邻接矩阵存储的图: 2. 返回图中指定边的权值: 3. 查找图中某顶点的第一个邻接顶点.某顶点关于另一个顶点的下一个邻接顶点序号: 4. 图的深度优 ...

  10. C#LeetCode刷题之#641-设计循环双端队列(Design Circular Deque)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4132 访问. 设计实现双端队列. 你的实现需要支持以下操作: M ...