Skip to content

Latest commit

 

History

History
134 lines (105 loc) · 3.81 KB

File metadata and controls

134 lines (105 loc) · 3.81 KB

Graph Insert (Navigation Properties)

ℹ️ 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.

Enabling Graph Insert

await dbContext.ExecuteBulkInsertAsync(blogs, options =>
{
    options.IncludeGraph = true;
});

How It Works

  1. The library traverses all reachable entities via navigation properties
  2. Entities are sorted in topological order (parents before children) to respect foreign key constraints
  3. Each entity type is bulk inserted in dependency order
  4. Generated IDs (identity columns) are propagated to foreign key properties
  5. Many-to-many join tables are populated automatically

Options

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

Supported Relationship Types

  • ✅ 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)

Performance Considerations

  • Graph insert is inherently slower than flat insert due to FK propagation overhead
  • For entities with identity columns, the library uses ExecuteBulkInsertReturnEntitiesAsync internally to retrieve generated IDs
  • Consider using client-generated keys (GUIDs with ValueGeneratedNever()) to avoid ID propagation overhead
  • Use MaxGraphDepth to limit traversal for large/deep graphs
  • Use IncludeNavigations or ExcludeNavigations to reduce the scope of insertions

Example

One-to-Many Relationship

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.Id

One-to-One Relationship

var 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.Id

Selective Navigation Inclusion

var 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" };
});

Limiting Graph Depth

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;
});

Limitations

  • 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.