| name | migrating-edmx-to-code-first |
| description | Migrates Entity Framework 6 EDMX-based models (Database-First/Model-First) to EF Core Code-First. Use when upgrading projects containing .edmx files, .Context.tt templates, EntityClient connection strings, or ObjectContext references. Triggers for "migrate EDMX", "convert EDMX to code first", "EF6 to EF Core", "remove EDMX", "database first to code first", and "scaffold DbContext". Also relevant when encountering EntitySQL, EntityObject, or ObjectSet patterns during .NET modernization.
|
| metadata | {"traits":".NET|CSharp|VisualBasic|DotNetCore","discovery":"lazy"} |
EDMX to Code-First Migration
Overview
Migrate Entity Framework EDMX files to EF Core Code-First. EDMX is not supported in EF Core, so the migration scaffolds a code-based model from the existing database and replaces all EDMX-dependent patterns.
STOP — is the old app still running? If a .NET Framework host and a new .NET host share
this database while both are live (a side-by-side migration), load the
managing-shared-database-schema skill first and follow it wherever it conflicts with the
guidance below: get_instructions(kind='skill', query='managing-shared-database-schema')
Two instructions in this skill are correct for a full cutover and destructive during a
side-by-side window: deleting the EF6 Migrations/ folder, and calling Database.Migrate()
at startup. During the window, the EF6 migration assets are the live schema ledger for the
host that still owns them, and a second host migrating at startup is a second schema writer.
If you cannot tell, assume the database is shared. Unless you can positively confirm the
old host is retired or runs against its own database, take the shared branch — do not delete
the EF6 migration assets, do not enable startup migration, and load the skill first.
Scope: This skill targets EDMX-based (Database-First/Model-First) projects. If the project already uses EF6 Code-First (no .edmx files), use the migrating-ef6-code-first-to-ef-core skill instead. For DbContext registration and DI setup during ASP.NET Core migration, also apply the migrating-ef-dbcontext skill — it is complementary to this one.
Workflow
Migration Progress:
- [ ] Step 1: Detect and assess
- [ ] Step 2: Install packages
- [ ] Step 3: Extract connection string
- [ ] Step 4: Scaffold from database
- [ ] Step 5: Register DbContext
- [ ] Step 6: Preserve customizations
- [ ] Step 7: Handle breaking changes
- [ ] Step 8: Cleanup
- [ ] Step 9: Validate
Step 1: Detect and Assess
- Search for
.edmx files in the project
- Note the DbContext name from
.Context.tt or .Designer.cs
- Find EntityClient connection strings in
web.config/app.config
- Check for partial class customizations (
.cs files matching entity names)
- Confirm with the user before proceeding: if the project targets .NET Framework, advise upgrading to .NET 8 or later and updating EntityFramework to 6.5.1 first. Explain that the EF Core model will be scaffolded from the database using the extracted connection string, and confirm whether to migrate to EF Core or stay with EF6
Step 2: Install Packages
Install EF Core packages matching the target framework. Use matching major versions because EF Core packages are tied to specific .NET runtime versions.
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" />
Use the EF Core major version that matches the project's target framework (e.g., EF Core 8.x for .NET 8, EF Core 10.x for .NET 10). Use the managing-package-references skill to add these packages — it handles version determination, CPM detection, and NuGet feed lookup.
Step 3: Extract Connection String
EntityClient connection strings embed the real ADO.NET string. Extract it and place in appsettings.json:
provider connection string="Data Source=...;Initial Catalog=..."
Extract ADO.NET connection string and add to appsettings.json:
{"ConnectionStrings": {"MyEntities": "Data Source=...;Initial Catalog=..."}}
Important: Never store passwords or secrets in source code or config files. Use Secret Manager, environment variables, or Azure Key Vault for production secrets.
Step 4: Scaffold from Database
The scaffold command requires a live database connection. Provide the user with the command to run — do not execute it directly, as it requires database access and the connection string may contain credentials.
# User must run this command — it connects to the database to reverse-engineer the model
dotnet ef dbcontext scaffold "<ADO.NET-connection-string-from-Step-3>" Microsoft.EntityFrameworkCore.SqlServer --output-dir Models --context MyDbContext
Replace <ADO.NET-connection-string-from-Step-3> with the connection string extracted in Step 3. Replace the provider name if the project uses a different database.
Useful options: --table (specific tables only), --schema (specific schema), --data-annotations (use attributes instead of fluent API), --force (overwrite existing files), --no-pluralize (keep table names as-is)
Step 5: Register DbContext
Use the migrating-ef-dbcontext skill to register the scaffolded DbContext in dependency injection, migrate connection strings to appsettings.json, replace direct instantiations with constructor injection, and migrate database initializers. That skill handles both EF6 and EF Core registration patterns.
Step 6: Preserve Customizations
Merge entity classes: Remove the newly scaffolded entity classes and update the scaffolded OnModelCreating code to reference the existing old entity classes instead. Merge any scaffolded configuration (column mappings, relationships) into the old entities or their partial class files. This preserves all existing customizations, partial class extensions, and business logic.
Migrate EntitySQL to LINQ: EntitySQL is not supported in EF Core.
- Find:
ObjectQuery<T>, EntityCommand, EntityConnection, .eSQL files
- Replace with LINQ or
FromSql()
context.Customers.FromSql($"SELECT * FROM Customers WHERE City = {city}")
ObjectContext → DbContext: EF Core uses DbContext exclusively.
- Replace
ObjectContext with DbContext, ObjectSet<T> with DbSet<T>
- Remove
EntityObject base class
- Replace
CreateObjectSet<T>() with Set<T>()
- Replace
DbEntityEntry<T> with EntityEntry<T>
- Replace
Database.Log with Microsoft.Extensions.Logging or LogTo in OnConfiguring
- Replace
System.Data.Entity usings with Microsoft.EntityFrameworkCore — except inside a retained EF6 Migrations/ folder, whose DbMigration classes are built on System.Data.Entity.Migrations (see the full-cutover gate under Migrations below)
Stored procedures: EDMX function-import mappings do not exist in EF Core.
- For read operations: use
FromSql to map result sets to entities:
context.Customers.FromSql($"EXEC GetCustomersByCity {city}");
- For CUD operations: configure stored procedure mapping in
OnModelCreating (EF Core 7.0+):
modelBuilder.Entity<Customer>()
.InsertUsingStoredProcedure("Customer_Insert", sp => sp.HasParameter(c => c.Name));
- For unsupported scenarios: use
context.Database.ExecuteSql(...).
Navigation loading:
Independent associations: EF Core does not support independent associations (relationships defined in the EDMX without explicit foreign key properties on the entity). Add FK properties to the entity classes or configure shadow properties in OnModelCreating:
public int CustomerId { get; set; }
public Customer Customer { get; set; }
modelBuilder.Entity<Order>()
.HasOne(o => o.Customer)
.WithMany(c => c.Orders)
.HasForeignKey("CustomerId");
Step 7: Handle Breaking Changes
Lazy loading: EF Core disables lazy loading by default.
- Option 1: Install
Microsoft.EntityFrameworkCore.Proxies, use UseLazyLoadingProxies()
- Option 2 (preferred): Use
.Include() for eager loading
Complex types: In EF Core 7 and earlier, [ComplexType] is replaced by owned entities:
modelBuilder.Entity<Customer>().OwnsOne(c => c.Address);
EF Core 8+ reintroduces native complex type support. If targeting .NET 8+, use ComplexProperty() instead:
modelBuilder.Entity<Customer>().ComplexProperty(c => c.Address);
Many-to-many: EF Core 5.0+ auto-generates the join table for simple many-to-many relationships. Pre-5.0 needs an explicit join entity. Even on 5.0+, explicit join entity configuration is required when the join table contains payload columns (columns other than the two foreign keys).
Migrations:
Check first — does anything else still write this database? If an EF6 host, job, or
pipeline still deploys schema here, the EF6 migration assets stay: that folder is those
writers' only way to deploy further schema changes, and the loss is not recoverable. Follow
managing-shared-database-schema and stop after the baseline registration below.
Advise the user to apply all pending EF6 migrations to their database before starting the EF Core migration — this is a manual prerequisite. Once confirmed, scaffold an EF Core InitialCreate baseline into its own folder, outside Migrations/:
dotnet ef migrations add InitialCreate --output-dir EFCoreMigrations
Do not let this land in Migrations/. That is EF Core's default output directory and also
the folder holding the retained EF6 DbMigration classes. Two things break if they share it:
the exclusion glob recommended in Step 8 (Migrations\**) is recursive and would drop the EF
Core migrations from compilation as well, and any later folder-level EF6 teardown would delete
the EF Core snapshot along with them.
Note that this baseline is not empty: with no prior snapshot, EF Core emits a full CreateTable set for the whole model. Do not apply it to the existing database — register it as already-applied by inserting its MigrationId into __EFMigrationsHistory (creating that table first, since a database managed only by EF6 does not have one). This avoids schema conflicts between the two migration systems.
Retiring the EF6 migration assets happens only at a confirmed full cutover, with no other writer
left — the migrating-ef6-code-first-to-ef-core skill carries that confirmation gate and the
removal inventory in its cutover-teardown reference.
Database initializers: EF Core does not support automatic migrations or database initializers (CreateDatabaseIfNotExists, DropCreateDatabaseIfModelChanges, DropCreateDatabaseAlways, MigrateDatabaseToLatestVersion). Remove all Database.SetInitializer calls and apply migrations from a single deployment step.
Do not call Database.Migrate() or EnsureCreated() at startup while another host writes
this schema. Every instance of the new host would race to migrate on boot, against a
database whose ledger belongs to the EF6 runner. Apply schema from one deployable migrator
instead; managing-shared-database-schema covers the ownership decision.
Configuration classes:
When using IEntityTypeConfiguration<T>, register all configurations in OnModelCreating via modelBuilder.ApplyConfigurationsFromAssembly(typeof(MyDbContext).Assembly).
Data validation: EF Core does not perform data validation on SaveChanges(). If the app relied on EF6's built-in validation (IValidatableObject invoked by SaveChanges), add an alternative validation strategy — ASP.NET model validation, FluentValidation, or manual checks before SaveChanges().
Async methods (optional): Convert ToList() → ToListAsync(), SaveChanges() → SaveChangesAsync(). Recommended but not required for the migration to succeed.
Step 8: Cleanup
Delete these EDMX artifacts:
*.edmx, *.edmx.diagram, *.Designer.cs, *.Context.tt, *.tt files
<EntityDeploy> entries from the project file
- Remove
EntityFramework and any related EF6 packages (e.g., EntityFramework.SqlServer, EntityFramework.SqlServerCompact) using the project's package management approach — unless a retained EF6 Migrations/ folder is still compiled, which depends on the EntityFramework reference. In that case either keep the reference or exclude the folder with <Compile Remove="Migrations\**" />. Renaming the folder does not exclude it: SDK-style projects glob **/*.cs.
Step 9: Validate
Compilation alone is not sufficient. Many EF6→EF Core differences only surface at runtime.
LLM responsibilities (automated):
- Build the project and fix any compilation errors
- Confirm no EDMX artifacts remain in the project (
.edmx, .tt, <EntityDeploy>, and — unless EF6 migrations are being retained for a still-live host — the EntityFramework package reference)
- Run existing unit tests
User responsibilities (require database access and manual verification):
4. Verify all CRUD operations work end-to-end against a test database
5. Check navigation property loading — confirm eager loading (.Include()) returns expected related data
6. Validate change tracking behavior matches expectations (Added, Modified, Deleted states)
7. Test stored procedure calls and FromSql/ExecuteSql results
8. Compare query results with the EF6 version for any complex LINQ queries
Present items 4–8 as a checklist for the user — the LLM cannot validate runtime database behavior.
Breaking Changes Quick Reference
| EF6 | EF Core | Action |
|---|
| EDMX | Not supported | Scaffold from DB |
| EntitySQL | LINQ/FromSql | Rewrite queries |
| EntityClient connection | Standard ADO.NET | Extract provider string |
| ObjectContext | DbContext | Replace references |
| DbEntityEntry | EntityEntry | Update type references |
| Database.Log | Microsoft.Extensions.Logging / LogTo | Configure logging in DI or OnConfiguring |
| System.Data.Entity | Microsoft.EntityFrameworkCore | Replace usings |
| [ComplexType] | OwnsOne() (pre-8) / ComplexProperty() (8+) | Configure in OnModelCreating |
| EntityTypeConfiguration | IEntityTypeConfiguration | Update interface |
| HasDatabaseGeneratedOption | ValueGeneratedOnAdd | Replace calls |
| EntityReference.Load() | Entry().Reference().Load() | Update syntax |
| Independent associations (no FK property) | Not supported | Add explicit FK properties or configure shadow properties |
DbSet.Add marks all reachable entities Added | Entities with store-generated key set → Unchanged; unset → Added | Test all Add/Update/Attach scenarios with entity graphs |
DbSet.Attach marks all reachable entities Unchanged | Entities with store-generated key set → Unchanged; unset → Added | Verify Attach behavior in disconnected-entity workflows |
| Table names = pluralized entity class name | Table names = DbSet<T> property name; falls back to class name | Verify table mappings or add explicit .ToTable() calls |
| Auto-creates DB/schema, checks model compatibility | No automatic initialization | Apply migrations from one deployment step. Never at startup while another host shares this database — see . Otherwise only where that deployable is the single designated migration runner: a host scaled to N instances runs N migrators concurrently |
Success Criteria
- All
.edmx files and related artifacts removed
- Code-based model scaffolded and compiling
- Partial class customizations preserved
- EntitySQL converted to LINQ or
FromSql()
- Connection strings in
appsettings.json
- (Optional) Lazy loading strategy configured
- Complex types and many-to-many relationships migrated
- (Optional) Async methods used where appropriate
- Project builds, tests pass, queries return correct results
- Adding a new EF Core migration produces one without any operations (model matches database)
- No secrets are stored in plain text