@@ -22,40 +22,164 @@ dotnet test tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/ -c Release
2222
2323## Architecture Overview
2424
25- This repository extends Entity Framework Core with performance enhancements and testing utilities across multiple database providers.
25+ This repository extends Entity Framework Core with performance enhancements and testing utilities across multiple database providers. The architecture follows a layered approach with shared abstractions and provider-specific implementations.
2626
2727### Core Package Structure
2828
2929** Runtime Packages (` src/ ` )** :
30- - ` Thinktecture.EntityFrameworkCore.Relational ` - Base abstractions and shared functionality
31- - ` Thinktecture.EntityFrameworkCore.BulkOperations ` - Multi-provider bulk operations
32- - ` Thinktecture.EntityFrameworkCore.SqlServer ` - SQL Server specific features
33- - ` Thinktecture.EntityFrameworkCore.Sqlite ` - SQLite specific features
34- - ` Thinktecture.EntityFrameworkCore.Testing ` - Base testing infrastructure
35- - ` Thinktecture.EntityFrameworkCore.SqlServer.Testing ` - SQL Server test helpers
36- - ` Thinktecture.EntityFrameworkCore.Sqlite.Testing ` - SQLite test helpers
30+
31+ 1 . ** ` Thinktecture.EntityFrameworkCore.Relational ` ** - Foundation layer
32+ - Base abstractions for all relational providers
33+ - Window functions (` RowNumber ` , ` PartitionBy ` , ` OrderBy ` )
34+ - LEFT JOIN support via ` LeftJoin() ` extension methods
35+ - Table hints abstraction (` ITableHint ` , ` WithTableHints() ` )
36+ - Nested transaction support (` NestedRelationalTransactionManager ` )
37+ - Entity data readers (` IEntityDataReader ` , ` EntityDataReader ` )
38+ - Default schema handling (` IDbDefaultSchema ` , ` DefaultSchemaModelCustomizer ` )
39+ - Tenant database provider infrastructure (` ITenantDatabaseProvider ` )
40+ - Query translation extensions and SQL expression types
41+
42+ 2 . ** ` Thinktecture.EntityFrameworkCore.BulkOperations ` ** - Bulk operations layer
43+ - Provider-agnostic interfaces:
44+ - ` IBulkInsertExecutor ` - Bulk insert operations
45+ - ` IBulkUpdateExecutor ` - Bulk update operations
46+ - ` IBulkInsertOrUpdateExecutor ` - Bulk upsert (MERGE) operations
47+ - ` ITruncateTableExecutor ` - Table truncation
48+ - Temp table abstractions:
49+ - ` ITempTableCreator ` - Creates temp tables with lifecycle management
50+ - ` ITempTableReference ` - Represents a temp table instance
51+ - ` ITempTableQuery<T> ` - Queryable wrapper with automatic cleanup
52+ - ` ITempTableBulkInsertExecutor ` - Bulk insert into temp tables
53+ - Property selection strategies:
54+ - ` IEntityPropertiesProvider ` - Base interface for property filtering
55+ - ` IncludingEntityPropertiesProvider ` - Include specific properties
56+ - ` ExcludingEntityPropertiesProvider ` - Exclude specific properties
57+ - Temp table name management with suffix leasing for concurrency
58+ - Collection parameter support (` ICollectionParameterFactory ` , ` ScalarCollectionParameter ` )
59+
60+ 3 . ** ` Thinktecture.EntityFrameworkCore.SqlServer ` ** - SQL Server implementation
61+ - Bulk operations via ` SqlBulkCopy ` (inserts) and MERGE statements (updates/upserts)
62+ - SQL Server-specific options:
63+ - ` SqlServerBulkInsertOptions ` - Bulk insert configuration
64+ - ` SqlServerBulkUpdateOptions ` - Bulk update configuration
65+ - ` SqlServerBulkInsertOrUpdateOptions ` - Upsert configuration
66+ - ` SqlServerTempTableBulkInsertOptions ` - Temp table bulk insert options
67+ - Table hints: ` SqlServerTableHint ` enum with values like ` NoLock ` , ` ReadPast ` , ` UpdLock ` , etc.
68+ - Temp table support with SQL Server-specific features
69+ - JSON collection parameters for passing complex data structures
70+ - Window function implementations
71+ - Migration customizations (` ThinktectureSqlServerMigrationsSqlGenerator ` )
72+ - Context factories for managing connections and transactions
73+ - Query translation and SQL generation customizations
74+
75+ 4 . ** ` Thinktecture.EntityFrameworkCore.Sqlite ` ** - SQLite implementation
76+ - Simplified bulk operations using INSERT statements
77+ - SQLite-specific options:
78+ - ` SqliteBulkInsertOptions ` - Bulk insert configuration
79+ - ` SqliteBulkUpdateOptions ` - Bulk update configuration
80+ - ` SqliteBulkInsertOrUpdateOptions ` - Upsert configuration
81+ - ` SqliteAutoIncrementBehavior ` - Control auto-increment handling
82+ - Temp table support adapted for SQLite limitations
83+ - Command builders for generating SQLite-compatible SQL
84+ - Query translation customizations for SQLite dialect
85+
86+ 5 . ** ` Thinktecture.EntityFrameworkCore.Testing ` ** - Testing infrastructure base
87+ - Test context providers (` ITestDbContextProvider ` , ` TestDbContextProviderBuilder ` )
88+ - Migration execution strategies:
89+ - ` IMigrationExecutionStrategy ` - Base interface
90+ - ` MigrationExecutionStrategy ` - Runs pending migrations
91+ - ` EnsureCreatedMigrationExecutionStrategy ` - Uses EnsureCreated
92+ - ` NoMigrationExecutionStrategy ` - No migration execution
93+ - Command capturing (` CommandCapturingInterceptor ` ) for SQL verification
94+ - Per-context model cache (` CachePerContextModelCacheKeyFactory ` )
95+ - Logging infrastructure (` SubLoggerFactory ` , ` TestingLoggingOptions ` )
96+ - Async enumerable helpers for testing
97+
98+ 6 . ** ` Thinktecture.EntityFrameworkCore.SqlServer.Testing ` ** - SQL Server test helpers
99+ - ` SqlServerDbContextIntegrationTests ` - Base class for SQL Server integration tests
100+ - ` SqlServerTestDbContextProvider ` - SQL Server-specific test context provider
101+ - Test isolation via lock tables (` SqlServerLockTableOptions ` )
102+ - Shared connection management for test performance
103+
104+ 7 . ** ` Thinktecture.EntityFrameworkCore.Sqlite.Testing ` ** - SQLite test helpers
105+ - ` SqliteDbContextIntegrationTests<T> ` - Base class for SQLite integration tests
106+ - ` SqliteTestDbContextProvider ` - SQLite-specific test context provider
107+ - In-memory database support for fast tests
108+ - Context provider factory for test fixtures
37109
38110** Test Structure (` tests/ ` )** :
39111- Tests mirror the ` src/ ` structure with ` .Tests ` suffix
40112- ` TestHelpers ` package provides shared test utilities
113+ - Integration tests use provider-specific base classes
41114
42115### Key Architectural Patterns
43116
44117** Bulk Operations Architecture** :
45- - Provider-agnostic interfaces (` IBulkInsertExecutor ` , ` IBulkUpdateExecutor ` , etc.)
46- - SQL Server implementation uses ` SqlBulkCopy ` for inserts and MERGE statements for updates
47- - Context factories manage connection lifecycle and transaction handling
48- - Strongly-typed options classes inherit from base interfaces
118+ - ** Provider-agnostic design** : Interfaces in ` BulkOperations ` package define contracts
119+ - ** Provider-specific implementations** : SQL Server uses ` SqlBulkCopy ` for fast inserts; SQLite uses batched INSERT statements
120+ - ** Context factories** : Manage connection lifecycle, transactions, and resource cleanup
121+ - ** Options pattern** : Strongly-typed options classes (` SqlServerBulkInsertOptions ` , etc.) configure behavior
122+ - ** Property providers** : Control which entity properties participate in bulk operations
123+ - ** Owned entity support** : Special handling for EF Core owned entities with complex object graphs
49124
50125** Temp Tables Architecture** :
51- - ` ITempTableCreator ` creates temp tables with ` ITempTableReference ` for lifecycle management
52- - ` ITempTableQuery<T> ` wraps ` IQueryable<T> ` with automatic cleanup via ` IAsyncDisposable `
53- - Integration with bulk operations via ` ITempTableBulkInsertExecutor `
126+ - ** Lifecycle management** : ` ITempTableCreator ` creates tables; ` ITempTableReference ` handles cleanup
127+ - ** Queryable integration** : ` ITempTableQuery<T> ` wraps ` IQueryable<T> ` with ` IAsyncDisposable ` for automatic cleanup
128+ - ** Bulk insert integration** : ` ITempTableBulkInsertExecutor ` enables fast population of temp tables
129+ - ** Name management** : Suffix-based naming with leasing mechanism prevents conflicts in concurrent scenarios
130+ - ** Primary key strategies** : Multiple providers (` ConfiguredPrimaryKeyPropertiesProvider ` , ` AdaptiveEntityTypeConfigurationPrimaryKeyPropertiesProvider ` ) control PK creation
131+ - ** Caching** : Statement caching (` TempTableStatementCache ` ) improves performance for repeated operations
132+ - ** Provider-specific features** : SQL Server supports persistent temp tables; SQLite uses temporary tables
133+
134+ ** Window Functions Architecture** :
135+ - ** Fluent API** : ` EF.Functions.RowNumber() ` , ` EF.Functions.PartitionBy() ` , ` EF.Functions.OrderBy() `
136+ - ** Server-side evaluation** : Translates to native SQL window functions
137+ - ** Provider support** : Implemented in both SQL Server and SQLite (via query translators)
138+ - ** Expression-based** : Uses custom SQL expressions (` WindowFunctionExpression ` , ` WindowFunctionPartitionByExpression ` )
139+
140+ ** LEFT JOIN Support** :
141+ - ** Extension methods** : Multiple ` LeftJoin() ` overloads on ` IQueryable<T> `
142+ - ** Result type** : Returns ` LeftJoinResult<TOuter, TInner> ` with nullable inner entity
143+ - ** Query translation** : Custom expression visitors translate to SQL LEFT JOIN
144+ - ** Type safety** : Strongly-typed results with proper null handling
145+
146+ ** Table Hints (SQL Server)** :
147+ - ** Type-safe API** : ` WithTableHints() ` extension accepts ` SqlServerTableHint ` enum
148+ - ** Query integration** : Hints applied via custom query translation
149+ - ** Common hints** : ` NoLock ` , ` ReadPast ` , ` UpdLock ` , ` HoldLock ` , ` RowLock ` , ` PageLock ` , ` TabLock `
150+ - ** Limited variants** : ` SqlServerTableHintLimited ` for restricted contexts
151+
152+ ** Nested Transactions** :
153+ - ** Manager** : ` NestedRelationalTransactionManager ` wraps EF Core transaction manager
154+ - ** Transaction types** : ` RootNestedDbContextTransaction ` (physical), ` ChildNestedDbContextTransaction ` (logical)
155+ - ** Savepoint simulation** : Child transactions use commit/rollback tracking without actual nested transactions
156+ - ** Lifecycle tracking** : Maintains transaction stack for proper cleanup
157+
158+ ** Collection Parameters** :
159+ - ** Scalar collections** : ` ScalarCollectionParameter<T> ` for primitive value lists
160+ - ** Factory pattern** : ` ICollectionParameterFactory ` creates provider-specific implementations
161+ - ** SQL Server JSON** : ` JsonCollectionParameter ` uses JSON for complex collections
162+ - ** Query translation** : Integrates with EF Core query pipeline for parameterized queries
163+
164+ ** Tenant Database Support** :
165+ - ** Provider interface** : ` ITenantDatabaseProvider ` abstracts tenant-specific database selection
166+ - ** Factory** : ` ITenantDatabaseProviderFactory ` creates providers per query context
167+ - ** Query integration** : Custom query context factory injects tenant information
168+ - ** Dummy implementation** : Default no-op provider for single-database scenarios
169+
170+ ** Entity Data Readers** :
171+ - ** ADO.NET bridge** : ` IEntityDataReader ` exposes entities as ` IDataReader `
172+ - ** Bulk operation support** : Enables ` SqlBulkCopy ` to read from entity collections
173+ - ** Property navigation** : ` PropertyWithNavigations ` handles complex property paths
174+ - ** Value extraction** : Supports regular properties, shadow properties, and navigation properties
175+ - ** Factory pattern** : ` IEntityDataReaderFactory ` creates readers from entity collections
54176
55177** Multi-Provider Support** :
56- - Shared interfaces in BulkOperations with provider-specific implementations
57- - SQL Server: Table hints, temp tables, window functions, tenant database support
58- - SQLite: Simplified bulk operations, window functions, limitation handling
178+ - ** Layered abstractions** : Relational → BulkOperations → Provider-specific
179+ - ** Shared infrastructure** : Common code in base packages; providers implement specifics
180+ - ** SQL Server features** : Table hints, JSON parameters, MERGE statements, temp tables
181+ - ** SQLite adaptations** : Simplified bulk operations, command builders, dialect handling
182+ - ** Extension points** : Providers customize query translation, SQL generation, and migration handling
59183
60184## Testing Infrastructure
61185
@@ -118,25 +242,157 @@ This repository extends Entity Framework Core with performance enhancements and
118242- SQLite: Account for concurrency limitations, DDL locks, missing server features
119243- Add conditional code paths only when necessary; maintain consistent API surface where possible
120244
121- ### Essential Patterns
245+ ### Essential Patterns and API Usage
246+
247+ #### Bulk Operations
248+ ``` csharp
249+ // Bulk insert entities
250+ await dbContext .BulkInsertAsync (entities , options =>
251+ {
252+ options .PropertyProvider = // optional: control which properties to insert
253+ });
254+
255+ // Bulk update entities
256+ await dbContext .BulkUpdateAsync (entities , options =>
257+ {
258+ options .PropertyProvider = // optional: control which properties to update
259+ });
260+
261+ // Bulk insert or update (upsert/MERGE)
262+ await dbContext .BulkInsertOrUpdateAsync (entities , options =>
263+ {
264+ // SQL Server: Uses MERGE statement
265+ // SQLite: Uses INSERT ... ON CONFLICT
266+ });
267+
268+ // Truncate table
269+ await dbContext .TruncateTableAsync <MyEntity >();
270+ ```
271+
272+ #### Temp Tables
273+ ``` csharp
274+ // Create and populate temp table from entities
275+ await using var tempTable = await dbContext .BulkInsertIntoTempTableAsync (entities );
276+
277+ // Create and populate temp table from scalar values
278+ await using var tempTable = await dbContext .BulkInsertValuesIntoTempTableAsync (
279+ new [] { 1 , 2 , 3 },
280+ builder => builder .HasColumn <int >(" Value" )
281+ );
282+
283+ // Use temp table in queries
284+ var results = await tempTable .Query
285+ .Join (dbContext .Orders , t => t .Id , o => o .CustomerId , (t , o ) => o )
286+ .ToListAsync ();
287+
288+ // Temp table is automatically dropped when disposed
289+ ```
290+
291+ #### Window Functions
292+ ``` csharp
293+ // Row number with partition and order
294+ var query = dbContext .Orders
295+ .Select (o => new
296+ {
297+ Order = o ,
298+ RowNum = EF .Functions .RowNumber (
299+ EF .Functions .PartitionBy (o .CustomerId ),
300+ EF .Functions .OrderBy (o .OrderDate )
301+ )
302+ });
303+
304+ // Multiple partitions and ordering
305+ var rowNum = EF .Functions .RowNumber (
306+ EF .Functions .PartitionBy (o .Category , o .Region ),
307+ EF .Functions .OrderBy (o .Date ).ThenByDescending (o .Amount )
308+ );
309+ ```
310+
311+ #### LEFT JOIN
312+ ``` csharp
313+ // LEFT JOIN with null-safe result
314+ var query = dbContext .Customers
315+ .LeftJoin (
316+ dbContext .Orders ,
317+ customer => customer .Id ,
318+ order => order .CustomerId ,
319+ (customer , order ) => new { Customer = customer , Order = order .Value }
320+ )
321+ .Where (x => x .Order == null || x .Order .Amount > 100 );
322+ ```
323+
324+ #### Table Hints (SQL Server)
325+ ``` csharp
326+ // Single hint
327+ var query = dbContext .Orders
328+ .WithTableHints (SqlServerTableHint .NoLock );
329+
330+ // Multiple hints
331+ var query = dbContext .Orders
332+ .WithTableHints (SqlServerTableHint .NoLock , SqlServerTableHint .ReadPast );
333+ ```
334+
335+ #### Nested Transactions
336+ ``` csharp
337+ // Outer transaction
338+ await using var transaction1 = await dbContext .Database .BeginTransactionAsync ();
339+
340+ // Nested transaction (logical savepoint)
341+ await using var transaction2 = await dbContext .Database .BeginTransactionAsync ();
342+
343+ await transaction2 .CommitAsync (); // Commits nested transaction
344+ await transaction1 .CommitAsync (); // Commits outer transaction
345+ ```
346+
347+ #### Property Selection in Bulk Operations
122348``` csharp
123- // Proper async method signature
349+ // Include specific properties
350+ await dbContext .BulkInsertAsync (entities , options =>
351+ {
352+ options .PropertyProvider = IncludingEntityPropertiesProvider .Include (
353+ e => e .Name ,
354+ e => e .Email
355+ );
356+ });
357+
358+ // Exclude specific properties
359+ await dbContext .BulkUpdateAsync (entities , options =>
360+ {
361+ options .PropertyProvider = ExcludingEntityPropertiesProvider .Exclude (
362+ e => e .CreatedAt ,
363+ e => e .Id
364+ );
365+ });
366+ ```
367+
368+ #### Collection Parameters
369+ ``` csharp
370+ // Create scalar collection parameter
371+ var param = dbContext .CreateScalarCollectionParameter (new [] { 1 , 2 , 3 });
372+
373+ // Use in queries (provider translates to appropriate SQL)
374+ var orders = await dbContext .Orders
375+ .Where (o => param .Contains (o .CustomerId ))
376+ .ToListAsync ();
377+ ```
378+
379+ #### General Async Patterns
380+ ``` csharp
381+ // Proper async method signature with cancellation
124382public async Task < Result > ProcessAsync (CancellationToken cancellationToken = default )
125383{
126384 ArgumentNullException .ThrowIfNull (parameter );
127385 var data = await GetDataAsync (cancellationToken );
128386 return ProcessData (data );
129387}
130388
131- // Bulk operations usage
132- await ActDbContext .BulkInsertAsync (entities );
133- await using var tempTable = await ActDbContext .BulkInsertIntoTempTableAsync (values );
134-
135- // Table hints (SQL Server)
136- query .WithTableHints (SqlServerTableHint .NoLock )
389+ // Always prefer server-side evaluation
390+ var query = dbContext .Orders
391+ .Where (o => o .Status == OrderStatus .Pending ) // Server-side
392+ .AsNoTracking (); // Read-only optimization
137393
138- // Window functions
139- EF . Functions . WindowFunction ( function , arg , EF . Functions . PartitionBy ( .. .), EF . Functions . OrderBy ( .. .))
394+ // Avoid client evaluation
395+ var results = await query . ToListAsync (); // Execute on server first
140396```
141397
142398## Important Files to Know
0 commit comments