An Ent extension for batch-creating interconnected entities in a single transaction.
When batch-inserting entities that reference each other, you're forced to manually order your inserts to satisfy foreign key constraints, issue individual queries to get IDs back, and then wire everything together. This quickly becomes tedious as your schema grows.
entds generates a DataStorer and Future types for your Ent schema. You declare all the entities you want to create, wire up relationships between them (even though nothing has been persisted yet), and call Save. entds handles the rest:
- Topological ordering -- entities are inserted in dependency order derived from your schema's required edges.
- Bulk operations -- inserts and edge updates are batched for efficiency.
- Cycle detection -- cyclic required-edge dependencies are caught at code generation time, not at runtime.
Add entds as an Ent extension in your code generation entrypoint:
// ent/entc.go
func main() {
err := entc.Generate("./schema", &gen.Config{}, entc.Extensions(entds.NewEntDS()))
if err != nil {
log.Fatal(err)
}
}Then use the generated DataStorer to create entities:
tx, _ := client.Tx(ctx)
ds := ent.NewDataStorer(tx, 500) // 500 = batch size
// Create entities -- order doesn't matter
group := ds.CreateGroup().SetName("Engineering").Future()
user := ds.CreateUser().SetName("Alice").Future()
// Wire up relationships between futures
user.SetFutureFavGroup(group)
group.AddFutureMembers(user)
// Save resolves dependencies, bulk-inserts, and links edges
ds.Save(ctx)
tx.Commit()Relationships can be set from either side of an edge. If you set a required edge via its inverse, entds redirects the assignment to the owning side automatically:
// These are equivalent -- entds handles the indirection
user.SetFutureFavGroup(group)
group.AddFutureFavoriteOf(user) // redirects to user.SetFutureFavGroup(group)For each entity type in your schema, entds generates:
Future<Type>-- a wrapper that holds a reference to a not-yet-persisted entity and its pending relationships.<type>DS-- the per-type storer responsible for bulk-creating entities and saving their edges.DataStorer-- the top-level coordinator that topologically sorts types and orchestrates the save.DependsOn-- a static dependency map derived from required edges, used for ordering.Future()/SetFuture*/AddFuture*methods -- added to Ent's existing*Createbuilders so you can chain naturally.
This is a prototype. The API works and the approach is sound, but it hasn't been battle-tested in production.