在上一篇关于快速使用ent orm的笔记中,咱们再最开始使用entc init User
建立schema,在ent orm 中的schema 其实就是数据库模型,在schema中咱们能够经过Fields 定义数据库中表的字段信息;经过Edges 定义表之间的关系信息;经过Index 定义字段的索引信息等等,这篇文章会整理一下关于ent orm 中如何使用这些。mysql
备注:文章中的全部代码在github.com/peanut-cc/ent_orm_notes
git
当咱们执行 entc init User
以后,会在当前目录下生成一个ent目录,在该目录下有一个schema目录,默认状况下schema/user.go文件以下:github
package schema import "github.com/facebook/ent" // User holds the schema definition for the User entity. type User struct { ent.Schema } // Fields of the User. func (User) Fields() []ent.Field { return nil } // Edges of the User. func (User) Edges() []ent.Edge { return nil }
若是要对user 这表添加字段,须要在Fileds方法中添加以下所示:sql
// Fields of the User. func (User) Fields() []ent.Field { return []ent.Field{ field.Int("age"), field.String("username"). Unique(), field.Time("created_at"). Default(time.Now), field.Float32("salary"). Optional(), } }
注意: 默认状况下,全部字段都是必填字段,能够使用Optional方法将其设置为optional。shell
下面的数据类型都是支持的:数据库
// Fields of the User. func (User) Fields() []ent.Field { return []ent.Field{ field.Int("age"), field.String("username"). Unique(), field.Time("created_at"). Default(time.Now), field.Float32("salary"). Optional(), field.Bool("active"). Default(false), field.JSON("strings", []string{}). Optional(), field.Enum("state"). Values("on", "off"). Optional(), } }
数据库表的id字段,默认是内置的,不须要单独添加,其类型默认为int, 并在数据库中自动递增,json
为了将id配置为在全部表中惟一,须要在schema migration的时候使用WithGlobalUniqueIDc#
若是须要对id字段进行其余配置,或者想要使用UUID格式存id,则须要覆盖id的配置。tcp
// Fields of the User. func (User) Fields() []ent.Field { return []ent.Field{ field.Int("id"). StructTag(`json:"oid,omitempty"`), } }
// Fields of the User. func (User) Fields() []ent.Field { return []ent.Field{ field.UUID("id", uuid.UUID{}), } }
// Fields of the User. func (User) Fields() []ent.Field { return []ent.Field{ field.String("id"). MaxLen(25). NotEmpty(). Unique(). Immutable(), } }
每一个数据库都有本身的从go的数据类型到数据库类型的映射,例如,Mysql 在数据库中将float64字段建立为双精度的。ent orm 有一个选项参数能够使用SchemaType 方法覆盖默认行为ide
// Fields of the Card. func (Card) Fields() []ent.Field { return []ent.Field{ field.Float("amount"). SchemaType(map[string]string{ dialect.MySQL: "decimal(6,2)", // Override MySQL. dialect.Postgres: "numeric", // Override Postgres. }), } }
字段的默认类型是基本的Go数据类型,例如,对于字符串字段,类型为string, 对于时间字段,类型为time.Time
GoType 方法提供了一个选项,能够使用自定义类型替换默认的ent类型。但自定义类型必须是能够转换为Go的基本类型的类型,或者实现了ValueScanner接口的类型
// Fields of the Card. func (Card) Fields() []ent.Field { return []ent.Field{ field.Float("amount"). GoType(Amount(0)), field.String("name"). Optional(). // A ValueScanner type. GoType(&sql.NullString{}), } }
Non-unique 的字段能够经过Default 和 UpdateDefault方法设置默认值
// Fields of the Group. func (Group) Fields() []ent.Field { return []ent.Field{ field.Time("created_at"). Default(time.Now), field.Time("updated_at"). Default(time.Now). UpdateDefault(time.Now), } }
关于字段的validator是经过 func(T) error 函数,该函数使用Validate方法在schema中定义,并在建立或更新schema的时候应用于字段的校验
字段的validator支持的类型有string 和全部的数字类型
// Fields of the Group. func (Group) Fields() []ent.Field { return []ent.Field{ field.String("name"). Match(regexp.MustCompile("[a-zA-Z_]+$")). Validate(func(s string) error { if strings.ToLower(s) == s { return errors.New("group name must begin with uppercase") } return nil }), } }
ent orm 提供了一些内置的validators, 以下:
Numeric types:
Positive() - Positive adds a minimum value validator with the value of 1
Negative() - Negative adds a maximum value validator with the value of -1
NonNegative() - NonNegative adds a minimum value validator with the value of 0
Min(i) - Validate that the given value is > i.
Max(i) - Validate that the given value is < i.
Range(i, j) - Validate that the given value is within the range [i, j].
string:
MinLen(i)
MaxLen(i)
Match(regexp.Regexp)
可选字段是在建立的时候不是必传的字段,并将在数据库设置为可为空的字段
默认状况下,字段都是必填字段
有时候你可能但愿区分字段的零值和nil,如数据库的某列包含0 或者NULL,Nillable选项正是为此而存在的.
若是有一个类型为T的字段设置为Nillable ,在经过go generate 生成代码的时候的时候生成的struct 中改字段的类型是*T, 若是数据库中该字段是NULL, 那么在ent orm的查询结果中就是nil, 不然对于没有设置Nillable的字段,若是数据库中字段的值是NULl,返回的则是改字段的零值
// Fields of the User. func (User) Fields() []ent.Field { return []ent.Field{ field.String("required_name"), field.String("optional_name").Optional(), field.String("nilable_name").Optional().Nillable(), field.String("nilable_name2").Optional().Nillable(), field.Int("age").Optional(), field.Int("age2").Optional().Nillable(), } }
咱们经过以下代码进行数据的建立和查询,这里分别建立了两条数据,第一条数据的设置SetOptionalName,SetNilableName 的字段都没有设置内容,第二次的时候都设置了内容
package main import ( "context" "log" "github.com/peanut-cc/ent_orm_notes/schema_notes/ent/user" _ "github.com/go-sql-driver/mysql" "github.com/peanut-cc/ent_orm_notes/schema_notes/ent" ) func main() { client, err := ent.Open("mysql", "root:123456@tcp(10.211.55.3:3306)/schema_notes?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) } client.User.Create().SetRequiredName("peanut").Save(ctx) client.User.Create().SetRequiredName("syncd"). SetOptionalName("option_name"). SetNilableName("nil_name"). SetNilableName2("nil_name2"). SetAge(18). SetAge2(20). SaveX(ctx) u := client.User.Query().Where(user.RequiredNameEQ("peanut")).OnlyX(ctx) log.Printf("required_name is:%v option_name is:%v nil_name is:%v nil_name2 is:%v age is :%v age2 is:%v\n", u.RequiredName, u.OptionalName, u.NilableName, u.NilableName2, u.Age, u.Age2) u2 := client.User.Query().Where(user.RequiredNameEQ("syncd")).OnlyX(ctx) log.Printf("required_name is:%v option_name is:%v nil_name is:%v nil_name2 is:%v age is :%v age2 is:%v\n", u2.RequiredName, u2.OptionalName, u2.NilableName, u2.NilableName2, u2.Age, u2.Age2) }
下面是数据的打印结果:
2020/08/26 20:39:47 required_name is:peanut option_name is: nil_name is:<nil> nil_name2 is:<nil> age is :0 age2 is:<nil> 2020/08/26 20:39:47 required_name is:syncd option_name is:option_name nil_name is:0xc000200580 nil_name2 is:0xc000200590 age is :18 age2 is:0xc00020c4d8
不可变字段,是只能在建立的时候设置值
// Fields of the user. func (User) Fields() []ent.Field { return []ent.Field{ field.String("name"), field.Time("created_at"). Default(time.Now). Immutable(), } }
能够使用Unique方法给字段设置惟一索引。 注意:惟一因此字段不能具备默认值
// Fields of the user. func (User) Fields() []ent.Field { return []ent.Field{ field.String("name"), field.String("nickname"). Unique(), } }
能够使用StorageKey方法配置自定义存储名称。在SQL中映射为列名
// Fields of the user. func (User) Fields() []ent.Field { return []ent.Field{ field.String("name"). StorageKey(`old_name"`), } }
能够在多个字段和一些关系表中建立索引
能够使用StructTag方法将自定义struct tag添加到生成的实体中。
请注意,若是未提供此选项,或者提供的该选项不包含json标记,则默认json标记将与字段名称一块儿添加。
// Fields of the user. func (User) Fields() []ent.Field { return []ent.Field{ field.String("name"). StructTag(`gqlgen:"gql_name"`), } }
能够使用Sensitive方法将字符串字段定义为Sensitive Fields。
Sensitive Fields不会被打印,而且在编码时将被忽略。
请注意,Sensitive Fields不能具备struct标记。
// Fields of the user. func (User) Fields() []ent.Field { return []ent.Field{ field.String("password"). Sensitive(), } }
在代码生成中,Annotations用于将任意元数据附加到字段对象。模板扩展能够检索这个元数据并在它们的模板中使用它。注意,元数据对象必须可序列化为 JSON 原始值(例如,struct、 map 或 slice)。
// Fields of the user. func (User) Fields() []ent.Field { return []ent.Field{ field.Time("creation_date"). Annotations(entgql.Annotation{ OrderField: "CREATED_AT", }), } }
Edges 也理解为表之间的association,一般指的咱们表之间的一对多,多对多关系等。
ent/schema/pet.go
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/edge" "github.com/facebook/ent/schema/field" ) // Pet holds the schema definition for the Pet entity. type Pet struct { ent.Schema } // Fields of the Pet. func (Pet) Fields() []ent.Field { return []ent.Field{ field.String("name"), } } // Edges of the Pet. func (Pet) Edges() []ent.Edge { return []ent.Edge{ edge.From("owner", User.Type). Ref("pets"). Unique(), } }
ent/schema/user.go
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/edge" "github.com/facebook/ent/schema/field" ) // User holds the schema definition for the User entity. type User struct { ent.Schema } // Fields of the User. func (User) Fields() []ent.Field { return []ent.Field{ field.String("name"), field.Int("age"), } } // Edges of the User. func (User) Edges() []ent.Edge { return []ent.Edge{ edge.To("pets", Pet.Type), edge.From("groups", Group.Type).Ref("users"), } }
ent/schema/group.go
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/edge" "github.com/facebook/ent/schema/field" ) // Group holds the schema definition for the Group entity. type Group struct { ent.Schema } // Fields of the Group. func (Group) Fields() []ent.Field { return []ent.Field{ field.String("name"), } } // Edges of the Group. func (Group) Edges() []ent.Edge { return []ent.Edge{ edge.To("users", User.Type), } }
在上面的关系中,一个用户能够有多个宠物,可是一个宠物只能属于一个用户。因此这里对于宠物来讲是一对一的关系,对于用户来讲是多对一关系。
咱们查看一下建立的pets表的信息:
CREATE TABLE `pets` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(255) COLLATE utf8mb4_bin NOT NULL, `user_pets` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`), KEY `pets_users_pets` (`user_pets`), CONSTRAINT `pets_users_pets` FOREIGN KEY (`user_pets`) REFERENCES `users` (`id`) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
由于在上述关系中:用户和宠物之间是一对多关系,因此这里使用的edge.To
;
而对宠物来讲是一对一的关系,因此这里使用edge.From
的Ref
edge.To
和edge.From
是建立表关系的两个方法
在这个例子中,设定一个用户只能有一张信用卡,而一个信用卡也只能属于一个用户。
ent/schema/user.go
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/edge" "github.com/facebook/ent/schema/field" ) // User holds the schema definition for the User entity. type User struct { ent.Schema } // Fields of the User. func (User) Fields() []ent.Field { return []ent.Field{ field.String("name"), field.Int("age"), } } // Edges of the User. func (User) Edges() []ent.Edge { return []ent.Edge{ edge.To("card", Card.Type).Unique(), } }
ent/schema/card.go
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/edge" "github.com/facebook/ent/schema/field" ) // Card holds the schema definition for the Card entity. type Card struct { ent.Schema } // Fields of the Card. func (Card) Fields() []ent.Field { return []ent.Field{ field.String("number"), field.Time("expired"), } } // Edges of the Card. func (Card) Edges() []ent.Edge { return []ent.Edge{ edge.From("owner", User.Type). Ref("card"). Unique(). // We add the "Required" method to the builder // to make this edge required on entity creation. // i.e. Card cannot be created without its owner. Required(), } }
在这个例子中用户和宠物之间是一对多关系,每一个用户能够有多个宠物,一个宠物只有一个主人
ent/schema/user.go
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/edge" "github.com/facebook/ent/schema/field" ) // User holds the schema definition for the User entity. type User struct { ent.Schema } // Fields of the User. func (User) Fields() []ent.Field { return []ent.Field{ field.String("name"), } } // Edges of the User. func (User) Edges() []ent.Edge { return []ent.Edge{ edge.To("pets", Pet.Type), } }
ent/schema/pet.go
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/edge" "github.com/facebook/ent/schema/field" ) // Pet holds the schema definition for the Pet entity. type Pet struct { ent.Schema } // Fields of the Pet. func (Pet) Fields() []ent.Field { return []ent.Field{ field.String("name"), } } // Edges of the Pet. func (Pet) Edges() []ent.Edge { return []ent.Edge{ edge.From("owner", User.Type). Ref("pets"). Unique(), } }
相关的查询以下代码:
package main import ( "context" "fmt" "log" _ "github.com/go-sql-driver/mysql" "github.com/peanut-cc/ent_orm_notes/one_to_many/ent" ) func main() { client, err := ent.Open("mysql", "root:123456@tcp(192.168.1.100:3306)/one_to_one?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) error { // Create the 2 pets. pedro, err := client.Pet. Create(). SetName("pedro"). Save(ctx) if err != nil { return fmt.Errorf("creating pet: %v", err) } lola, err := client.Pet. Create(). SetName("lola"). Save(ctx) if err != nil { return fmt.Errorf("creating pet: %v", err) } // Create the user, and add its pets on the creation. // 建立用户,并添加用户和宠物的关系 a8m, err := client.User. Create(). SetName("a8m"). AddPets(pedro, lola). Save(ctx) if err != nil { return fmt.Errorf("creating user: %v", err) } fmt.Println("User created:", a8m)shell // Output: User(id=1, age=30, name=a8m) // Query the owner. Unlike `Only`, `OnlyX` panics if an error occurs. // 根据宠物反向查询所属的用户 owner := pedro.QueryOwner().OnlyX(ctx) fmt.Println(owner.Name) // Output: a8m // Traverse the sub-graph. Unlike `Count`, `CountX` panics if an error occurs. // 根据宠物反向查询用户,并查询该用户有多少宠物 count := pedro. QueryOwner(). // a8m QueryPets(). // pedro, lola CountX(ctx) // count fmt.Println(count) // Output: 2 return nil }
在这个例子中,用户和组之间是多对多关系,每一个组有多个用户,每一个用户也能够加入多个组
ent/schema/group.go
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/edge" "github.com/facebook/ent/schema/field" ) // Group holds the schema definition for the Group entity. type Group struct { ent.Schema } // Fields of the Group. func (Group) Fields() []ent.Field { return []ent.Field{ field.String("name"), } } // Edges of the Group. func (Group) Edges() []ent.Edge { return []ent.Edge{ edge.To("users", User.Type), } }
ent/schema/user.go
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/edge" "github.com/facebook/ent/schema/field" ) // User holds the schema definition for the User entity. type User struct { ent.Schema } // Fields of the User. func (User) Fields() []ent.Field { return []ent.Field{ field.String("name"), } } // Edges of the User. func (User) Edges() []ent.Edge { return []ent.Edge{ edge.From("groups", Group.Type).Ref("users"), } }
这个时候,会生成第三张表,group_users表,查看表的信息以下:
CREATE TABLE `group_users` ( `group_id` bigint(20) NOT NULL, `user_id` bigint(20) NOT NULL, PRIMARY KEY (`group_id`,`user_id`), KEY `group_users_user_id` (`user_id`), CONSTRAINT `group_users_group_id` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE, CONSTRAINT `group_users_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
经常使用的查询方法:
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/edge" "github.com/facebook/ent/schema/field" ) // User holds the schema definition for the User entity. type User struct { ent.Schema } // Fields of the User. func (User) Fields() []ent.Field { return []ent.Field{ field.String("name"), } } // Edges of the User. func (User) Edges() []ent.Edge { return []ent.Edge{ edge.From("groups", Group.Type).Ref("users"), } }
这种关系其实也挺常见的,如咱们微博帐户,不一样帐户之间能够相关关注
package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/edge" "github.com/facebook/ent/schema/field" ) // User holds the schema definition for the User entity. type User struct { ent.Schema } // Fields of the User. func (User) Fields() []ent.package schema import ( "github.com/facebook/ent" "github.com/facebook/ent/schema/edge" "github.com/facebook/ent/schema/field" ) // User holds the schema definition for the User entity. type User struct { ent.Schema } // Fields of the User. func (User) Fields() []ent.Field { return []ent.Field{ field.String("name"), } } // Edges of the User. func (User) Edges() []ent.Edge { return []ent.Edge{ edge.To("following", User.Type). From("followers"), } } Field { return []ent.Field{ field.String("name"), } } // Edges of the User. func (User) Edges() []ent.Edge { return []ent.Edge{ edge.To("following", User.Type). From("followers"), } }
这样会生成一个user_following表,表信息为:
CREATE TABLE `user_following` ( `user_id` bigint(20) NOT NULL, `follower_id` bigint(20) NOT NULL, PRIMARY KEY (`user_id`,`follower_id`), KEY `user_following_follower_id` (`follower_id`), CONSTRAINT `user_following_follower_id` FOREIGN KEY (`follower_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, CONSTRAINT `user_following_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
在这里和在Fields中一样也有Required
StorageKey
Indexes
Annotations
用法基本同样,这里再也不说明