Skip to main content

Transactions

Starting A Transaction​

// GenTx generates group of entities in a transaction.
func GenTx(ctx context.Context, client *ent.Client) error {
tx, err := client.Tx(ctx)
if err != nil {
return fmt.Errorf("starting a transaction: %w", err)
}
hub, err := tx.Group.
Create().
SetName("Github").
Save(ctx)
if err != nil {
return rollback(tx, fmt.Errorf("failed creating the group: %w", err))
}
// Create the admin of the group.
dan, err := tx.User.
Create().
SetAge(29).
SetName("Dan").
AddManage(hub).
Save(ctx)
if err != nil {
return rollback(tx, err)
}
// Create user "Ariel".
a8m, err := tx.User.
Create().
SetAge(30).
SetName("Ariel").
AddGroups(hub).
AddFriends(dan).
Save(ctx)
if err != nil {
return rollback(tx, err)
}
fmt.Println(a8m)
// Output:
// User(id=2, age=30, name=Ariel)

// Commit the transaction.
return tx.Commit()
}

// rollback calls to tx.Rollback and wraps the given error
// with the rollback error if occurred.
func rollback(tx *ent.Tx, err error) error {
if rerr := tx.Rollback(); rerr != nil {
err = fmt.Errorf("%w: %v", err, rerr)
}
return err
}

You must call Unwrap() if you are querying edges off of a created entity after a successful transaction (example: a8m.QueryGroups()). Unwrap restores the state of the underlying client embedded within the entity to a non-transactable version.

Note

Calling Unwrap() on a non-transactional entity (i.e., after a transaction has been committed or rolled back) will cause a panic.

The full example exists in GitHub.

Transactional Client​

Sometimes, you have an existing code that already works with *ent.Client, and you want to change it (or wrap it) to interact with transactions. For these use cases, you have a transactional client. An *ent.Client that you can get from an existing transaction.

// WrapGen wraps the existing "Gen" function in a transaction.
func WrapGen(ctx context.Context, client *ent.Client) error {
tx, err := client.Tx(ctx)
if err != nil {
return err
}
txClient := tx.Client()
// Use the "Gen" below, but give it the transactional client; no code changes to "Gen".
if err := Gen(ctx, txClient); err != nil {
return rollback(tx, err)
}
return tx.Commit()
}

// Gen generates a group of entities.
func Gen(ctx context.Context, client *ent.Client) error {
// ...
return nil
}

The full example exists in GitHub.

Best Practices​

Reusable function that runs callbacks in a transaction:

func WithTx(ctx context.Context, client *ent.Client, fn func(tx *ent.Tx) error) error {
tx, err := client.Tx(ctx)
if err != nil {