ℹ️ This feature is not available for Oracle and MySQL providers due to limitations in retrieving generated IDs.
This library supports bulk inserting entire object graphs, including entities with their related navigation properties.
await dbContext.ExecuteBulkInsertAsync(blogs, options =>
{
options.IncludeGraph = true;
});- The library traverses all reachable entities via navigation properties
- Entities are sorted in topological order (parents before children) to respect foreign key constraints
- Each entity type is bulk inserted in dependency order
- Generated IDs (identity columns) are propagated to foreign key properties
- Many-to-many join tables are populated automatically
| Option | Default | Description |
|---|---|---|
IncludeGraph |
false |
Enable graph traversal |
MaxGraphDepth |
0 (unlimited) |
Maximum depth to traverse. Use 0 for unlimited. |
IncludeNavigations |
null (all) |
Specific navigation property names to include |
ExcludeNavigations |
null (none) |
Navigation property names to exclude |
- ✅ One-to-Many (e.g., Blog → Posts)
- ✅ Many-to-One (e.g., Post → Blog)
- ✅ One-to-One (e.g., Blog → BlogSettings)
- ✅ Many-to-Many with join table (e.g., Post ↔ Tags)
- ✅ Self-referencing/Hierarchies (e.g., Category → Parent/Children)
- Graph insert is inherently slower than flat insert due to FK propagation overhead
- For entities with identity columns, the library uses
ExecuteBulkInsertReturnEntitiesAsyncinternally to retrieve generated IDs - Consider using client-generated keys (GUIDs with
ValueGeneratedNever()) to avoid ID propagation overhead - Use
MaxGraphDepthto limit traversal for large/deep graphs - Use
IncludeNavigationsorExcludeNavigationsto reduce the scope of insertions
var blog = new Blog
{
Name = "My Blog",
Posts = new List<Post>
{
new Post { Title = "First Post" },
new Post { Title = "Second Post" }
}
};
await dbContext.ExecuteBulkInsertAsync(new[] { blog }, o => o.IncludeGraph = true);
// After insert:
// - blog.Id is populated
// - blog.Posts[0].BlogId == blog.Id
// - blog.Posts[1].BlogId == blog.Idvar blog = new Blog
{
Name = "My Blog",
Settings = new BlogSettings { EnableComments = true }
};
await dbContext.ExecuteBulkInsertAsync(new[] { blog }, o => o.IncludeGraph = true);
// After insert:
// - blog.Id is populated
// - blog.Settings.BlogId == blog.Idvar blog = new Blog
{
Name = "My Blog",
Posts = new List<Post> { new Post { Title = "Post" } },
Settings = new BlogSettings { EnableComments = true }
};
// Only insert Posts, not Settings
await dbContext.ExecuteBulkInsertAsync(new[] { blog }, o =>
{
o.IncludeGraph = true;
o.IncludeNavigations = new HashSet<string> { "Posts" };
});var blog = new Blog
{
Name = "My Blog",
Posts = new List<Post>
{
new Post
{
Title = "Post",
Tags = new List<Tag> { new Tag { Name = "EF Core" } } // Won't be inserted
}
}
};
// MaxGraphDepth = 1 means only Blog and direct children (Posts)
await dbContext.ExecuteBulkInsertAsync(new[] { blog }, o =>
{
o.IncludeGraph = true;
o.MaxGraphDepth = 1;
});- Shadow foreign keys: Currently not supported. Add a CLR property for foreign keys.
- Circular references: Handled gracefully by tracking visited entities, but may result in incomplete graphs.
- OnConflict/Upsert: Not currently supported with
IncludeGraph = true.