Ent ORM note 1 – quick taste

Time:2021-9-28

A few days ago, I saw the news that the ORM ent incubated by Facebook was turned into a formal project. I went out curious and had a simple experience. I felt that it was easier to use than Gorm, so I planned to sort out the official documents and learn how to use them.

install

Ent ORM needs to use the entc command for automatic code generation, so you need to install entc:

go get github.com/facebook/ent/cmd/entc

All code notes about this series will be placed in

github.com/peanut-cc/ent_orm_notes

Quick use

Create schema

Normally, you should execute the following command in your own project root directory. I’m here because there will be multiple examples in the future. In order to make the content of each example independent, you will execute the command in the subdirectory

entc init User

After this command is executed, the following directory structure is generated:

└── quick_user_example
    └── ent
        ├── generate.go
        └── schema
            └── user.go

The content of user.go in the schema directory is also very simple:

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
}

Add fields to schema

Adding fields to the schema is very simple. You only need to generateent/schema/user.goofFieldsMethod. The modified code is as follows:

package schema

import (
   "github.com/facebook/ent"
   "github.com/facebook/ent/schema/field"
)

// User holds the schema definition for the User entity.
type User struct {
   ent.Schema
}

// Fields of the User.
//Used to define fields for the user table
func (User) Fields() []ent.Field {
   return []ent.Field{
      field.Int("age").
         Positive(),
      field.String("name").Default("unknown"),
   }
}

// Edges of the User.
func (User) Edges() []ent.Edge {
   return nil
}

implementgo generate ./entThe code is automatically generated. The directory structure after executing the command is:

└── quick_user_example
    └── ent
        ├── client.go
        ├── config.go
        ├── context.go
        ├── ent.go
        ├── enttest
        │   └── enttest.go
        ├── generate.go
        ├── hook
        │   └── hook.go
        ├── migrate
        │   ├── migrate.go
        │   └── schema.go
        ├── mutation.go
        ├── predicate
        │   └── predicate.go
        ├── privacy
        │   └── privacy.go
        ├── runtime
        │   └── runtime.go
        ├── runtime.go
        ├── schema
        │   └── user.go
        ├── tx.go
        ├── user
        │   ├── user.go
        │   └── where.go
        ├── user_create.go
        ├── user_delete.go
        ├── user.go
        ├── user_query.go
        └── user_update.go

Create table to database

Create tables and simply add and query data:

package main

import (
   "context"
   "fmt"
   "log"

   _ "github.com/go-sql-driver/mysql"
   "github.com/peanut-pg/ent_orm_notes/quick_user_example/ent"
   "github.com/peanut-pg/ent_orm_notes/quick_user_example/ent/user"
)

func main() {
   client, err := ent.Open("mysql", "root:[email protected](192.168.1.104:3306)/ent_orm?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)
   }
   CreateUser(ctx, client)
   peanut, err := QueryUser(ctx, client)
   if err != nil {
      log.Fatalln(err)
   }
   log.Fatalf("query user name is:%v, aget is %v", peanut.Name, peanut.Age)
}

//CREATEUSER create user name = peanout, age = 18
func CreateUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
   u, err := client.User.
      Create().
      SetAge(18).
      SetName("peanut").
      Save(ctx)
   if err != nil {
      return nil, fmt.Errorf("failed creating user: %v", err)
   }
   log.Println("user was created: ", u)
   return u, nil
}

//Queryuser query user where name = peanut
func QueryUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
   u, err := client.User.
      Query().
      Where(user.NameEQ("peanut")).
      // `Only` fails if no user found,
      // or more than 1 user returned.
      Only(ctx)
   if err != nil {
      return nil, fmt.Errorf("failed querying user: %v", err)
   }
   log.Println("user returned: ", u)
   return u, nil
}

Create table relationships

Create the schema of car and group in the same way

entc init Car Group

Give separatelyent/schemaUnder directorycar.goandgroup.goAdd corresponding field information

car.goFile:

package schema

import (
	"github.com/facebook/ent"
	"github.com/facebook/ent/schema/field"
)

// Car holds the schema definition for the Car entity.
type Car struct {
	ent.Schema
}

// Fields of the Car.
func (Car) Fields() []ent.Field {
	return []ent.Field{
		field.String("model"),
		field.Time("registered_at"),
	}
}

// Edges of the Car.
func (Car) Edges() []ent.Edge {
	return nil
}

group.goFile:

package schema

import (
   "regexp"

   "github.com/facebook/ent"
   "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").
         // regexp validation for group name.
         Match(regexp.MustCompile("[a-zA-Z_]+$")),
   }
}

// Edges of the Group.
func (Group) Edges() []ent.Edge {
   return nil
}

The relationship between tables in ENT ORM is realized through the edges method, which we changeent/schema/user.goEdges method in:

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.
//Used to define fields for the user table
func (User) Fields() []ent.Field {
   return []ent.Field{
      field.Int("age").
         Positive(),
      field.String("name").Default("unknown"),
   }
}

// Edges of the User.
//Relationship with cars table
func (User) Edges() []ent.Edge {
   return []ent.Edge{
      edge.To("cars", Car.Type),
   }
}

implementgo generate ./entAutomatically generate the code, and then regenerate the table structure

Then execute in the datashow create table ent_orm.carsTo view the detailed structure of a table:

CREATE TABLE `cars` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `model` varchar(255) COLLATE utf8mb4_bin NOT NULL,
  `registered_at` timestamp NULL DEFAULT NULL,
  `user_cars` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `cars_users_cars` (`user_cars`),
  CONSTRAINT `cars_users_cars` FOREIGN KEY (`user_cars`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

It can be seen that the foreign key relationship established by the imprinting function

Data addition of foreign keys

//Createcars creates Tesla and Ford cars, and the car belongs to user: peanut_ pg
func CreateCars(ctx context.Context, client *ent.Client) (*ent.User, error) {
   // creating new car with model "Tesla".
   tesla, err := client.Car.
      Create().
      SetModel("Tesla").
      SetRegisteredAt(time.Now()).
      Save(ctx)
   if err != nil {
      return nil, fmt.Errorf("failed creating car: %v", err)
   }

   // creating new car with model "Ford".
   ford, err := client.Car.
      Create().
      SetModel("Ford").
      SetRegisteredAt(time.Now()).
      Save(ctx)
   if err != nil {
      return nil, fmt.Errorf("failed creating car: %v", err)
   }
   log.Println("car was created: ", ford)

   // create a new user, and add it the 2 cars.
   peanut_pg, err := client.User.
      Create().
      SetAge(18).
      SetName("peanut_pg").
      //Addcars belongs the car to user peanout_ pg
      AddCars(tesla, ford).
      Save(ctx)
   if err != nil {
      return nil, fmt.Errorf("failed creating user: %v", err)
   }
   log.Println("user was created: ", peanut_pg)
   return peanut_pg, nil
}

Foreign key query

The code is as follows:

package main

import (
   "context"
   "fmt"
   "log"
   "time"

   _ "github.com/go-sql-driver/mysql"
   "github.com/peanut-pg/ent_orm_notes/quick_user_example/ent"
   "github.com/peanut-pg/ent_orm_notes/quick_user_example/ent/car"
   "github.com/peanut-pg/ent_orm_notes/quick_user_example/ent/user"
)

func main() {
   client, err := ent.Open("mysql", "root:[email protected](192.168.1.104:3306)/ent_orm?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)
   }
   peanut_pg, err := QueryUserByName(ctx, client, "peanut_pg")
   if err != nil {
      log.Fatalln(err)
   }
   QueryCars(ctx, peanut_pg)
}

//Queryuserbyname query by name
func QueryUserByName(ctx context.Context, client *ent.Client, name string) (*ent.User, error) {
   u, err := client.User.
      Query().
      Where(user.NameEQ(name)).
      // `Only` fails if no user found,
      // or more than 1 user returned.
      Only(ctx)
   if err != nil {
      return nil, fmt.Errorf("failed querying user: %v", err)
   }
   log.Println("user returned: ", u)
   return u, nil
}

//Querycars query user peanut_ Does PG have Ford
func QueryCars(ctx context.Context, peanut_pg *ent.User) error {
   cars, err := peanut_pg.QueryCars().All(ctx)
   if err != nil {
      return fmt.Errorf("failed querying user cars: %v", err)
   }
   log.Println("returned cars:", cars)

   // what about filtering specific cars.
   ford, err := peanut_pg.QueryCars().
      Where(car.ModelEQ("Ford")).
      Only(ctx)
   if err != nil {
      return fmt.Errorf("failed querying user cars: %v", err)
   }
   log.Println(ford)
   return nil
}

inverse query

In normal queries, we often use some reverse queries. For example, we want to query the user of the car, which needs to be modified at this time

ent/schema/car.goEdges method in:

package schema

import (
	"github.com/facebook/ent"
	"github.com/facebook/ent/schema/edge"
	"github.com/facebook/ent/schema/field"
)

// Car holds the schema definition for the Car entity.
type Car struct {
	ent.Schema
}

// Fields of the Car.
func (Car) Fields() []ent.Field {
	return []ent.Field{
		field.String("model"),
		field.Time("registered_at"),
	}
}

// Edges of the Car.
func (Car) Edges() []ent.Edge {
	return []ent.Edge{
		edge.From("owner", User.Type).
			// create an inverse-edge called "owner" of type `User`
			// and reference it to the "cars" edge (in User schema)
			// explicitly using the `Ref` method.
			Ref("cars").
			// setting the edge to unique, ensure
			// that a car can have only one owner.
			Unique(),
	}
}

First passQueryCarByModelQuery a car with model = Tesla, and thenQueryCarUser Check who owns this car

//Querycarbymodel query car. Model = Tesla
func QueryCarByModel(ctx context.Context, client *ent.Client) (*ent.Car, error) {
	car, err := client.Car.Query().
		Where(car.ModelEQ("Tesla")).
		Only(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed query car")
	}
	return car, nil
}

//Querycaruser queries who car. Model = Tesla belongs to
func QueryCarUser(ctx context.Context, car *ent.Car) error {
	owner, err := car.QueryOwner().Only(ctx)
	if err != nil {
		return fmt.Errorf("failed querying car %q owner:%v", car.Model, err)
	}
	log.Printf("car %q owner: %q\n", car.Model, owner.Name)
	return nil
}

Complex query

Add a user and group relationship to the above relationship and modify them respectivelyent/schema/user.goandent/schema/car.goEdges method

// Edges of the User.
//Relationship with cars table
func (User) Edges() []ent.Edge {
   return []ent.Edge{
      edge.To("cars", Car.Type),
      // create an inverse-edge called "groups" of type `Group`
      // and reference it to the "users" edge (in Group schema)
      // explicitly using the `Ref` method.
      edge.From("groups", Group.Type).
         Ref("users"),
   }
}
// Edges of the Car.
func (Car) Edges() []ent.Edge {
   return []ent.Edge{
      edge.From("owner", User.Type).
         // create an inverse-edge called "owner" of type `User`
         // and reference it to the "cars" edge (in User schema)
         // explicitly using the `Ref` method.
         Ref("cars").
         // setting the edge to unique, ensure
         // that a car can have only one owner.
         Unique(),
   }
}

implementgo generate ./entAutomatic code generation

Generate basic data by the following methods:

//Creategraph create basic data
func CreateGraph(ctx context.Context, client *ent.Client) error {
   // first, create the users.
   a8m, err := client.User.
      Create().
      SetAge(30).
      SetName("Ariel").
      Save(ctx)
   if err != nil {
      return err
   }
   neta, err := client.User.
      Create().
      SetAge(28).
      SetName("Neta").
      Save(ctx)
   if err != nil {
      return err
   }
   // then, create the cars, and attach them to the users in the creation.
   _, err = client.Car.
      Create().
      SetModel("TeslaY").
      SetRegisteredAt(time.Now()). // ignore the time in the graph.
      SetOwner(a8m).               // attach this graph to Ariel.
      Save(ctx)
   if err != nil {
      return err
   }
   _, err = client.Car.
      Create().
      SetModel("TeslaX").
      SetRegisteredAt(time.Now()). // ignore the time in the graph.
      SetOwner(a8m).               // attach this graph to Ariel.
      Save(ctx)
   if err != nil {
      return err
   }
   _, err = client.Car.
      Create().
      SetModel("TeslaS").
      SetRegisteredAt(time.Now()). // ignore the time in the graph.
      SetOwner(neta).              // attach this graph to Neta.
      Save(ctx)
   if err != nil {
      return err
   }
   // create the groups, and add their users in the creation.
   _, err = client.Group.
      Create().
      SetName("GitLab").
      AddUsers(neta, a8m).
      Save(ctx)
   if err != nil {
      return err
   }
   _, err = client.Group.
      Create().
      SetName("GitHub").
      AddUsers(a8m).
      Save(ctx)
   if err != nil {
      return err
   }
   log.Println("The graph was created successfully")
   return nil
}

Three query examples

//Querygithub query all cars of users with group = GitHub
func QueryGithub(ctx context.Context, client *ent.Client) error {
   cars, err := client.Group.
      Query().
      Where(group.Name("GitHub")).
      QueryUsers().
      QueryCars().
      All(ctx)
   if err != nil {
      return fmt.Errorf("failed getting cars:%v", err)
   }
   // cars returned: [Car(id=3, model=TeslaY, registered_at=Tue Aug 25 00:43:55 2020) Car(id=4, model=TeslaX, registered_at=Tue Aug 25 00:43:55 2020)]
   log.Println("cars returned:", cars)
   return nil
}
func QueryArielCars(ctx context.Context, client *ent.Client) error {
   // Get "Ariel" from previous steps.
   a8m := client.User.
      Query().
      Where(
         user.HasCars(),
         user.Name("Ariel"),
      ).
      OnlyX(ctx)
   cars, err := a8m.       			// Get the groups, that a8m is connected to:
            QueryGroups(). 			// (Group(Name=GitHub), Group(Name=GitLab),)
            QueryUsers().  			// (User(Name=Ariel, Age=30), User(Name=Neta, Age=28),)
            QueryCars().   			//
            Where(         			//
         car.Not(                  //  Get Neta and Ariel cars, but filter out
            car.ModelEQ("TeslaX"), //  those who named "Mazda"
         ),
      ).
      All(ctx)
   if err != nil {
      return fmt.Errorf("failed getting cars: %v", err)
   }
   log.Println("cars returned:", cars)
   // Output: (Car(Model=Tesla, RegisteredAt=), Car(Model=Ford, RegisteredAt=),)
   return nil
}
//Querygroupwithusers queries all groups by users
func QueryGroupWithUsers(ctx context.Context, client *ent.Client) error {
   groups, err := client.Group.
      Query().
      Where(group.HasUsers()).
      All(ctx)
   if err != nil {
      return fmt.Errorf("failed getting groups: %v", err)
   }
   log.Println("groups returned:", groups)
   // Output: (Group(Name=GitHub), Group(Name=GitLab),)
   return nil
}

Extended reading

  • https://entgo.io/docs/getting-started/

Recommended Today

SQL exercise 20 – Modeling & Reporting

This blog is used to review and sort out the common topic modeling architecture, analysis oriented architecture and integration topic reports in data warehouse. I have uploaded these reports to GitHub. If you are interested, you can have a lookAddress:https://github.com/nino-laiqiu/TiTanI recorded a relatively complete development process in my hexo blog deployed on GitHub. You can […]