LINQ to SQL to Entity Framework Core Migration
Overview
Migrates .NET Framework projects from LINQ to SQL (System.Data.Linq) to Entity Framework Core. LINQ to SQL is not available in .NET 6+ — this is a migration blocker, not an optional improvement.
Reference files for detailed patterns:
ref/entity-mapping-conversion.md— DBML/attribute mapping to EF Coreref/datacontext-to-dbcontext.md— DataContext lifecycle and API migrationref/query-translation-gotchas.md— Query behavior differences (critical for runtime correctness)ref/stored-procedure-migration.md— SP/function mappingref/concurrency-and-change-tracking.md— UpdateCheck/conflict resolutionref/relationship-migration.md— EntitySet/EntityRef to navigation properties
Prerequisites
Verify LINQ to SQL usage before proceeding:
- Check for
System.Data.Linqassembly reference in the project file - Search for
.dbmlfiles in the project directory - Search for
using System.Data.Linqin code files
If none found, skip this skill.
Workflow
Migration Progress:
- [ ] Step 1: Assess LINQ to SQL usage scope
- [ ] Step 2: Set up EF Core infrastructure
- [ ] Step 3: Scaffold or convert entity model
- [ ] Step 4: Migrate DataContext to DbContext
- [ ] Step 5: Migrate stored procedures and functions
- [ ] Step 6: Fix relationship loading patterns
- [ ] Step 7: Migrate concurrency handling
- [ ] Step 8: Fix query translation issues
- [ ] Step 9: Validate
- [ ] Step 10: Remove LINQ to SQL artifacts
Step 1: Assess LINQ to SQL Usage Scope
Before making changes, inventory the full scope. This determines effort and identifies blockers.
Find and count:
.dbmlfiles and their entity/SP counts (parse the XML)- DataContext classes (search for
: DataContextor: System.Data.Linq.DataContext) - DataContext instantiation points (
new *DataContext() - Stored procedure mappings (
[Function(attribute usage) IMultipleResultsusage (blocker — no EF Core equivalent; seeref/stored-procedure-migration.md)EntitySet<T>andEntityRef<T>usage countsUpdateCheck.WhenChangedusage (requires concurrency strategy decision)- Partial class extensions of generated entities (business logic to preserve)
Flag these blockers early:
IMultipleResults→ requires ADO.NET fallback or SP redesignUpdateCheck.WhenChanged→ no direct EF Core equivalent (seeref/concurrency-and-change-tracking.md)
Step 2: Set Up EF Core Infrastructure
- Add NuGet packages:
Microsoft.EntityFrameworkCore.SqlServer(or appropriate provider)Microsoft.EntityFrameworkCore.Design(for tooling)
- Ensure the project targets .NET 6+ (should already be the case in an upgrade scenario)
Step 3: Create New EF Core Entity Model
CRITICAL: Create new files — do NOT rewrite the DBML-generated *.designer.cs file in place.
The *.designer.cs file (e.g., CompanyDB.designer.cs) was auto-generated by the LINQ to SQL O/R Designer. It is full of LINQ to SQL plumbing (INotifyPropertyChanging, EntitySet<T>, EntityRef<T>, OnXChanging/OnXChanged partial methods). Do not attempt to "convert" this file into EF Core entities — it will retain confusing artifacts and the .designer.cs name implies auto-generation.
Instead, create new clean files:
- New DbContext file — e.g.,
MyDbContext.cs(or{OriginalDataContextName}DbContext.cs). Define theDbContextsubclass withDbSet<T>properties here. - New entity class files — create one file per entity class (e.g.,
Entities/Customer.cs,Entities/Order.cs) or a singleEntities.csfile for small models. Write clean POCO classes with EF Core data annotations or Fluent API configuration. - Preserve partial class logic — if the project has partial classes extending DBML-generated entities (separate from the
.designer.csfile), migrate that business logic into the new entity files.
Recommended approach: Scaffold first, then reconcile.
- Use
dotnet ef dbcontext scaffold "ConnectionString" Microsoft.EntityFrameworkCore.SqlServer -o Entitiesto generate baseline entities from the existing database into a new folder - Compare scaffolded entities with DBML-defined entities:
- Reconcile custom C# names from DBML (the DBML may rename entities/properties differently from the database)
- Preserve custom partial class logic from existing code
- Apply Fluent API configuration for non-convention mappings
- If scaffolding is not possible (no database access), manually create entity POCOs by reading the
.dbmlXML to extract table/column definitions, then translate to EF Core attributes
See ref/entity-mapping-conversion.md for the complete attribute mapping table and DBML conversion strategy.
Important: During conversion, both LINQ to SQL and EF Core use [Table] and [Column] attributes from different namespaces. Use fully-qualified names or manage using directives carefully to avoid ambiguous reference errors.
Step 4: Migrate DataContext to DbContext
Replace all DataContext usage with the new DbContext class created in Step 3. This is not just a rename — the lifecycle model differs fundamentally.
Update all consuming code to reference the new DbContext class name and the new entity types:
Table<T>properties →DbSet<T>propertiesSubmitChanges()→SaveChanges()/SaveChangesAsync()GetChangeSet()→ChangeTracker.Entries()ExecuteCommand()→Database.ExecuteSqlRaw()ExecuteQuery<T>()→FromSqlRaw()/SqlQueryRaw<T>()DataContext.Log→ILoggerFactory/LogTo()onDbContextOptionsBuilderObjectTrackingEnabled = false→AsNoTracking()queries- Direct instantiation (
new MyDataContext(conn)) → DI-injected DbContext
Critical: If DataContext was used in using blocks and you migrate to DI-managed DbContext (scoped lifetime), remove the using blocks — the DI container manages disposal. Keeping both causes premature disposal.
See ref/datacontext-to-dbcontext.md for detailed API mappings and lifecycle patterns.
Step 5: Migrate Stored Procedures and Functions
LINQ to SQL maps SPs as methods on DataContext with [Function] attributes. EF Core has no attribute-based SP mapping.
For each stored procedure:
- SPs returning entity types →
FromSqlRaw("EXEC sp_name @p0, @p1", params) - SPs with no return →
Database.ExecuteSqlRaw() - SPs with non-entity results →
SqlQueryRaw<T>()(EF Core 8+) with[Keyless]result types - SPs with output parameters → use
SqlParameterobjects directly - UDFs →
HasDbFunction()in Fluent API
See ref/stored-procedure-migration.md for complete patterns and the IMultipleResults blocker.
Step 6: Fix Relationship Loading Patterns
LINQ to SQL lazy-loads via EntitySet<T> / EntityRef<T> by default. EF Core does NOT lazy-load by default.
This is the highest-risk area for subtle runtime bugs:
- Replace
EntitySet<T>→ICollection<T> - Replace
EntityRef<T>→ standard navigation property - Find all navigation property access patterns and add
Include()/ThenInclude()calls - Convert
DataLoadOptions.LoadWith<T>()→Include() - Convert
DataLoadOptions.AssociateWith<T>()→ filtered includes
Code that worked before (order.Customer.Name) will return null / throw NullReferenceException without explicit loading.
See ref/relationship-migration.md for detailed patterns.
Step 7: Migrate Concurrency Handling
If the project uses UpdateCheck attributes on columns, a concurrency strategy decision is needed.
Decision tree:
- Project uses
rowversion/timestampcolumn → use[Timestamp]attribute (straightforward) - Project uses
UpdateCheck.Alwayson all columns → add arowversioncolumn (recommended) or use[ConcurrencyCheck] - Project uses
UpdateCheck.WhenChanged→ no direct equivalent — recommend addingrowversioncolumn UpdateCheck.Never→ those columns simply don't get[ConcurrencyCheck]
Also migrate conflict resolution: ChangeConflictException → DbUpdateConcurrencyException (different resolution API).
See ref/concurrency-and-change-tracking.md for the complete decision tree and API mappings.
Step 8: Fix Query Translation Issues
EF Core is stricter than LINQ to SQL about query translation. The #1 source of runtime breaks:
- Client-side evaluation: LINQ to SQL silently evaluated untranslatable expressions client-side. EF Core throws. Add
.AsEnumerable()before client-side operations. - GroupBy: Complex GroupBy with materialization fails in EF Core
- Take/Skip without OrderBy: EF Core requires explicit
OrderBy - String comparison: Case-sensitivity may differ by provider
- CompiledQuery: Remove
CompiledQuery.Compile()calls — EF Core handles compilation automatically
See ref/query-translation-gotchas.md for the complete list.
Step 9: Validate
- Build all affected projects — zero errors required
- Search for remaining
System.Data.Linqreferences in code - Search for remaining
DataContext,EntitySet,EntityReftype usage - Verify navigation property loading works (test queries that traverse relationships)
- Verify stored procedure calls work
- Verify concurrency conflict handling works
Only proceed to Step 10 after validation passes. The old .dbml and .designer.cs files are useful as a reference if you need to fix issues found during validation.
Step 10: Remove LINQ to SQL Artifacts
MANDATORY — do not skip this step. LINQ to SQL artifacts serve no purpose after migration and will confuse future developers.
Delete these files (verify each exists before deleting):
*.dbmlfiles — the LINQ to SQL O/R Designer model definition (XML). Not used by EF Core.*.dbml.layoutfiles — Visual Studio designer UI layout. Not used by EF Core.- DBML-generated
*.designer.csfiles — the auto-generated DataContext and entity classes. These must have been replaced by new clean files in Step 3. Do NOT delete WinForms/WPF*.Designer.csfiles — only the one that was generated from the.dbml(typically named{DbmlName}.designer.csand containingDataContextinheritance).
Then clean up remaining references:
4. Remove System.Data.Linq assembly reference from the project file (.csproj)
5. Remove using System.Data.Linq and using System.Data.Linq.Mapping statements from all code files
6. Remove any <None Include="*.dbml"> or <Compile Include="*.designer.cs"> entries with <DependentUpon>*.dbml</DependentUpon> or <Generator>MSLinqToSQLGenerator</Generator> from the project file (SDK-style projects handle this automatically; old-style .csproj may need manual cleanup)
How to identify the DBML-generated designer file: It is the .designer.cs file that has a <DependentUpon>SomeName.dbml</DependentUpon> entry in the project file, or that contains a class inheriting from System.Data.Linq.DataContext. Do NOT confuse it with WinForms Form1.Designer.cs files — those are unrelated.
Final check: After cleanup, confirm zero *.dbml, *.dbml.layout files remain and no System.Data.Linq references exist anywhere in the project.
Disambiguation
Do NOT apply this skill to:
System.Linq(LINQ to Objects) — works in modern .NETSystem.Xml.Linq(LINQ to XML) — works in modern .NETSystem.Data.Entity(Entity Framework 6) — separate migration skillLinqToDB(linq2db) — third-party library, different migration path
Success Criteria
- New clean files created for DbContext and entity classes (not rewritten in the old
*.designer.cs) - All LINQ to SQL artifacts deleted:
*.dbml,*.dbml.layout, DBML-generated*.designer.cs - All
System.Data.Linqreferences removed from project file and code - EF Core DbContext replaces all DataContext usage
- All stored procedures migrated to EF Core patterns
- Navigation property loading is explicit (no silent null references)
- Concurrency strategy decided and implemented
- Project builds and queries execute correctly