Skip to content

Commit d744e8c

Browse files
committed
update custom instructions
1 parent 4dadfeb commit d744e8c

File tree

6 files changed

+1051
-405
lines changed

6 files changed

+1051
-405
lines changed

.claude/CLAUDE-ARCHITECTURE.md

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
# CLAUDE-ARCHITECTURE.md
2+
3+
Architecture and EF Core integration patterns for Thinktecture.EntityFrameworkCore.
4+
5+
## Package Dependency Graph
6+
7+
```
8+
Relational Foundation layer (base abstractions)
9+
10+
└─ BulkOperations Provider-agnostic bulk operation interfaces
11+
12+
├─ SqlServer SQL Server implementation (SqlBulkCopy, MERGE)
13+
│ └─ SqlServer.Testing SQL Server test utilities
14+
15+
└─ Sqlite.Core SQLite foundation
16+
└─ Sqlite Full SQLite package
17+
└─ Sqlite.Testing SQLite test utilities
18+
19+
Testing Shared test infrastructure (used by *.Testing)
20+
```
21+
22+
Each layer adds provider-specific implementations to the abstractions defined above it. The `Relational` package has no provider dependency; `BulkOperations` depends only on `Relational`.
23+
24+
## Core Feature Areas
25+
26+
### 1. Bulk Operations
27+
- **Interfaces**: `IBulkInsertExecutor`, `IBulkUpdateExecutor`, `IBulkInsertOrUpdateExecutor`, `ITruncateTableExecutor`
28+
- **SQL Server**: Uses `SqlBulkCopy` for inserts, MERGE statements for updates/upserts
29+
- **SQLite**: Uses batched INSERT/UPDATE statements
30+
- **Property selection**: `IEntityPropertiesProvider` with `IncludingEntityPropertiesProvider` / `ExcludingEntityPropertiesProvider`
31+
- **Entry point**: Extension methods on `DbContext` in `BulkOperationsDbContextExtensions`
32+
33+
### 2. Temp Tables
34+
- **Creation**: `ITempTableCreator` creates temp tables; `ITempTableReference` manages cleanup via `IAsyncDisposable`
35+
- **Queryable wrapper**: `ITempTableQuery<T>` wraps `IQueryable<T>` with automatic cleanup on dispose
36+
- **Bulk population**: `ITempTableBulkInsertExecutor` for fast data loading
37+
- **Name management**: `TempTableSuffixLeasing` + `TempTableSuffixCache` prevent name conflicts in concurrent scenarios
38+
- **Entry point**: `dbContext.BulkInsertIntoTempTableAsync(entities)` and `dbContext.BulkInsertValuesIntoTempTableAsync(values)`
39+
40+
### 3. Window Functions
41+
- **Fluent API**: `EF.Functions.RowNumber()`, `EF.Functions.Average()`, etc. with `PartitionBy()` and `OrderBy()`
42+
- **Translation**: `RelationalDbFunctionsTranslator` translates to `WindowFunctionExpression` / `WindowFunctionPartitionByExpression`
43+
- **Both providers**: Implemented for SQL Server and SQLite via query translators
44+
45+
### 4. LEFT JOIN
46+
- **Extension methods**: `source.LeftJoin(inner, outerKey, innerKey, resultSelector)` on `IQueryable<T>`
47+
- **Result type**: `LeftJoinResult<TOuter, TInner>` with nullable inner entity
48+
- **Translation**: Custom expression visitors convert to SQL LEFT JOIN
49+
50+
### 5. Table Hints (SQL Server only)
51+
- **API**: `query.WithTableHints(SqlServerTableHint.NoLock)`
52+
- **Enum values**: `NoLock`, `ReadPast`, `UpdLock`, `HoldLock`, `RowLock`, `PageLock`, `TabLock`, etc.
53+
54+
### 6. Nested Transactions
55+
- **Manager**: `NestedRelationalTransactionManager` replaces EF Core's default transaction manager
56+
- **Root transactions**: `RootNestedDbContextTransaction` wraps actual DB transaction
57+
- **Child transactions**: `ChildNestedDbContextTransaction` are logical (no actual nested SQL transactions)
58+
59+
### 7. Collection Parameters
60+
- **Scalar**: `ScalarCollectionParameter<T>` for passing value lists to queries
61+
- **JSON (SQL Server)**: `JsonCollectionParameter` serializes collections as JSON
62+
- **Factory**: `ICollectionParameterFactory` creates provider-specific parameters
63+
64+
### 8. Tenant Database Support
65+
- **Interface**: `ITenantDatabaseProvider` provides per-tenant database names
66+
- **Query integration**: Injects tenant info into query context to prevent cache collisions
67+
68+
## EF Core Extension Architecture
69+
70+
### DbContextOptionsExtension Pattern
71+
72+
This is the primary integration point with EF Core. The library uses a **three-tier extension hierarchy**:
73+
74+
```
75+
DbContextOptionsExtensionBase (shared utilities)
76+
├─ RelationalDbContextOptionsExtension (provider-agnostic features)
77+
├─ SqlServerDbContextOptionsExtension (SQL Server features)
78+
└─ SqliteDbContextOptionsExtension (SQLite features)
79+
```
80+
81+
Each extension:
82+
1. Implements `IDbContextOptionsExtension`
83+
2. Has **boolean feature flags** (e.g., `AddWindowFunctionsSupport`, `AddBulkOperationSupport`)
84+
3. Registers services in `ApplyServices(IServiceCollection services)`
85+
4. Has an inner `DbContextOptionsExtensionInfo` class for service provider caching
86+
87+
**Feature flag cascading** - dependent features auto-enable prerequisites:
88+
```csharp
89+
public bool AddCustomQueryableMethodTranslatingExpressionVisitorFactory
90+
{
91+
get => _addCustomQueryableMethodTranslatingExpressionVisitorFactory
92+
|| AddBulkOperationSupport
93+
|| AddWindowFunctionsSupport
94+
|| AddTableHintSupport;
95+
}
96+
```
97+
98+
### User-Facing Registration API
99+
100+
Users enable features via `DbContextOptionsBuilder` extension methods:
101+
```csharp
102+
services.AddDbContext<MyContext>(options =>
103+
options.UseSqlServer(connectionString)
104+
.AddBulkOperationSupport()
105+
.AddWindowFunctionsSupport()
106+
.AddTableHintSupport()
107+
.AddNestedTransactionsSupport()
108+
.AddSchemaRespectingComponents()
109+
.AddTenantDatabaseSupport<MyTenantProvider>());
110+
```
111+
112+
These extension methods add or update the appropriate `DbContextOptionsExtension` on the options builder.
113+
114+
### Service Registration in ApplyServices
115+
116+
When EF Core builds the service provider, it calls `ApplyServices()` on each extension. Services are registered conditionally based on feature flags:
117+
118+
```csharp
119+
public override void ApplyServices(IServiceCollection services)
120+
{
121+
if (AddBulkOperationSupport)
122+
{
123+
services.Add<IConventionSetPlugin, BulkOperationConventionSetPlugin>(GetLifetime<IConventionSetPlugin>());
124+
services.TryAddScoped<ITempTableCreator, SqlServerTempTableCreator>();
125+
services.TryAddScoped<SqlServerBulkOperationExecutor>();
126+
// Register executor as multiple interfaces (single implementation)
127+
services.TryAddScoped<IBulkInsertExecutor>(p => p.GetRequiredService<SqlServerBulkOperationExecutor>());
128+
services.TryAddScoped<IBulkUpdateExecutor>(p => p.GetRequiredService<SqlServerBulkOperationExecutor>());
129+
// ...
130+
}
131+
132+
if (AddWindowFunctionsSupport)
133+
services.Add<IMethodCallTranslatorPlugin, RelationalMethodCallTranslatorPlugin>(...);
134+
}
135+
```
136+
137+
### Component Decorator Pattern
138+
139+
The library uses a **decorator pattern** to non-destructively wrap EF Core's internal services. This is the key mechanism in `RelationalDbContextComponentDecorator`:
140+
141+
```csharp
142+
// What it does:
143+
// 1. Finds EF Core's existing registration for TService
144+
// 2. Re-registers the original implementation under its own type
145+
// 3. Replaces the TService registration with a decorator that wraps the original
146+
147+
ComponentDecorator.RegisterDecorator<IModelCustomizer>(
148+
services, typeof(DefaultSchemaModelCustomizer<>));
149+
150+
// Result: EF Core resolves IModelCustomizer → DefaultSchemaModelCustomizer<OriginalCustomizer>
151+
// DefaultSchemaModelCustomizer<T> receives the original customizer via DI
152+
```
153+
154+
**Used for:**
155+
- `IModelCustomizer``DefaultSchemaModelCustomizer<T>` (adds default schema)
156+
- `IModelCacheKeyFactory``DefaultSchemaRespectingModelCacheKeyFactory<T>` (includes schema in cache key)
157+
- `IMigrationsAssembly``DefaultSchemaRespectingMigrationAssembly<T>` (applies schema to migrations)
158+
- `IQueryContextFactory``ThinktectureRelationalQueryContextFactory<T>` (adds tenant params)
159+
160+
### Service Replacement with Validation
161+
162+
For direct service replacement (not decoration), the library validates that the existing registration is what's expected:
163+
164+
```csharp
165+
protected void AddWithCheck<TService, TImplementation, TExpectedImplementation>(IServiceCollection services)
166+
{
167+
// Verifies the current registration is TExpectedImplementation before replacing with TImplementation
168+
// Throws InvalidOperationException if unexpected service is registered
169+
}
170+
```
171+
172+
### Service Lifetime Discovery
173+
174+
Custom services must match EF Core's expected lifetime for each service type:
175+
176+
```csharp
177+
protected ServiceLifetime GetLifetime<TService>()
178+
{
179+
// Looks up TService in EF Core's RelationalServices/CoreServices registries
180+
// Returns Singleton, Scoped, or Transient as defined by EF Core
181+
}
182+
```
183+
184+
### Singleton Options Bridge
185+
186+
Configuration from `IDbContextOptionsExtension` (scoped) is bridged to singleton services via `ISingletonOptions`:
187+
188+
```csharp
189+
public class RelationalDbContextOptionsExtensionOptions : ISingletonOptions
190+
{
191+
public bool WindowFunctionsSupportEnabled { get; private set; }
192+
193+
public void Initialize(IDbContextOptions options)
194+
{
195+
var extension = options.FindExtension<RelationalDbContextOptionsExtension>();
196+
WindowFunctionsSupportEnabled = extension.AddWindowFunctionsSupport;
197+
}
198+
}
199+
```
200+
201+
## Query Translation Pipeline
202+
203+
```
204+
LINQ Expression
205+
└─ IQueryableMethodTranslatingExpressionVisitorFactory
206+
└─ ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor
207+
└─ Handles custom methods (AsSubQuery, LeftJoin)
208+
209+
Method Calls (EF.Functions.*)
210+
└─ IMethodCallTranslatorPlugin
211+
└─ RelationalMethodCallTranslatorPlugin
212+
└─ RelationalDbFunctionsTranslator
213+
└─ Produces: WindowFunctionExpression, RowNumberExpression, etc.
214+
215+
SQL Generation
216+
└─ IQuerySqlGeneratorFactory
217+
└─ ThinktectureSqlServerQuerySqlGeneratorFactory
218+
└─ Custom QuerySqlGenerator
219+
└─ Handles window functions, table hints, tenant databases
220+
```
221+
222+
## Convention Set Plugin
223+
224+
Model building conventions are extended via `IConventionSetPlugin`:
225+
226+
```csharp
227+
public class BulkOperationConventionSetPlugin : IConventionSetPlugin
228+
{
229+
public ConventionSet ModifyConventions(ConventionSet conventionSet)
230+
{
231+
if (_options.ConfigureTempTablesForPrimitiveTypes)
232+
conventionSet.ModelInitializedConventions.Add(TempTableConvention.Instance);
233+
return conventionSet;
234+
}
235+
}
236+
```
237+
238+
## Migration Customization
239+
240+
- `ThinktectureSqlServerMigrationsSqlGenerator` extends `SqlServerMigrationsSqlGenerator`
241+
- Overrides `Generate(CreateTableOperation)`, `Generate(DropTableOperation)` for conditional SQL (IF EXISTS/IF NOT EXISTS)
242+
- `DefaultSchemaRespectingMigrationAssembly<T>` injects `IDbDefaultSchema` into migration instances at runtime
243+
244+
## How to Add a New Feature
245+
246+
1. **Add boolean flag** to the appropriate `DbContextOptionsExtension` class
247+
2. **Add user-facing extension method** on `DbContextOptionsBuilder` (in `Extensions/` directory)
248+
3. **Implement ApplyServices() logic** - register services conditionally based on flag
249+
4. **Update ExtensionInfo** - add flag to `GetServiceProviderHashCode()`, `ShouldUseSameServiceProvider()`, and `PopulateDebugInfo()`
250+
5. **Implement the feature** using EF Core's extension points:
251+
- Query translation → `IMethodCallTranslatorPlugin` or `IQueryableMethodTranslatingExpressionVisitorFactory`
252+
- Model building → `IConventionSetPlugin`
253+
- SQL generation → `IQuerySqlGeneratorFactory`
254+
- Service wrapping → Component Decorator pattern
255+
6. **Add tests** in the appropriate test project with SQL verification
256+
7. **Use `GetLifetime<T>()`** to match EF Core's expected service lifetime
257+
258+
## Entity Data Reader (ADO.NET Bridge)
259+
260+
`IEntityDataReader` / `EntityDataReader` expose entity collections as `IDataReader` for use with `SqlBulkCopy`:
261+
- `IEntityDataReaderFactory` creates readers from entity collections
262+
- `PropertyWithNavigations` handles complex property paths including navigations
263+
- Supports regular properties, shadow properties, and owned entity navigation properties

0 commit comments

Comments
 (0)