Agent Skills: Entity Framework Core

Entity Framework Core patterns for data access, migrations, relationships, performance, and best practices

UncategorizedID: lobbi-docs/claude/entity-framework

Install this agent skill to your local

pnpm dlx add-skill https://github.com/markus41/claude/tree/HEAD/plugins/dotnet-blazor/skills/entity-framework

Skill Files

Browse the full folder contents for entity-framework.

Download Skill

Loading file tree…

plugins/dotnet-blazor/skills/entity-framework/SKILL.md

Skill Metadata

Name
entity-framework
Description
Entity Framework Core patterns for data access, migrations, relationships, performance, and best practices

Entity Framework Core

DbContext Setup

public sealed class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
    public DbSet<Product> Products => Set<Product>();
    public DbSet<Order> Orders => Set<Order>();
    public DbSet<Customer> Customers => Set<Customer>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
    }
}

Entity Configuration (Fluent API)

public sealed class ProductConfiguration : IEntityTypeConfiguration<Product>
{
    public void Configure(EntityTypeBuilder<Product> builder)
    {
        builder.HasKey(p => p.Id);
        builder.Property(p => p.Name).HasMaxLength(200).IsRequired();
        builder.Property(p => p.Price).HasPrecision(18, 2);
        builder.Property(p => p.Sku).HasMaxLength(50);
        builder.HasIndex(p => p.Sku).IsUnique();

        builder.HasOne(p => p.Category)
            .WithMany(c => c.Products)
            .HasForeignKey(p => p.CategoryId)
            .OnDelete(DeleteBehavior.Restrict);

        builder.HasMany(p => p.Tags)
            .WithMany(t => t.Products)
            .UsingEntity(j => j.ToTable("ProductTags"));
    }
}

Migrations

# Add migration
dotnet ef migrations add InitialCreate --project Data --startup-project Web

# Update database
dotnet ef database update --project Data --startup-project Web

# Generate SQL script
dotnet ef migrations script --idempotent --project Data --startup-project Web

# Remove last migration (if not applied)
dotnet ef migrations remove --project Data --startup-project Web

Query Patterns

Basic CRUD

// Read (always use async, use AsNoTracking for reads)
var products = await db.Products
    .AsNoTracking()
    .Where(p => p.IsActive)
    .OrderBy(p => p.Name)
    .ToListAsync(ct);

// Read with projection (best performance)
var dtos = await db.Products
    .AsNoTracking()
    .Where(p => p.IsActive)
    .Select(p => new ProductDto(p.Id, p.Name, p.Price))
    .ToListAsync(ct);

// Create
db.Products.Add(new Product { Name = "New", Price = 9.99m });
await db.SaveChangesAsync(ct);

// Update
var product = await db.Products.FindAsync([id], ct);
if (product is not null)
{
    product.Price = newPrice;
    await db.SaveChangesAsync(ct);
}

// Delete
var product = await db.Products.FindAsync([id], ct);
if (product is not null)
{
    db.Products.Remove(product);
    await db.SaveChangesAsync(ct);
}

Eager Loading

var orders = await db.Orders
    .Include(o => o.Customer)
    .Include(o => o.Items)
        .ThenInclude(i => i.Product)
    .AsSplitQuery() // Prevents cartesian explosion
    .ToListAsync(ct);

Compiled Queries (hot paths)

private static readonly Func<AppDbContext, int, CancellationToken, Task<Product?>> GetProductById =
    EF.CompileAsyncQuery((AppDbContext db, int id, CancellationToken ct) =>
        db.Products.FirstOrDefault(p => p.Id == id));

// Usage
var product = await GetProductById(db, id, ct);

Bulk Operations (.NET 10)

// Bulk update without loading entities
await db.Products
    .Where(p => p.CategoryId == oldCategoryId)
    .ExecuteUpdateAsync(s => s.SetProperty(p => p.CategoryId, newCategoryId), ct);

// Bulk delete
await db.Products
    .Where(p => p.IsDeleted && p.DeletedAt < cutoff)
    .ExecuteDeleteAsync(ct);

Performance Tips

  • Always use AsNoTracking() for read-only queries
  • Use Select() projection to only load needed columns
  • Use AsSplitQuery() for multiple includes
  • Add indexes for frequently queried columns
  • Use ExecuteUpdateAsync/ExecuteDeleteAsync for bulk operations
  • Use compiled queries for frequently executed queries
  • Never call ToList() before Where() (loads entire table)
  • Use IQueryable in repositories, materialize in services