5 Hidden EF Core NuGet Packages That Make Your .NET Code Better
Five lesser-known EF Core NuGet packages for clean exceptions, naming conventions, bulk speed, dynamic queries, and auditing — with simple examples and diagrams.
A toolbox in your kitchen
Think about cooking dinner at home. You can peel potatoes with a plain knife. It works, but it is slow and a little dangerous. Now imagine your uncle gifts you a small peeler. Suddenly the same job is faster, cleaner, and safer. The peeler did not change the potato. It just gave you a better tool for one job.
EF Core is your kitchen. It already cooks most meals well. But a few small NuGet packages are like that peeler — each one quietly fixes a job that EF Core makes harder than it should be. They are not famous. Many .NET developers never hear about them. That is why we call them hidden.
Today you will meet five of these helpers. Each one solves a real, everyday pain: messy database errors, ugly table names, slow bulk inserts, queries that must change at runtime, and the question every team eventually asks — "who changed this record, and when?"
Let us open the toolbox.
The five hidden helpers
Steps
Errors
Clean exceptions
Naming
snake_case names
Bulk
Fast big inserts
Dynamic
Runtime queries
Audit
Track changes
Quick map of all five
Before we go deep, here is the whole picture in one table. Keep it nearby as a cheat sheet.
| Package | The pain it removes | Free? |
|---|---|---|
| EntityFramework.Exceptions | Database errors look the same and are hard to read | Yes |
| EFCore.NamingConventions | Table and column names do not match your database style | Yes |
| EFCore.BulkExtensions | Saving thousands of rows is painfully slow | Free for small use |
| Microsoft.EntityFrameworkCore.DynamicLinq | Filters and sorts must be chosen at runtime | Yes |
| Audit.EntityFramework.Core | You cannot tell who changed which record | Yes |
Now let us meet each one properly.
1. EntityFramework.Exceptions — read database errors like a human
When EF Core hits a database rule, like a duplicate email or a missing foreign key, it throws a DbUpdateException. The problem is that every kind of failure throws the same exception type. To know what really went wrong, you must dig into the inner exception and read raw database error numbers. Those numbers are different on SQL Server, PostgreSQL, and MySQL. It feels like reading a doctor's handwriting.
EntityFramework.Exceptions turns those messy errors into clear, named C# exceptions like UniqueConstraintException and CannotInsertNullException. You catch the exact problem and react properly.
You enable it once on your DbContext:
using EntityFramework.Exceptions.SqlServer;
public class AppDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer(connectionString);
options.UseExceptionProcessor();
}
}Now your save code becomes calm and readable:
try
{
context.Users.Add(new User { Email = "[email protected]" });
await context.SaveChangesAsync();
}
catch (UniqueConstraintException)
{
// This email is already taken. Tell the user kindly.
return "That email is already registered.";
}
catch (CannotInsertNullException)
{
return "A required field was left empty.";
}No magic numbers. No digging. Just plain catch blocks that say what they mean.
2. EFCore.NamingConventions — make names match your database style
In C#, we name things in PascalCase, like FullName and OrderDate. But many databases, especially PostgreSQL, prefer snake_case, like full_name and order_date. Without help, you would write a HasColumnName("full_name") line for every single column. That is boring and easy to get wrong.
EFCore.NamingConventions does it for the whole model with one line. You pick the style, and it renames every table and column for you.
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseNpgsql(connectionString);
options.UseSnakeCaseNamingConvention();
}That single call turns FullName into full_name everywhere. The package supports several styles, so your database can look exactly how your team likes.
| Method | FullName becomes |
|---|---|
UseSnakeCaseNamingConvention() | full_name |
UseLowerCaseNamingConvention() | fullname |
UseCamelCaseNamingConvention() | fullName |
UseUpperCaseNamingConvention() | FULLNAME |
UseUpperSnakeCaseNamingConvention() | FULL_NAME |
The best part: your C# code stays clean and PascalCase. Only the database side changes. Your team reads pretty C#, and your database team reads pretty SQL. Everyone is happy.
One setting, whole model renamed
Steps
C# Property
FullName
Convention
UseSnakeCase
DB Column
full_name
3. EFCore.BulkExtensions — save thousands of rows fast
Here is a slow trap. Suppose you must insert 50,000 rows. With normal EF Core, SaveChanges often sends many separate commands to the database. It works, but it can take many seconds, sometimes minutes. It is like posting 50,000 letters one envelope at a time.
EFCore.BulkExtensions adds BulkInsert, BulkUpdate, and BulkDelete. These use the database's fast bulk-copy path. It is like loading all 50,000 letters into one truck and driving them over together.
using EFCore.BulkExtensions;
var products = GenerateProducts(50_000); // a big list
await context.BulkInsertAsync(products);
// Done in a fraction of the time SaveChanges would take.The speed difference on large data is dramatic. The exact numbers depend on your machine and database, but the shape of the result is always the same: bulk wins big as the row count grows.
| Rows to insert | Normal SaveChanges | BulkInsert |
|---|---|---|
| 1,000 | Fast enough | Fast |
| 50,000 | Slow (many seconds) | Much faster |
| 500,000 | Very slow | Still quick |
One honest warning. EFCore.BulkExtensions uses a dual license. It is free for personal projects and for smaller companies, but larger commercial use needs a paid license. Always read the license on its GitHub page before you ship. (This is a normal and fair model — the author maintains the library carefully.)
4. Microsoft.EntityFrameworkCore.DynamicLinq — build queries at runtime
Most of the time you write queries in code, like Where(p => p.Price > 100). But sometimes you do not know the filter until the program is running. Think of a search page where the user types the column name and the rule, like "sort by Price, show items where Stock is above 10." You cannot hard-code that, because the choice comes from the user.
Microsoft.EntityFrameworkCore.DynamicLinq lets you pass the filter and sort as plain strings. This is an official Microsoft package, so it fits naturally with EF Core.
using System.Linq.Dynamic.Core;
string filter = "Stock > 10"; // comes from the user
string orderBy = "Price descending"; // comes from the user
var results = await context.Products
.Where(filter)
.OrderBy(orderBy)
.ToListAsync();The strings get translated into real SQL, just like normal LINQ. This is perfect for admin dashboards, report builders, and flexible search screens.
One safety note for young coders: when text comes from users, never paste it blindly. Validate the allowed column names first, so nobody can sneak in something harmful. Treat user input like a stranger at the door — polite, but checked.
5. Audit.EntityFramework.Core — know who changed what
Every serious app eventually faces a question: "Who changed this price last Tuesday?" Without a plan, you have no answer. EF Core saves the new value but forgets the old one.
Audit.EntityFramework.Core watches your SaveChanges and writes an audit trail. For each change it can record the old values, the new values, the time, and even the user. You do not sprinkle logging code everywhere. The package listens in the background.
using Audit.EntityFramework;
public class AppDbContext : AuditDbContext
{
public AppDbContext(DbContextOptions options) : base(options) { }
// Now every SaveChanges is recorded automatically.
}You then choose where the trail goes — a database table, a JSON file, or a logging service. The flow below shows how a single save quietly produces both the real change and its history record.
How auditing happens
Steps
Edit
Change a row
SaveChanges
You call save
Audit hook
Captures old + new
History
Stored safely
This is gold for banking, school records, hospital systems, and anywhere trust matters. When something looks wrong, the audit trail is your honest witness.
How they fit together
These five are not rivals. They each sit at a different spot in the life of a database call. Errors and naming shape how you write your model. Bulk and dynamic shape how you query and save. Auditing watches after the save. You can use one, a few, or all five, depending on your needs.
A simple way to choose
You do not need to memorize all of this. Just ask yourself a question when a pain appears, and reach for the matching tool. Here is the thinking path.
Which package do I need?
Steps
Messy errors?
Use Exceptions
Ugly names?
Use Naming
Slow inserts?
Use Bulk
Runtime filters?
Use DynamicLinq
Need history?
Use Audit
A small tip from experience: add a package only when you truly feel its pain. A project with three well-chosen packages is healthier than one stuffed with twenty you barely use. Every package you add is something you must keep updated and trust. So choose like you pack for a trip — bring what you will actually use.
Trying one yourself, step by step
Let us say you want to try the naming package. The steps are short.
First, install it from NuGet. In the terminal you run a single command:
// In your terminal, not in C#:
// dotnet add package EFCore.NamingConventionsThen add the one line in OnConfiguring, as you saw earlier. Then create a migration and look at the generated SQL. You will see your tables and columns wearing their new snake_case clothes. Nothing in your C# classes changed. That is the quiet magic of a good helper package — big result, tiny effort.
The same gentle pattern repeats for the others: install the package, add a line or two of setup, and start using the new feature. None of them ask you to rewrite your project. They simply hand you a better tool for one job, like that potato peeler from the start.
A few honest cautions
Good helpers still need care. Keep these in mind so the tools help and never hurt.
Check the license, especially for the bulk package. Free for small use does not always mean free for a big company.
Keep packages updated, but test after each update. New versions sometimes change behavior.
Prefer official Microsoft packages when two choices are equal, because they tend to track new EF Core releases quickly. EF Core itself is now on a fast yearly rhythm — .NET 10 is the current long-term-support release — so packages that keep pace save you trouble.
And measure. When a package promises speed, prove it with real numbers on your own data. Trust, but verify.
References and further reading
- Tools & Extensions — EF Core (Microsoft Learn) — the official list of community and Microsoft EF Core extensions.
- EF Core NuGet Packages (Microsoft Learn) — how the core packages are organized.
- EFCore.NamingConventions (GitHub) — source, options, and examples for naming styles.
- EFCore.BulkExtensions (GitHub) — bulk operations and the important license details.
- EFCore.CheckConstraints (GitHub) — a bonus plugin that adds database check constraints automatically.
Quick recap
- A NuGet package is a ready-made box of code you add to your project to gain a feature fast.
- EntityFramework.Exceptions turns confusing
DbUpdateExceptionerrors into clear, named exceptions you can catch precisely. - EFCore.NamingConventions renames your whole model to a style like
snake_casewith one line, keeping your C# clean. - EFCore.BulkExtensions makes inserting and updating thousands of rows far faster — but check its license for commercial use.
- Microsoft.EntityFrameworkCore.DynamicLinq lets users choose filters and sorts at runtime using strings; always validate that input.
- Audit.EntityFramework.Core records who changed what and when, building a trustworthy history with little extra code.
- Add a package only when you feel its pain, keep packages updated, and measure real results before trusting any speed claim.
Related Posts
EF Core Query Splitting: Fix Slow Queries and Cartesian Explosion
Learn how EF Core query splitting (AsSplitQuery) fixes the cartesian explosion problem with simple examples, diagrams, and real performance numbers. Know when to split and when not to.
Soft Delete with EF Core: Delete Data Without Losing It
Learn soft delete in EF Core the right way. Use an interceptor and global query filters to hide deleted rows automatically, with simple examples, diagrams, code, and best practices for .NET 10.
Multi-Tenant Applications With EF Core: A Beginner's Guide
Learn multi-tenancy in EF Core the simple way. Isolate tenant data with global query filters, ITenantService, and named filters in .NET 10, with diagrams and code.
EF Core Migrations: A Detailed Beginner Guide for .NET
Learn EF Core migrations step by step. Add, apply, revert, and ship database changes safely with simple examples, diagrams, tables, and best practices for .NET 10.
Eager Loading of Child Entities in EF Core: A Beginner's Guide
Learn eager loading in EF Core with Include and ThenInclude. Load child entities in one query, avoid the N+1 problem, and use filtered Include with simple examples.
EF Core DbContext Options Explained: A Beginner's Friendly Guide
Learn EF Core DbContext options in simple words: AddDbContext, the options builder, retry on failure, query splitting, logging, lifetimes and pooling, with diagrams and examples.