ent orm筆記2---schema使用(上)

在上一篇關於快速使用ent orm的筆記中,咱們再最開始使用entc init User 建立schema,在ent orm 中的schema 其實就是數據庫模型,在schema中咱們能夠經過Fields 定義數據庫中表的字段信息;經過Edges 定義表之間的關係信息;經過Index 定義字段的索引信息等等,這篇文章會整理一下關於ent orm 中如何使用這些。mysql

備註:文章中的全部代碼在github.com/peanut-cc/ent_orm_notesgit

Fileds

當咱們執行 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

數據類型

下面的數據類型都是支持的:數據庫

  • All Go numeric types. Like int, uint8, float64, etc.
  • bool
  • string
  • time.Time
  • []byte (only supported by SQL dialects).
  • JSON (only supported by SQL dialects).
  • Enum (only supported by SQL dialects).
// 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字段

數據庫表的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 Type

字段的默認類型是基本的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{}),
	}
}

Default Values 默認值

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),
	}
}

Validators

關於字段的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
			}),
	}
}

內置的 Validators

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)

Optional 可選字段

可選字段是在建立的時候不是必傳的字段,並將在數據庫設置爲可爲空的字段
默認狀況下,字段都是必填字段

Nillable

有時候你可能但願區分字段的零值和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

Immutable 不可變字段

不可變字段,是隻能在建立的時候設置值

// Fields of the user.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("name"),
        field.Time("created_at").
            Default(time.Now).
            Immutable(),
    }
}

Uniqueness 惟一索引

能夠使用Unique方法給字段設置惟一索引。 注意:惟一因此字段不能具備默認值

// Fields of the user.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("name"),
        field.String("nickname").
            Unique(),
    }
}

Storage Key

能夠使用StorageKey方法配置自定義存儲名稱。在SQL中映射爲列名

// Fields of the user.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("name").
            StorageKey(`old_name"`),
    }
}

Indexes 索引

能夠在多個字段和一些關係表中建立索引

Struct Tags

能夠使用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 Fields

能夠使用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

在代碼生成中,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

快速使用

Edges 也理解爲表之間的association,一般指的咱們表之間的一對多,多對多關係等。

er-group-users

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.FromRef

edge.Toedge.From 是建立表關係的兩個方法

一對一關係

er-user-card

在這個例子中,設定一個用戶只能有一張信用卡,而一個信用卡也只能屬於一個用戶。

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(),
   }
}

一對多關係

er-user-pets

在這個例子中用戶和寵物之間是一對多關係,每一個用戶能夠有多個寵物,一個寵物只有一個主人

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
}

多對多關係

er-user-groups

在這個例子中,用戶和組之間是多對多關係,每一個組有多個用戶,每一個用戶也能夠加入多個組

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"),
   }
}

多對多(單張表)

er-following-followers

這種關係其實也挺常見的,如咱們微博帳戶,不一樣帳戶之間能夠相關關注

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 用法基本同樣,這裏再也不說明

延伸閱讀

相關文章
相關標籤/搜索