1. Introduction to ent Entity Operations

This tutorial will comprehensively guide you through mastering entity operations in the ent framework, covering the complete process of creating, querying, updating, and deleting entities. It is suitable for beginners to gradually delve into the core functionality of ent.

3. Entity Creation Operation

3.1 Creating a Single Entity

Creating an entity is the fundamental operation for data persistence. Below are the steps to create a single entity object using the ent framework and save it to the database:

  1. First, define the structure and fields of an entity, i.e., define the model of the entity in the schema file.
  2. Run the ent generate command to generate the corresponding entity operation code.
  3. Use the generated Create method to build a new entity, and set the entity's field values through chained calls.
  4. Finally, call the Save method to save the entity to the database.

The following is an example that demonstrates how to create and save a user entity:

package main

import (
    "context"
    "log"
    "entdemo/ent"
)

func main() {
    // Create a Client instance for database interaction
    client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
    if err != nil {
        log.Fatalf("Failed to open the database connection: %v", err)
    }
    defer client.Close()

    // Create a context
    ctx := context.Background()
    
    // Create a user entity using the Client
    a8m, err := client.User.
        Create().
        SetName("a8m").
        Save(ctx)
    if err != nil {
        log.Fatalf("Failed to create the user entity: %v", err)
    }

    // Entity successfully saved to the database
    log.Printf("User entity saved: %v", a8m)
}

In this example, a database client client is created first. Then, the User.Create method is used to set the attributes of the new user, and finally, the Save method is called to save the user to the database.

3.2 Batch Entity Creation

In certain scenarios, there may be a need to create multiple entities, such as during database initialization or bulk data import operations. The ent framework provides the capability to create entities in batches, which offers better performance compared to creating and saving entities individually.

The steps for batch entity creation are as follows:

  1. Use the CreateBulk method instead of the Create method, which allows the creation of multiple entities in a single operation.
  2. Call Create for each entity to be created.
  3. Once all entities have been created, use the Save method to save the entities to the database in bulk.

Below is an example of batch entity creation:

package main

import (
    "context"
    "log"
    "entdemo/ent"
)

func main() {
    client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
    if err != nil {
        log.Fatalf("Failed to open the database connection: %v", err)
    }
    defer client.Close()
    ctx := context.Background()

    // Batch create Pet entities
    pets, err := client.Pet.CreateBulk(
        client.Pet.Create().SetName("pedro").SetOwner(a8m),
        client.Pet.Create().SetName("xabi").SetOwner(a8m),
        client.Pet.Create().SetName("layla").SetOwner(a8m),
    ).Save(ctx)
    if err != nil {
        log.Fatalf("Failed to batch create Pet entities: %v", err)
    }

    log.Printf("Created %d Pet entities in batch\n", len(pets))
}

In this example, a client is created first, and then multiple Pet entities are constructed using the CreateBulk method, setting their names and owner fields. All entities are saved to the database at once when the Save method is called, providing better performance for handling large amounts of data.

4. Entity Query Operations

4.1 Basic Querying

Database querying is the fundamental way to retrieve information. In ent, the Query method is used to start a query. Below are the steps and an example of basic entity querying:

  1. Make sure you have a usable Client instance.
  2. Use Client.Query() or entity helper methods like Pet.Query() to create a query.
  3. Add filtering conditions as needed, such as Where.
  4. Execute the query and retrieve the results by calling the All method.
package main

import (
    "context"
    "log"
    "entdemo/ent"
    "entdemo/ent/user"
)

func main() {
    client, err := ent.Open("sqlite3", "file:ent?cache=shared&_fk=1")
    if err != nil {
        log.Fatalf("Failed to open the database connection: %v", err)
    }
    defer client.Close()
    ctx := context.Background()

    // Query all users named "a8m"
    users, err := client.User.
        Query().
        Where(user.NameEQ("a8m")).
        All(ctx)
    if err != nil {
        log.Fatalf("Failed to query users: %v", err)
    }

    for _, u := range users {
        log.Printf("Found user: %#v\n", u)
    }
}

This example demonstrates how to find all users with the name "a8m".

4.2 Pagination and Sorting

Pagination and sorting are commonly used advanced features when querying, used to control the output order and quantity of data. Here's how to achieve pagination and sorting queries using ent:

  1. Use the Limit method to set the maximum number of results to be returned.
  2. Use the Offset method to skip some of the previous results.
  3. Use the Order method to specify the sorting field and direction.

Here's an example of a pagination and sorting query:

package main

import (
    "context"
    "log"
    "entdemo/ent"
    "entdemo/ent/pet"
)

func main() {
    client, err := ent.Open("sqlite3", "file:ent?cache=shared&_fk=1")
    if err != nil {
        log.Fatalf("Failed to open the database connection: %v", err)
    }
    defer client.Close()
    ctx := context.Background()

    // Query Pets in descending order of age with pagination
    pets, err := client.Pet.
        Query().
        Order(ent.Desc(pet.FieldAge)).
        Limit(10).
        Offset(0).
        All(ctx)
    if err != nil {
        log.Fatalf("Failed to query Pets: %v", err)
    }

    for _, p := range pets {
        log.Printf("Found Pet: %#v\n", p)
    }
}

This example demonstrates how to retrieve the first page, up to 10 records, of pets sorted in descending order by age. By modifying the values of Limit and Offset, you can achieve paging through the entire dataset.

5. Entity Update Operations

5.1 Updating a Single Entity

In many applications, updating entities is an essential part of daily operations. In this section, we will demonstrate how to use the Ent framework to update a single entity in the database.

Firstly, assuming we need to update a user's age, we can utilize the Update method generated by Ent.

// Assuming we already have a user entity 'a8m' and a context 'ctx'

a8m, err := a8m.Update().         // Create a user update builder
    SetAge(30).                   // Set the user's age to 30 years old
    Save(ctx)                     // Perform the save operation and return the result
if err != nil {
    log.Fatalf("Failed to update the user: %v", err)
}

You can also update multiple fields simultaneously:

a8m, err := a8m.Update().
    SetAge(30).                    // Update age
    SetName("Ariel").              // Update name
    AddRank(10).                   // Increase the rank by 10
    Save(ctx)
if err != nil {
    log.Fatalf("Failed to update the user: %v", err)
}

The update operation can be chained, which is very flexible and easy to read. Calling the Save method will perform the update and return the updated entity or an error message.

5.2 Conditional Updates

Ent allows you to perform updates based on conditions. Here's an example where only users that meet specific conditions will be updated.

// Assuming we have a user's `id` and we want to mark that user as done for version `currentVersion`

err := client.Todo.
    UpdateOneID(id).          // Create a builder to update by user ID
    SetStatus(todo.StatusDone).
    AddVersion(1).
    Where(
        todo.Version(currentVersion),  // The update operation is only executed when the current version matches
    ).
    Exec(ctx)
switch {
case ent.IsNotFound(err):
    fmt.Println("Todo not found")
case err != nil:
    fmt.Println("Update error:", err)
}

When using conditional updates, the .Where() method must be involved. This allows you to determine whether the update should be performed based on the current values in the database, which is crucial for ensuring data consistency and integrity.

6. Entity Deletion Operations

6.1 Deleting a Single Entity

Deleting entities is another important function in database operations. Ent framework provides a simple API for performing deletion operations.

The following example demonstrates how to delete a given user entity:

err := client.User.
    DeleteOne(a8m).  // Create a user deletion builder
    Exec(ctx)        // Execute the deletion operation
if err != nil {
    log.Fatalf("Failed to delete user: %v", err)
}

6.2 Conditional Deletion

Similar to update operations, we can also perform deletion operations based on specific conditions. In certain scenarios, we may only want to delete entities that meet specific conditions. Using the .Where() method can define these conditions:

// Suppose we want to delete all files with an update time earlier than a certain date

affected, err := client.File.
    Delete().
    Where(file.UpdatedAtLT(date)).  // Only execute deletion if the file's update time is earlier than the given date
    Exec(ctx)
if err != nil {
    log.Fatalf("Failed to delete files: %v", err)
}

// This operation returns the number of records affected by the deletion operation
fmt.Printf("%d files have been deleted\n", affected)

Using conditional deletion operations ensures precise control over our data operations, ensuring that only entities that truly meet the conditions are deleted. This enhances the security and reliability of database operations.