Add() and AddRange() start tracking new entities so they can be inserted into the database when SaveChangesAsync() is called.
It is useful when you want to insert one or more new rows using the normal EF Core change-tracking workflow.
Add One Entity
Most of the time, you will combine Add() with SaveChangesAsync().
using var context = new AppDbContext();
var product = new Product
{
Name = "Mechanical Keyboard",
Price = 120
};
context.Products.Add(product);
await context.SaveChangesAsync();
This inserts one new product in the database.
Add Multiple Entities with AddRange
Use AddRange() when you want to add multiple new entities in one call.
using var context = new AppDbContext();
var keyboard = new Product { Name = "Mechanical Keyboard", Price = 120 };
var mouse = new Product { Name = "Wireless Mouse", Price = 40 };
var monitor = new Product { Name = "27-inch Monitor", Price = 250 };
context.Products.AddRange(keyboard, mouse, monitor);
await context.SaveChangesAsync();
This inserts the three products in the database.
Add Related Entities
EF Core can also insert related entities when they are part of the same object graph.
using var context = new AppDbContext();
var category = new Category
{
Name = "Peripherals",
Products = new List<Product>
{
new Product { Name = "Mechanical Keyboard", Price = 120 },
new Product { Name = "Wireless Mouse", Price = 40 },
new Product { Name = "27-inch Monitor", Price = 250 }
}
};
context.Categories.Add(category);
await context.SaveChangesAsync();
This inserts the category and the related products.
Add and AddRange Methods
EF Core provides several methods for adding new entities to the DbSet.
The most common ones are:
Add<TEntity>(TEntity entity)AddRange<TEntity>(IEnumerable<TEntity> entities)AddRange<TEntity>(params TEntity[] entities)
In most cases, you do not need to specify the generic type explicitly.
context.Products.Add(product);
The C# compiler can infer the entity type from the object you pass. For more information, see the C# documentation on generic methods.
Use Add() when you want to add one entity.
Use AddRange() when you want to add multiple entities as part of the same unit of work.
context.Products.AddRange(keyboard, mouse, monitor);
Both methods use the normal EF Core change-tracking workflow. The entities are tracked first, and the database inserts are sent when SaveChangesAsync() runs.
For more details about range methods such as AddRange, UpdateRange, AttachRange, and RemoveRange, see the official EF Core documentation on range methods.
Important Behavior
Adding data with EF Core follows the normal ChangeTracker and SaveChangesAsync() workflow.
That means:
Add()does not insert the row immediatelyAddRange()does not insert rows immediately- the entities are tracked as
Added SaveChangesAsync()sends theINSERTstatements to the database- generated key values are updated on the entity instances after saving
- related entities can also be inserted when they are part of the same object graph
For a deeper explanation of how tracked changes are persisted, see SaveChanges.
For more about how EF Core stores entity states, see ChangeTracker.
How Adding Data Works
When you add a new entity, EF Core starts tracking it in the Added state.
using var context = new AppDbContext();
var product = new Product
{
Name = "Mechanical Keyboard",
Price = 120
};
context.Products.Add(product);
At this point, the entity exists only in memory. No row has been inserted in the database yet.
When SaveChangesAsync() runs, EF Core finds all tracked entities in the Added state and generates the required INSERT statements.
await context.SaveChangesAsync();
After the insert succeeds, EF Core updates the entity state and stores generated database values, such as identity keys, back on the entity instance.
For example, if the database generates the product key, you can read it after SaveChangesAsync() completes.
using var context = new AppDbContext();
var product = new Product
{
Name = "Mechanical Keyboard",
Price = 120
};
context.Products.Add(product);
await context.SaveChangesAsync();
var generatedProductId = product.Id;
A simplified flow looks like this:
- You create a new entity instance.
- You call
Add()orAddRange(). - EF Core tracks the entity as
Added. - You call
SaveChangesAsync(). - EF Core sends the
INSERTstatement to the database. - The database generates values such as identity keys.
- EF Core updates the entity instance with those generated values.
For a deeper look at how EF Core tracks entity states, see Tracking Changes of Entities.
Add Related Entities and Object Graphs
EF Core can insert related entities when they are reachable from the entity being added.
For example, if a Category contains new Product entities through the Products navigation property, adding the category also adds the products.
using var context = new AppDbContext();
var category = new Category
{
Name = "Peripherals",
Products = new List<Product>
{
new Product { Name = "Mechanical Keyboard", Price = 120 },
new Product { Name = "Wireless Mouse", Price = 40 },
new Product { Name = "27-inch Monitor", Price = 250 }
}
};
context.Categories.Add(category);
await context.SaveChangesAsync();
EF Core tracks the full object graph:
- the
Categoryis marked asAdded - each related
Productis marked asAdded - the category row is inserted first
- the generated category key is used for the related product rows
This is useful when you want to create a principal entity and its related dependent entities in the same save operation.
However, EF Core only follows relationships that are actually connected in the object graph.
For example, this code creates a separate product object in memory, but that product is not connected to the category being added.
using var context = new AppDbContext();
var category = new Category
{
Name = "Peripherals"
};
var product = new Product
{
Name = "Mechanical Keyboard",
Price = 120
};
context.Categories.Add(category);
await context.SaveChangesAsync();
In this case, only the category is reachable from the graph passed to Add(), so only the category is added.
A clearer pattern is to connect the product through the navigation property.
using var context = new AppDbContext();
var category = new Category
{
Name = "Peripherals",
Products = new List<Product>
{
new Product { Name = "Mechanical Keyboard", Price = 120 }
}
};
context.Categories.Add(category);
await context.SaveChangesAsync();
For tracked relationship scenarios, see Saving Data in Connected Scenario.
AddRange is Not BulkInsert
AddRange() is useful when you want to add multiple entities to the ChangeTracker in one call.
However, AddRange() is not the same as a real bulk insert.
using var context = new AppDbContext();
var keyboard = new Product { Name = "Mechanical Keyboard", Price = 120 };
var mouse = new Product { Name = "Wireless Mouse", Price = 40 };
var monitor = new Product { Name = "27-inch Monitor", Price = 250 };
context.Products.AddRange(keyboard, mouse, monitor);
await context.SaveChangesAsync();
This still uses the normal EF Core workflow:
- entities are tracked
- entities are marked as
Added SaveChangesAsync()generates the database commands- EF Core sends the inserts to the database
EF Core can batch multiple commands to reduce database round-trips, but this is still different from a dedicated bulk insert operation.
For small or moderate numbers of entities, AddRange() is often enough.
For thousands or millions of rows, a real bulk insert library is usually a better choice.
// @nuget: Z.EntityFramework.Extensions.EFCore
using Z.EntityFramework.Extensions;
using var context = new AppDbContext();
var products = GetProductsToInsert();
await context.BulkInsertAsync(products);
Entity Framework Extensions BulkInsert is designed for high-volume insert scenarios where performance matters more than the normal tracked entity workflow.
In short:
- use
Add()for one new entity - use
AddRange()for multiple new entities in the normal EF Core workflow - use
BulkInsertwhen you need high-performance inserts for large datasets
When to Use Add or AddRange
Use Add() or AddRange() when:
- you want to insert new entities using the normal EF Core workflow
- you need EF Core to track the entities before saving
- you want to insert one entity or a moderate number of entities
- you want to save related entities as part of the same object graph
- you need generated key values after
SaveChangesAsync() - you are already working inside a tracked unit of work
Common examples include:
- creating a new product
- adding a few records from a form
- inserting a category with related products
- inserting several entities before calling
SaveChangesAsync()once - combining inserts with other tracked changes in the same unit of work
For a broader overview of saving tracked changes, see SaveChanges.
When Not to Use Add or AddRange
Add() and AddRange() are not always the best choice.
Avoid them when:
- you need to insert thousands or millions of rows as fast as possible
- you do not need the normal
ChangeTrackerworkflow - you are importing a large file
- you are synchronizing large datasets
- the insert operation is mainly a performance-critical batch process
For high-volume inserts, use BulkInsert.
For set-based updates and deletes, see ExecuteUpdate and ExecuteDelete.
Common Pitfalls
Be careful with the following mistakes.
Forgetting SaveChangesAsync
Add() and AddRange() only start tracking entities.
This does not insert the row:
using var context = new AppDbContext();
var product = new Product
{
Name = "Mechanical Keyboard",
Price = 120
};
context.Products.Add(product);
You must call SaveChangesAsync() to persist the insert.
await context.SaveChangesAsync();
Expecting AddRange to Be BulkInsert
AddRange() is not a true bulk insert operation.
It adds multiple entities to the ChangeTracker, but SaveChangesAsync() still controls how the inserts are sent to the database.
For very large inserts, use BulkInsert.
Adding Related Entities Without Connecting the Graph
EF Core can insert related entities when they are part of the object graph being added.
If the related objects are not actually connected to the graph being added, EF Core may not insert the related data the way you expect.
A clear object graph is easier to understand and safer to save.
var category = new Category
{
Name = "Peripherals",
Products = new List<Product>
{
new Product { Name = "Mechanical Keyboard", Price = 120 }
}
};
Using Add for an Existing Entity
Add() tells EF Core that the entity is new.
If you call Add() for an entity that already exists in the database, EF Core can try to insert it again.
For existing entities, use an update workflow instead.
For more information, see Updating Data.
External Resources - Adding Data
The following videos are useful if you want to see how EF Core inserts new entities, how the standard Add() / AddRange() + SaveChanges() workflow behaves, and when a real bulk insert library becomes more appropriate.
Video 1 - Adding Data to a Database in EF Core 7
This video shows the basic insert workflow in EF Core 7: creating entity instances, adding them to the appropriate DbSet with Add(), and calling SaveChanges() to persist them in the database. It also demonstrates inserting a related entity by using an existing foreign key and verifies the generated primary key in the database. It is a useful visual introduction to the standard insert pattern before moving to AddRange(), related entity graphs, or bulk insert scenarios.
Key timestamps:
- 0:00 — Introduction and entity setup using
ManagerandEmployee - 1:40 — Adding an entity to the appropriate
DbSetwithAdd() - 2:39 — Calling
SaveChanges()and verifying the generated primary key in the database - 3:36 — Inserting a related
Employeeusing an existingManagerforeign key - 4:30 — Recap of the three-step insert pattern
Video 2 - Insert Data in ASP.NET Core with Entity Framework Core
This video shows how a basic Add() + SaveChanges() insert flow can be used inside an ASP.NET Core application. It demonstrates adding a new Book entity, saving it to SQL Server, retrieving the generated identity value after SaveChanges(), and debugging the insert flow through a repository and controller. It is useful if you want to see the insert pattern in a real web application. Since the video also spends time on repository, dependency injection, controller, and debugging code, it should be treated as a practical application example rather than a deep EF Core insert reference.
Key timestamps:
- 2:00 — Creating the
AddNewBookrepository method and adding a new entity withAdd() - 4:00 — Calling
SaveChanges()and retrieving the generated identity value - 7:00 — Registering the repository with Dependency Injection
- 12:00 — Debugging the POST action and verifying the insert in SQL Server
Video 3 - EF Core Bulk Operations and BulkInsert Performance
This video from ZZZ Projects demonstrates the performance difference between standard EF Core inserts using AddRange() + SaveChanges() and real bulk insert operations with Entity Framework Extensions. It compares insert performance, explains the cost of returning generated identity values, and shows how IncludeGraph() can be used for related entity graphs. It is especially useful when you want to understand why AddRange() is not the same as a true BulkInsert.
Key timestamps:
- 0:00 — Introduction to
SaveChanges()and bulk insert benchmarks with 10,000 products - 2:00 — Comparing
SaveChanges()withBulkInsert()and generated identity values - 4:00 — Using
BulkInsertOptimize()and understanding optimized inserts - 6:00 — Inserting related entity graphs with
IncludeGraph()
Summary
Adding data in EF Core uses the normal change-tracking and SaveChangesAsync() workflow.
Key points:
- use
Add()to add one new entity - use
AddRange()to add multiple new entities - call
SaveChangesAsync()to send theINSERTstatements to the database - EF Core tracks new entities as
Added - related entities can be inserted together when they are part of the same object graph
- generated key values are available after
SaveChangesAsync()succeeds AddRange()is not the same as a realBulkInsert
Use Add() and AddRange() for normal EF Core insert scenarios. For high-volume inserts, use BulkInsert.
Related Articles
If you want to understand how adding data fits into the broader EF Core saving workflow, these pages are the best next step:
- SaveChanges — how EF Core persists tracked changes
- Saving Data in Connected Scenario — how EF Core saves entities that are already tracked by the current context
- ChangeTracker — how EF Core stores entity states such as
Added - Tracking Changes of Entities — how EF Core detects changes before saving
- Updating Data — how to update existing entities with the normal tracked workflow
- Deleting Data — how to delete entities with the normal tracked workflow
- Adding data via the DbContext — another way to add entities using the
DbContextAPI - Bulk Insert — how to insert large datasets with Entity Framework Extensions
FAQ
Does Add insert the entity immediately?
No. Add() only starts tracking the entity as Added. The row is inserted when SaveChangesAsync() is called.
Does AddRange insert rows immediately?
No. AddRange() starts tracking multiple entities as Added. The inserts are sent to the database when SaveChangesAsync() runs.
What is the difference between Add and AddRange?
Add() is used for one entity. AddRange() is used for multiple entities.
Both methods use the normal EF Core ChangeTracker and SaveChangesAsync() workflow.
Is AddRange the same as BulkInsert?
No. AddRange() is not a true bulk insert operation.
AddRange() tracks multiple entities, and SaveChangesAsync() sends the database commands. A real bulk insert library such as Entity Framework Extensions is designed for high-volume insert scenarios.
Is AddRange faster than Add?
No, they provide similar performance. AddRange doesn’t magically save entities faster; it simply allows you to add a list to the change tracker instead of calling the Add method multiple times.
Even the EF Core team says there is almost no difference: “These methods are provided as a convenience. Using a ‘range’ method has the same functionality as multiple calls to the equivalent non-range method. There is no significant performance difference between the two approaches.”
Can EF Core insert related entities automatically?
Yes. EF Core can insert related entities when they are reachable from the object graph being added.
For example, adding a Category with new Product entities in its Products collection can insert both the category and the products.
How do I get the generated ID after inserting?
After SaveChangesAsync() succeeds, EF Core updates the entity instance with generated database values such as identity keys.
context.Products.Add(product);
await context.SaveChangesAsync();
var id = product.Id;