dotnet-entity-framework6
Maintain or migrate EF6-based applications with realistic guidance on what to keep, what to modernize, and when EF Core is or is not the right next step. Use when working in an EF6 codebase or planning a data layer migration.
Install
npx skills add https://github.com/Postpartum-genushyacinthus29/dotnet-skills/tree/main/skills/dotnet-entity-framework6
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install postpartum-genushyacinthus29-dotnet-skills@llmmart
git clone https://github.com/Postpartum-genushyacinthus29/dotnet-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole postpartum-genushyacinthus29/dotnet-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Entity Framework 6
Trigger On
- working in an EF6 codebase on .NET Framework or modern .NET
- deciding whether to keep EF6, move to modern .NET runtime, or port to EF Core
- reviewing EDMX, code-first, or legacy ASP.NET/WPF/WinForms data access
- planning a data layer migration strategy
Workflow
Audit current EF6 usage before planning any migration. Identify which features the codebase depends on:
// Common EF6-specific patterns to inventory: // - EDMX designer models (check for *.edmx files) // - ObjectContext vs DbContext usage // - Lazy loading with virtual navigation properties // - Database.SqlQuery<T>() for raw SQL // - Stored procedure mappings in model // - Spatial types (DbGeography, DbGeometry)Decide runtime vs ORM migration separately:
Path When to use Keep EF6 on .NET Framework Legacy app with no runtime pressure EF6 on modern .NET Runtime upgrade needed, ORM migration too risky EF6 → EF Core Clean data layer, no EDMX, minimal stored-procedure mapping For maintenance work — keep EF6 stable:
- use repository + unit of work patterns to isolate data access (see references/patterns.md)
- prefer
DbContextoverObjectContextfor new code - use
AsNoTracking()for read-only queries - configure concurrency tokens with
[ConcurrencyCheck]orIsRowVersion()
For migration work — validate each slice:
- map EF6 features to EF Core equivalents (see references/migration.md)
- migrate one bounded context at a time, not the entire data layer
- run integration tests against the real database provider, not InMemory
- verify:
dotnet ef migrations addsucceeds, queries produce equivalent results, lazy loading behavior matches expectations
Do not promise EF Core features to EF6 codebases — EF6 is stable and supported but not on the innovation path. Keep expectations realistic.
flowchart LR
A["Audit EF6 usage"] --> B{"EDMX or complex mappings?"}
B -->|Yes| C["High migration cost — consider keeping EF6"]
B -->|No| D["Evaluate EF Core migration"]
D --> E["Migrate one context at a time"]
E --> F["Integration test against real DB"]
C --> G["Modernize runtime only"]
F --> H["Validate query equivalence"]
G --> H
Deliver
- realistic EF6 maintenance or migration guidance based on actual codebase audit
- clear separation between runtime upgrade and ORM upgrade work
- bounded migration slices with concrete validation checkpoints
- reduced risk for legacy data access changes
Validate
- EF6 feature inventory is complete before migration planning starts
- migration assumptions are backed by real feature usage, not guesses
- EF6-only features (EDMX, spatial types, ObjectContext patterns) are identified early
- integration tests run against the real database provider, not mocks or InMemory
- the proposed path avoids unnecessary churn in stable data access code
References
- references/migration.md - decision framework, migration approaches, EF6-to-EF Core feature mapping, and common pitfalls
- references/patterns.md - repository and unit of work patterns, query optimization, concurrency handling, auditing, and testing strategies for EF6 codebases
Files (dotnet-skills)
-
references
-
migration.md 5.9 KB
# EF6 to EF Core Migration Guide ## Migration Decision Framework ### When to Stay on EF6 - Application is stable and not actively evolving - Heavy use of EDMX designer models without code-first equivalents - Complex stored procedure mappings that drive business logic - Dependency on ObjectContext or ObjectStateManager APIs - No plans to move the host application to modern .NET ### When to Consider EF Core - Targeting .NET 6+ or planning a runtime migration - Need for cross-platform deployment (Linux, containers) - Desire for improved performance characteristics - Need for features only available in EF Core (e.g., compiled queries, split queries, interceptors) - Starting a new module or service that will coexist with EF6 code ### When to Skip EF Core Entirely - Moving to a different data access strategy (Dapper, raw ADO.NET, document stores) - Decomposing the monolith into microservices with dedicated data strategies - The data layer is being replaced by an external API or service ## Migration Approaches ### Parallel Coexistence Run EF6 and EF Core side by side in the same solution: 1. Add EF Core packages alongside existing EF6 references 2. Create a new DbContext for EF Core targeting the same database 3. Migrate entity configurations incrementally 4. Gradually shift new features and queries to EF Core 5. Retire EF6 DbContext once all code paths are migrated Benefits: - No big-bang cutover - Validate behavior slice by slice - Rollback is straightforward Risks: - Two DbContexts means two change trackers; avoid crossing them in the same unit of work - Schema migrations need coordination ### Module-by-Module Migration Migrate entire bounded contexts or modules at once: 1. Identify module boundaries in the existing codebase 2. Extract the module's data access into a dedicated project 3. Port that project to EF Core and modern .NET 4. Integrate via API or shared database until full cutover Benefits: - Clean separation reduces cross-cutting risks - Easier to test in isolation Risks: - Requires clear module boundaries - May need temporary integration shims ### Big-Bang Rewrite Replace the entire data layer in a single release: 1. Map all entities, configurations, and queries 2. Port all migrations or regenerate schema 3. Run extensive regression testing 4. Deploy as a single release Benefits: - No ongoing dual maintenance Risks: - High risk of regressions - Long development cycle without production feedback - Rollback is difficult ## Feature Mapping ### Entity Configuration | EF6 | EF Core | |---------------------------------|----------------------------------------------| | `EntityTypeConfiguration<T>` | `IEntityTypeConfiguration<T>` or Fluent API | | `modelBuilder.Configurations.Add()` | `modelBuilder.ApplyConfigurationsFromAssembly()` | | EDMX designer | No equivalent; use code-first | | Complex Types | Owned Types | ### Lazy Loading | EF6 | EF Core | |--------------------------------------|----------------------------------------------| | Enabled by default with virtual props | Opt-in via `UseLazyLoadingProxies()` or `ILazyLoader` | ### Change Tracking | EF6 | EF Core | |----------------------------|----------------------------------------------| | Snapshot by default | Snapshot by default; change-tracking proxies optional | | `ObjectStateManager` | `ChangeTracker` | ### Stored Procedures | EF6 | EF Core | |---------------------------------------------|----------------------------------------------| | `MapToStoredProcedures()` for CUD | Sproc mapping for CUD introduced in EF Core 7 | | Function imports in EDMX | `FromSql` or raw SQL | ### Migrations | EF6 | EF Core | |-------------------------------|----------------------------------------------| | `Add-Migration`, `Update-Database` | `Add-Migration`, `Update-Database` (similar commands) | | `__MigrationHistory` table | `__EFMigrationsHistory` table | ## Common Pitfalls ### Behavioral Differences - **Query translation**: EF Core has different LINQ translation behavior; some queries that worked in EF6 may throw or produce different SQL - **Cascade delete defaults**: EF Core defaults to cascade delete for required relationships; EF6 does not - **Shadow properties**: EF Core tracks FK values as shadow properties by default; EF6 requires explicit FK properties - **Global query filters**: EF Core supports them; EF6 does not ### Missing Features in EF Core Some EF6 features have no direct equivalent: - EDMX designer and visual model-first workflows - ObjectContext API (only DbContext is supported) - Automatic migrations (removed; use explicit migrations) - Entity SQL (ESQL) query language ### Provider Differences - Verify your database provider has EF Core support - Provider feature parity varies; some advanced database features may differ - Test against the real database, not just in-memory ## Testing Strategy 1. **Characterization tests**: Before migration, write tests that capture current EF6 behavior 2. **SQL diff comparisons**: Compare generated SQL between EF6 and EF Core for critical queries 3. **Integration tests**: Run against a real database with realistic data volumes 4. **Performance baselines**: Measure query performance before and after migration ## References - [Microsoft Docs: Porting from EF6 to EF Core](https://learn.microsoft.com/en-us/ef/efcore-and-ef6/porting/) - [Microsoft Docs: EF6 Overview](https://learn.microsoft.com/en-us/ef/ef6/) - [Microsoft Docs: EF Core Overview](https://learn.microsoft.com/en-us/ef/core/) -
patterns.md 8.6 KB
# EF6 Maintenance Patterns ## Repository and Unit of Work ### Basic Repository Pattern ```csharp public interface IRepository<T> where T : class { T GetById(int id); IQueryable<T> Query(); void Add(T entity); void Update(T entity); void Delete(T entity); } public class EF6Repository<T> : IRepository<T> where T : class { private readonly DbContext _context; private readonly DbSet<T> _dbSet; public EF6Repository(DbContext context) { _context = context; _dbSet = context.Set<T>(); } public T GetById(int id) => _dbSet.Find(id); public IQueryable<T> Query() => _dbSet; public void Add(T entity) => _dbSet.Add(entity); public void Update(T entity) => _context.Entry(entity).State = EntityState.Modified; public void Delete(T entity) => _dbSet.Remove(entity); } ``` ### Unit of Work Pattern ```csharp public interface IUnitOfWork : IDisposable { IRepository<TEntity> Repository<TEntity>() where TEntity : class; int SaveChanges(); Task<int> SaveChangesAsync(); } public class EF6UnitOfWork : IUnitOfWork { private readonly DbContext _context; private readonly Dictionary<Type, object> _repositories = new(); public EF6UnitOfWork(DbContext context) { _context = context; } public IRepository<TEntity> Repository<TEntity>() where TEntity : class { if (!_repositories.ContainsKey(typeof(TEntity))) { _repositories[typeof(TEntity)] = new EF6Repository<TEntity>(_context); } return (IRepository<TEntity>)_repositories[typeof(TEntity)]; } public int SaveChanges() => _context.SaveChanges(); public Task<int> SaveChangesAsync() => _context.SaveChangesAsync(); public void Dispose() => _context.Dispose(); } ``` ## Connection and Context Management ### Scoped Context Lifetime Always scope DbContext to a logical unit of work: ```csharp // ASP.NET MVC: use per-request lifetime public class OrderController : Controller { private readonly MyDbContext _context; public OrderController(MyDbContext context) { _context = context; } } // Non-web: use explicit using blocks using (var context = new MyDbContext()) { var orders = context.Orders.Where(o => o.Status == "Pending").ToList(); // process orders context.SaveChanges(); } ``` ### Avoiding Long-Lived Contexts Do not: - Keep a DbContext alive across multiple HTTP requests - Share a DbContext across threads - Cache a DbContext in a static field ### Connection Resiliency Configure retry logic for transient failures: ```csharp public class MyDbConfiguration : DbConfiguration { public MyDbConfiguration() { SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy(5, TimeSpan.FromSeconds(10))); } } ``` ## Query Optimization ### Eager Loading Use `Include` to avoid N+1 queries: ```csharp var orders = context.Orders .Include(o => o.Customer) .Include(o => o.OrderItems.Select(oi => oi.Product)) .Where(o => o.OrderDate >= startDate) .ToList(); ``` ### Projection Select only needed columns: ```csharp var orderSummaries = context.Orders .Where(o => o.Status == "Shipped") .Select(o => new OrderSummaryDto { OrderId = o.Id, CustomerName = o.Customer.Name, TotalAmount = o.OrderItems.Sum(oi => oi.Quantity * oi.UnitPrice) }) .ToList(); ``` ### No-Tracking Queries Use `AsNoTracking()` for read-only scenarios: ```csharp var products = context.Products .AsNoTracking() .Where(p => p.IsActive) .ToList(); ``` ### Compiled Queries (LINQ to Entities) For frequently executed queries: ```csharp private static readonly Func<MyDbContext, int, Customer> GetCustomerById = CompiledQuery.Compile<MyDbContext, int, Customer>( (ctx, id) => ctx.Customers.FirstOrDefault(c => c.Id == id)); // Usage var customer = GetCustomerById(_context, customerId); ``` ## Handling Concurrency ### Optimistic Concurrency with RowVersion ```csharp public class Order { public int Id { get; set; } public string Status { get; set; } [Timestamp] public byte[] RowVersion { get; set; } } // Handle concurrency conflict try { context.SaveChanges(); } catch (DbUpdateConcurrencyException ex) { var entry = ex.Entries.Single(); var databaseValues = entry.GetDatabaseValues(); if (databaseValues == null) { // Entity was deleted } else { // Resolve conflict: client wins, database wins, or merge entry.OriginalValues.SetValues(databaseValues); } } ``` ## Auditing and Interception ### SaveChanges Override for Auditing ```csharp public class AuditableDbContext : DbContext { public override int SaveChanges() { var entries = ChangeTracker.Entries() .Where(e => e.Entity is IAuditable && (e.State == EntityState.Added || e.State == EntityState.Modified)); foreach (var entry in entries) { var auditable = (IAuditable)entry.Entity; auditable.ModifiedDate = DateTime.UtcNow; auditable.ModifiedBy = GetCurrentUser(); if (entry.State == EntityState.Added) { auditable.CreatedDate = DateTime.UtcNow; auditable.CreatedBy = GetCurrentUser(); } } return base.SaveChanges(); } } ``` ### Command Interception for Logging ```csharp public class LoggingInterceptor : IDbCommandInterceptor { public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> context) { LogCommand(command); } public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> context) { LogCommand(command); } public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> context) { LogCommand(command); } private void LogCommand(DbCommand command) { Debug.WriteLine($"SQL: {command.CommandText}"); } // Implement other interface members... } // Registration DbInterception.Add(new LoggingInterceptor()); ``` ## Stored Procedure Integration ### Mapping CUD Operations ```csharp modelBuilder.Entity<Order>() .MapToStoredProcedures(s => s.Insert(i => i.HasName("usp_InsertOrder")) .Update(u => u.HasName("usp_UpdateOrder")) .Delete(d => d.HasName("usp_DeleteOrder"))); ``` ### Calling Stored Procedures Directly ```csharp // Return entities var orders = context.Database.SqlQuery<Order>( "EXEC GetOrdersByCustomer @customerId", new SqlParameter("@customerId", customerId)).ToList(); // Non-query context.Database.ExecuteSqlCommand( "EXEC ArchiveOldOrders @cutoffDate", new SqlParameter("@cutoffDate", cutoffDate)); ``` ## Testing Strategies ### Integration Testing with LocalDB ```csharp [TestClass] public class OrderRepositoryTests { private MyDbContext _context; [TestInitialize] public void Setup() { var connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=TestDb;Integrated Security=True"; _context = new MyDbContext(connectionString); _context.Database.CreateIfNotExists(); } [TestCleanup] public void Cleanup() { _context.Database.Delete(); _context.Dispose(); } [TestMethod] public void CanAddOrder() { var order = new Order { Status = "New" }; _context.Orders.Add(order); _context.SaveChanges(); Assert.IsTrue(order.Id > 0); } } ``` ### Mocking with Interfaces Wrap DbContext behind an interface for unit testing: ```csharp public interface IMyDbContext { IDbSet<Order> Orders { get; } int SaveChanges(); } // In tests, mock IMyDbContext var mockContext = new Mock<IMyDbContext>(); var mockOrders = new Mock<IDbSet<Order>>(); mockContext.Setup(c => c.Orders).Returns(mockOrders.Object); ``` ## Performance Monitoring ### Database Logging ```csharp context.Database.Log = sql => Debug.WriteLine(sql); ``` ### Identifying Slow Queries Use SQL Server Profiler, Extended Events, or Query Store alongside EF6 logging to correlate slow queries with application code paths. ## Common Anti-Patterns to Avoid 1. **Lazy loading in loops**: Causes N+1 queries; use eager loading or projection 2. **Tracking entities unnecessarily**: Use `AsNoTracking()` for read-only queries 3. **Returning IQueryable from repositories**: Leaks query composition outside the data layer 4. **Ignoring connection management**: Always dispose DbContext properly 5. **Mixing ObjectContext and DbContext**: Pick one API and stick with it 6. **Skipping concurrency handling**: Add RowVersion for entities with concurrent updates
-
-
SKILL.md 3.7 KB
--- name: dotnet-entity-framework6 version: "1.0.1" category: "Data, Distributed, and AI" description: "Maintain or migrate EF6-based applications with realistic guidance on what to keep, what to modernize, and when EF Core is or is not the right next step. Use when working in an EF6 codebase or planning a data layer migration." compatibility: "Requires EF6 or a transition plan from EF6 to EF Core or modern .NET." --- # Entity Framework 6 ## Trigger On - working in an EF6 codebase on .NET Framework or modern .NET - deciding whether to keep EF6, move to modern .NET runtime, or port to EF Core - reviewing EDMX, code-first, or legacy ASP.NET/WPF/WinForms data access - planning a data layer migration strategy ## Workflow 1. **Audit current EF6 usage** before planning any migration. Identify which features the codebase depends on: ```csharp // Common EF6-specific patterns to inventory: // - EDMX designer models (check for *.edmx files) // - ObjectContext vs DbContext usage // - Lazy loading with virtual navigation properties // - Database.SqlQuery<T>() for raw SQL // - Stored procedure mappings in model // - Spatial types (DbGeography, DbGeometry) ``` 2. **Decide runtime vs ORM migration separately:** | Path | When to use | |------|-------------| | Keep EF6 on .NET Framework | Legacy app with no runtime pressure | | EF6 on modern .NET | Runtime upgrade needed, ORM migration too risky | | EF6 → EF Core | Clean data layer, no EDMX, minimal stored-procedure mapping | 3. **For maintenance work** — keep EF6 stable: - use repository + unit of work patterns to isolate data access (see references/patterns.md) - prefer `DbContext` over `ObjectContext` for new code - use `AsNoTracking()` for read-only queries - configure concurrency tokens with `[ConcurrencyCheck]` or `IsRowVersion()` 4. **For migration work** — validate each slice: - map EF6 features to EF Core equivalents (see references/migration.md) - migrate one bounded context at a time, not the entire data layer - run integration tests against the real database provider, not InMemory - verify: `dotnet ef migrations add` succeeds, queries produce equivalent results, lazy loading behavior matches expectations 5. **Do not promise EF Core features to EF6 codebases** — EF6 is stable and supported but not on the innovation path. Keep expectations realistic. ```mermaid flowchart LR A["Audit EF6 usage"] --> B{"EDMX or complex mappings?"} B -->|Yes| C["High migration cost — consider keeping EF6"] B -->|No| D["Evaluate EF Core migration"] D --> E["Migrate one context at a time"] E --> F["Integration test against real DB"] C --> G["Modernize runtime only"] F --> H["Validate query equivalence"] G --> H ``` ## Deliver - realistic EF6 maintenance or migration guidance based on actual codebase audit - clear separation between runtime upgrade and ORM upgrade work - bounded migration slices with concrete validation checkpoints - reduced risk for legacy data access changes ## Validate - EF6 feature inventory is complete before migration planning starts - migration assumptions are backed by real feature usage, not guesses - EF6-only features (EDMX, spatial types, ObjectContext patterns) are identified early - integration tests run against the real database provider, not mocks or InMemory - the proposed path avoids unnecessary churn in stable data access code ## References - references/migration.md - decision framework, migration approaches, EF6-to-EF Core feature mapping, and common pitfalls - references/patterns.md - repository and unit of work patterns, query optimization, concurrency handling, auditing, and testing strategies for EF6 codebases
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.