You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .claude/CLAUDE.md
+4-2Lines changed: 4 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -253,8 +253,10 @@ private NpgsqlBulkOperationExecutor SUT => field ??= ActDbContext.GetService<Npg
253
253
- PostgreSQL resolves `NpgsqlDbType` per column via `INpgsqlTypeMapping` for correct type handling (e.g., `jsonb`/`json` from `string`)
254
254
- Table/schema override via options; property selection via `IEntityPropertiesProvider`
255
255
- PostgreSQL temp table collation: strings with `.` split into schema+name by default; set `SplitCollationComponents = false` to escape as single identifier
256
-
- **Query-based bulk update** (all providers): `DbSet<TTarget>.BulkUpdateAsync(sourceQuery, targetKey, sourceKey, setBuilder, filter?, options?)`. SQL Server generates `UPDATE t SET ... FROM target INNER JOIN (sourceQuerySql) AS s ON ... [WHERE filterCondition]`; PostgreSQL/SQLite generate `UPDATE target AS t SET ... FROM (sourceQuerySql) AS s WHERE ... [AND filterCondition]`. Uses `SetPropertyBuilder<TTarget, TSource>` for fluent property assignment. Key selectors support single and composite keys (anonymous types). Source SQL and parameters extracted via `CreateDbCommand()`. Optional `filter` (`Expression<Func<TTarget, TSource, bool>>`) restricts which rows are updated; filter can reference both target and source properties (e.g., `(e, f) => e.Count < f.Count`); filter is translated via `ValueExpressionTranslator` (same instance as SET values), with unified `@__ev_N` parameters. Shared expression analysis logic in `BulkUpdateFromQueryHelper`. Table/schema override via `SqlServerBulkUpdateFromQueryOptions`, `NpgsqlBulkUpdateFromQueryOptions`, or `SqliteBulkUpdateFromQueryOptions`. Value expressions support constants and captured variables (e.g., `.Set(e => e.Count, (e, f) => 42)` or `.Set(e => e.Name, (e, f) => myVar)`), complex arithmetic expressions referencing target and/or source properties (e.g., `.Set(e => e.Count, (e, f) => e.Count + f.Count * 2)` or `.Set(e => e.Count, (e, f) => f.Count + 10)`), in addition to simple property access and `EF.Property` calls; constant values are emitted as SQL parameters (`@__bv_N`); complex expressions are translated to SQL via `ValueExpressionTranslator` with expression-value parameters (`@__ev_N`).
257
-
- **Query-based bulk insert** (all providers): `DbSet<TTarget>.BulkInsertAsync(sourceQuery, mapBuilder)`. Generates `INSERT INTO target (cols) SELECT s.cols FROM (sourceQuerySql) AS s` — identical structure across all three providers (only identifier quoting differs). Uses `InsertPropertyBuilder<TTarget, TSource>` for fluent column mapping via `Map(target => target.Prop, source => source.Prop)` with single-parameter value selector. No key selectors needed (insert, not join). Table/schema override via `SqlServerBulkInsertFromQueryOptions`, `NpgsqlBulkInsertFromQueryOptions`, or `SqliteBulkInsertFromQueryOptions`. Value selectors support constants and captured variables (e.g., `.Map(e => e.Count, f => 42)` or `.Map(e => e.Name, f => myVar)`), complex arithmetic expressions referencing source properties (e.g., `.Map(e => e.Count, f => f.Count * 2 + 1)`), in addition to simple property access and `EF.Property` calls; constant values are emitted as SQL parameters (`@__bv_N`); complex expressions are translated to SQL via `ValueExpressionTranslator` with expression-value parameters (`@__ev_N`).
256
+
- **Query-based bulk update** (all providers): `DbSet<TTarget>.BulkUpdateAsync(sourceQuery, targetKey, sourceKey, setBuilder, filter?, options?)`. SQL Server generates `UPDATE t SET ... FROM target INNER JOIN (sourceQuerySql) AS s ON ... [WHERE filterCondition]`; PostgreSQL/SQLite generate `UPDATE target AS t SET ... FROM (sourceQuerySql) AS s WHERE ... [AND filterCondition]`. Uses `SetPropertyBuilder<TTarget, TSource>` for fluent property assignment. Key selectors support single and composite keys (anonymous types). Source SQL and parameters extracted via `CreateDbCommand()`. Optional `filter` (`Expression<Func<TTarget, TSource, bool>>`) restricts which rows are updated; filter can reference both target and source properties (e.g., `(e, f) => e.Count < f.Count`); filter is translated via `ValueExpressionTranslator` (same instance as SET values), with unified `@__ev_N` parameters. Shared expression analysis logic in `BulkUpdateFromQueryHelper`. Table/schema override via `SqlServerBulkUpdateFromQueryOptions`, `NpgsqlBulkUpdateFromQueryOptions`, or `SqliteBulkUpdateFromQueryOptions`. Value expressions support constants, captured variables, and static members (e.g., `.Set(e => e.Count, (e, f) => 42)`, `.Set(e => e.Name, (e, f) => myVar)`, or `.Set(e => e.Status, (e, f) => Status.Published)`), complex arithmetic expressions referencing target and/or source properties (e.g., `.Set(e => e.Count, (e, f) => e.Count + f.Count * 2)` or `.Set(e => e.Count, (e, f) => f.Count + 10)`), in addition to simple property access and `EF.Property` calls; constant values are emitted as SQL parameters (`@__bv_N`); complex expressions are translated to SQL via `ValueExpressionTranslator` with expression-value parameters (`@__ev_N`).
257
+
- **Query-based bulk insert** (all providers): `DbSet<TTarget>.BulkInsertAsync(sourceQuery, mapBuilder)`. Generates `INSERT INTO target (cols) SELECT s.cols FROM (sourceQuerySql) AS s` — identical structure across all three providers (only identifier quoting differs). Uses `InsertPropertyBuilder<TTarget, TSource>` for fluent column mapping via `Map(target => target.Prop, source => source.Prop)` with single-parameter value selector. No key selectors needed (insert, not join). Table/schema override via `SqlServerBulkInsertFromQueryOptions`, `NpgsqlBulkInsertFromQueryOptions`, or `SqliteBulkInsertFromQueryOptions`. Value selectors support constants, captured variables, and static members (e.g., `.Map(e => e.Count, f => 42)` or `.Map(e => e.Name, f => myVar)`), complex arithmetic expressions referencing source properties (e.g., `.Map(e => e.Count, f => f.Count * 2 + 1)`), in addition to simple property access and `EF.Property` calls; constant values are emitted as SQL parameters (`@__bv_N`); complex expressions are translated to SQL via `ValueExpressionTranslator` with expression-value parameters (`@__ev_N`).
258
+
259
+
-**Value expression evaluation** (query-based bulk update/insert): `ClosureFlatteningVisitor` in `EfCoreValueExpressionTranslator` evaluates closure fields **and static members** (e.g. smart-enum values like `Status.Published`) to constants, guarded by `IEvaluatableExpressionFilter` so server-translatable members (e.g. `DateTime.Now`) stay untouched. Resulting `ConstantExpression`s bypass EF's SQL translator (which fails for types without a built-in type mapping) and are emitted directly with the target column's type mapping, so value-converter-typed values work; in the insert path, the converter is applied via `ConvertToProvider` before the value is bound as a raw parameter.
258
260
259
261
-**Source entity type resolution** (query-based bulk update/insert): Source entity type is resolved via `FindEntityType(typeof(TSource))` first (regular CLR-type lookup), then falls back to `FindEntityType(EntityNameProvider.GetTempTableName(typeof(TSource)))` for temp-table-only entities registered via `ConfigureTempTableEntity<T>()`. Without the fallback, column name resolution uses property names instead of mapped column names, causing SQL errors.
Copy file name to clipboardExpand all lines: src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/Internal/EfCoreValueExpressionTranslator.cs
+53-28Lines changed: 53 additions & 28 deletions
Original file line number
Diff line number
Diff line change
@@ -94,17 +94,9 @@ public EfCoreValueExpressionTranslator(
94
94
thrownewArgumentException($"Update value expression must have exactly 2 parameters. Expression: {valueExpr}");
Copy file name to clipboardExpand all lines: tests/Thinktecture.EntityFrameworkCore.PostgreSQL.Tests/EntityFrameworkCore/BulkOperations/NpgsqlBulkOperationExecutorTests/BulkUpdateFromQueryAsync.cs
+120Lines changed: 120 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -628,6 +628,126 @@ public async Task Should_update_with_captured_variable()
0 commit comments