If you work with .NET, you work with Entity Framework. It’s in almost every project — from small APIs to enterprise applications with dozens of entities and complex relationships.
And if you’re honest, a lot of EF Core work is tedious. Writing entity configurations. Creating migrations. Crafting LINQ queries that actually produce efficient SQL. It’s not hard, but it takes time and attention.
Claude Code understands your DbContext. And that changes the workflow significantly.
Adding a new entity
Let’s say you need to add an Order entity to your project. You have an existing AppDbContext with a few entities already configured. You tell Claude Code:
“Add an Order entity with Id, CustomerId (FK to Customer), OrderDate, Status (enum), and a collection of OrderLines with ProductId, Quantity, and UnitPrice.”
Claude Code reads your existing DbContext, sees how you structure your entities, and produces code that matches your conventions. If you use separate configuration classes, it creates one. If you configure everything in OnModelCreating, it adds the configuration there.
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; } = null!;
public DateTime OrderDate { get; set; }
public OrderStatus Status { get; set; }
public List<OrderLine> Lines { get; set; } = new();
}
public class OrderLine
{
public int Id { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; } = null!;
public int ProductId { get; set; }
public Product Product { get; set; } = null!;
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
And the configuration:
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.HasOne(o => o.Customer)
.WithMany(c => c.Orders)
.HasForeignKey(o => o.CustomerId);
builder.HasMany(o => o.Lines)
.WithOne(l => l.Order)
.HasForeignKey(l => l.OrderId);
builder.Property(o => o.Status)
.HasConversion<string>();
builder.Property(o => o.OrderDate)
.HasDefaultValueSql("GETUTCDATE()");
}
}
Then it runs dotnet ef migrations add AddOrderEntity and verifies the build succeeds. One prompt, three files, a working migration.
Complex LINQ queries
This is where Claude Code really shines. Instead of puzzling over LINQ syntax, you just describe what you need in plain English.
“Get all orders with more than 3 items that were shipped this month, grouped by customer, with the total amount per customer.”
Claude Code reads your entities, understands the relationships, and writes:
var result = await dbContext.Orders
.Where(o => o.Status == OrderStatus.Shipped
&& o.OrderDate.Month == DateTime.UtcNow.Month
&& o.OrderDate.Year == DateTime.UtcNow.Year)
.Where(o => o.Lines.Count > 3)
.GroupBy(o => new { o.CustomerId, o.Customer.Name })
.Select(g => new
{
CustomerId = g.Key.CustomerId,
CustomerName = g.Key.Name,
OrderCount = g.Count(),
TotalAmount = g.SelectMany(o => o.Lines)
.Sum(l => l.Quantity * l.UnitPrice)
})
.OrderByDescending(x => x.TotalAmount)
.ToListAsync();
That’s a query I’d normally spend ten minutes writing and testing. Claude Code produces it in seconds — and because it reads your actual entity classes, the property names and relationships are correct.
Migration management
Migrations are one of those things that feel simple until they’re not. Renaming a column? EF Core generates a drop-and-add by default. Splitting a table? The migration needs manual adjustments.
Claude Code helps in two ways. First, it generates the migration command for you:
dotnet ef migrations add RenameCustomerEmail \
--project src/MyApp.Infrastructure \
--startup-project src/MyApp.Api
Second — and more importantly — it reviews the generated migration and spots issues. If EF Core generated a destructive migration (dropping a column instead of renaming), Claude Code will flag it and suggest the correct approach:
migrationBuilder.RenameColumn(
name: "EmailAddress",
table: "Customers",
newName: "Email");
This saves you from deploying data loss to production.
Performance pitfalls
Ask Claude Code to review your EF Core queries and it will spot common performance issues that are easy to miss during development.
N+1 queries. You’re loading orders in a loop and accessing order.Customer.Name without an Include. Claude Code sees the pattern and suggests eager loading:
// Before: N+1 problem
var orders = await dbContext.Orders.ToListAsync();
foreach (var order in orders)
Console.WriteLine(order.Customer.Name); // Extra query per order
// After: single query
var orders = await dbContext.Orders
.Include(o => o.Customer)
.ToListAsync();
Missing indexes. If you frequently filter on OrderDate or Status, Claude Code suggests adding an index in your configuration:
builder.HasIndex(o => new { o.Status, o.OrderDate });
Unnecessary tracking. For read-only queries, it recommends AsNoTracking() — a small change that can make a big difference in performance.
DbContext configuration
EF Core’s Fluent API is powerful but verbose. Relationships, value conversions, owned types — there’s a lot to configure, and the documentation doesn’t always have the exact example you need.
Claude Code handles these configurations well. Need a value conversion for a strongly-typed ID? A owned type for an address? A table-per-hierarchy mapping with a discriminator?
// Strongly-typed ID conversion
builder.Property(o => o.Id)
.HasConversion(
id => id.Value,
value => new OrderId(value));
// Owned type for Address
builder.OwnsOne(c => c.ShippingAddress, address =>
{
address.Property(a => a.Street).HasMaxLength(200);
address.Property(a => a.City).HasMaxLength(100);
address.Property(a => a.ZipCode).HasMaxLength(10);
});
Just describe what you want and Claude Code writes the configuration that matches the rest of your DbContext.
What Claude Code gets wrong
Let’s be honest. Claude Code isn’t perfect with EF Core, and there are a few areas where you need to pay attention.
Editing migration files directly. Sometimes Claude Code will try to modify a generated migration file. Don’t let it. Migrations are generated code — if you need changes, modify the entity or configuration and generate a new migration.
Complex inheritance mapping. TPH, TPT, TPC — Claude Code sometimes mixes up the configuration patterns, especially for table-per-concrete-type mappings. Always verify the generated SQL.
Database provider specifics. Claude Code might suggest SQL Server syntax when you’re using PostgreSQL, or vice versa. If you mention your provider in your CLAUDE.md file, this happens less often.
Existing migration history. Claude Code doesn’t always know which migrations have already been applied to your database. It might suggest creating a migration that conflicts with your current state. Always run dotnet ef migrations list first.
Practical tips
After months of using Claude Code with EF Core, these are the things that make the biggest difference:
Document your database provider in CLAUDE.md. Write “We use PostgreSQL with Npgsql” or “SQL Server with EF Core 8.” This prevents wrong dialect suggestions.
Let Claude Code generate migrations, but always review them. Run
dotnet ef migrations scriptto see the SQL before applying.Ask for query explanations. Say “explain this LINQ query and what SQL it produces” — Claude Code will walk you through it.
Use it for refactoring. “Split this God-DbContext into separate bounded contexts” is exactly the kind of tedious, high-context work that Claude Code excels at.
Don’t let it touch existing migration files. This is the number one rule. Migrations are append-only history.
Try it yourself
Next time you’re staring at a complex LINQ query or writing yet another entity configuration, try this:
claude
Describe what you need in plain English. Let Claude Code read your DbContext, understand your conventions, and generate code that fits. You’ll find that the repetitive parts of EF Core — the ones that take time but not creativity — simply disappear.
What remains is the part that matters: designing your data model.