| name | migrating-linq-to-sql-to-ef-core |
| description | Migrates LINQ to SQL (System.Data.Linq) data access layer to Entity Framework Core during .NET Framework to modern .NET upgrades. Covers DataContext to DbContext conversion, DBML entity mapping to EF Core model configuration, stored procedure migration, query translation differences, and concurrency handling changes. Use when assessment detects LINQ to SQL usage or when upgrading projects referencing System.Data.Linq. Triggers for "LINQ to SQL", "System.Data.Linq", "DataContext migration", "DBML to EF Core", "linq2sql", "migrate LINQ to SQL".
|
| metadata | {"traits":".NET|CSharp|VisualBasic|DotNetCore"} |
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:
references/entity-mapping-conversion.md — DBML/attribute mapping to EF Core
references/datacontext-to-dbcontext.md — DataContext lifecycle and API migration
references/query-translation-gotchas.md — Query behavior differences (critical for runtime correctness)
references/stored-procedure-migration.md — SP/function mapping
references/concurrency-and-change-tracking.md — UpdateCheck/conflict resolution
references/relationship-migration.md — EntitySet/EntityRef to navigation properties
Prerequisites
Verify LINQ to SQL usage before proceeding:
- Check for
System.Data.Linq assembly reference in the project file
- Search for
.dbml files in the project directory
- Search for
using System.Data.Linq in 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:
.dbml files and their entity/SP counts (parse the XML)
- DataContext classes (search for
: DataContext or : System.Data.Linq.DataContext)
- DataContext instantiation points (
new *DataContext()
- Stored procedure mappings (
[Function( attribute usage)
IMultipleResults usage (blocker — no EF Core equivalent; see references/stored-procedure-migration.md)
EntitySet<T> and EntityRef<T> usage counts
UpdateCheck.WhenChanged usage (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 redesign
UpdateCheck.WhenChanged → no direct EF Core equivalent (see references/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 the DbContext subclass with DbSet<T> properties here.
- New entity class files — create one file per entity class (e.g.,
Entities/Customer.cs, Entities/Order.cs) or a single Entities.cs file 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.cs file), 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 Entities to 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
.dbml XML to extract table/column definitions, then translate to EF Core attributes
See references/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> properties
SubmitChanges() → SaveChanges() / SaveChangesAsync()
GetChangeSet() → ChangeTracker.Entries()
ExecuteCommand() → Database.ExecuteSqlRaw()
ExecuteQuery<T>() → FromSqlRaw() / SqlQueryRaw<T>()
DataContext.Log → ILoggerFactory / LogTo() on DbContextOptionsBuilder
ObjectTrackingEnabled = 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 references/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
SqlParameter objects directly
- UDFs →
HasDbFunction() in Fluent API
See references/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 references/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/timestamp column → use [Timestamp] attribute (straightforward)
- Project uses
UpdateCheck.Always on all columns → add a rowversion column (recommended) or use [ConcurrencyCheck]
- Project uses
UpdateCheck.WhenChanged → no direct equivalent — recommend adding rowversion column
UpdateCheck.Never → those columns simply don't get [ConcurrencyCheck]
Also migrate conflict resolution: ChangeConflictException → DbUpdateConcurrencyException (different resolution API).
See references/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 references/query-translation-gotchas.md for the complete list.
Step 9: Validate
- Build all affected projects — zero errors required
- Search for remaining
System.Data.Linq references in code
- Search for remaining
DataContext, EntitySet, EntityRef type 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):
*.dbml files — the LINQ to SQL O/R Designer model definition (XML). Not used by EF Core.
*.dbml.layout files — Visual Studio designer UI layout. Not used by EF Core.
- DBML-generated
*.designer.cs files — 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.cs files — only the one that was generated from the .dbml (typically named {DbmlName}.designer.cs and containing DataContext inheritance).
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 .NET
System.Xml.Linq (LINQ to XML) — works in modern .NET
System.Data.Entity (Entity Framework 6) — separate migration skill
LinqToDB (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.Linq references 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