Skip to main content

SqlMergeBuilder

SqlMergeBuilder<TTarget, TSource> builds SQL Server MERGE statements. For PostgreSQL upsert, use SqlInsertBuilder.OnConflictDoUpdate instead.

Note: Build() throws InvalidOperationException on dialects other than SqlServerDialect. Only SQL Server supports the MERGE syntax as implemented here.

var result = new SqlMergeBuilder<User, UserDto>(new SqlServerDialect())
.Into("Users")
.Using("UserUpdates", "src")
.On(t => t.Id, s => s.Id)
.WhenMatchedUpdate(m => m
.MatchedSetColumn(t => t.Name, s => s.Name)
.MatchedSetColumn(t => t.Email, s => s.Email))
.WhenNotMatchedInsert(i => i
.NotMatchedInsertColumn(t => t.Id, s => s.Id)
.NotMatchedInsertColumn(t => t.Name, s => s.Name))
.Build();

Advanced Example

var merge = new SqlMergeBuilder<User>()
.Target("Users")
.Source("StagingUsers")
.On("Users.Id = StagingUsers.Id")
.WhenMatchedUpdate()
.Set("Users.Name", "StagingUsers.Name")
.WhenNotMatchedInsert()
.Value("Id", "StagingUsers.Id")
.Value("Name", "StagingUsers.Name");

Into (Merge)

Signature

SqlMergeBuilder<TTarget, TSource> Into(string tableName, string? schema = null)

Description Sets the target table for the MERGE.


Using

Signature

SqlMergeBuilder<TTarget, TSource> Using(string sourceTable, string alias = "src")

Description Sets the source table name and alias used in the USING clause.


On

Signature

SqlMergeBuilder<TTarget, TSource> On(
Expression<Func<TTarget, object>> targetKey,
Expression<Func<TSource, object>> sourceKey)

Description Adds a join condition: target.col = source.col. Can be called multiple times for composite keys.

Example

builder.On(t => t.TenantId, s => s.TenantId).On(t => t.ExternalId, s => s.ExternalId);
// ON target.[TenantId] = src.[TenantId] AND target.[ExternalId] = src.[ExternalId]

WhenMatchedUpdate

Signature

SqlMergeBuilder<TTarget, TSource> WhenMatchedUpdate(
Action<SqlMergeBuilder<TTarget, TSource>> configure)

Description Configures the WHEN MATCHED THEN UPDATE SET ... clause. The configure action calls MatchedSetColumn and/or MatchedSetValue.


MatchedSetColumn

Signature

SqlMergeBuilder<TTarget, TSource> MatchedSetColumn(
Expression<Func<TTarget, object>> targetCol,
Expression<Func<TSource, object>> sourceCol)

Description In WHEN MATCHED UPDATE: sets target.targetCol = source.sourceCol (column-to-column copy, no parameters).


MatchedSetValue

Signature

SqlMergeBuilder<TTarget, TSource> MatchedSetValue<TValue>(
Expression<Func<TTarget, object>> targetCol, TValue value)

Description In WHEN MATCHED UPDATE: sets target.targetCol = @pmN (parameterized value).


WhenNotMatchedInsert

Signature

SqlMergeBuilder<TTarget, TSource> WhenNotMatchedInsert(
Action<SqlMergeBuilder<TTarget, TSource>> configure)

Description Configures the WHEN NOT MATCHED BY TARGET THEN INSERT (...) clause. The configure action calls NotMatchedInsertColumn and/or NotMatchedInsertValue.


NotMatchedInsertColumn

Signature

SqlMergeBuilder<TTarget, TSource> NotMatchedInsertColumn(
Expression<Func<TTarget, object>> targetCol,
Expression<Func<TSource, object>> sourceCol)

Description In WHEN NOT MATCHED INSERT: maps targetCol = source.sourceCol.


NotMatchedInsertValue

Signature

SqlMergeBuilder<TTarget, TSource> NotMatchedInsertValue<TValue>(
Expression<Func<TTarget, object>> targetCol, TValue value)

Description In WHEN NOT MATCHED INSERT: maps targetCol = @pmN (parameterized value).


WhenNotMatchedBySourceDelete

Signature

SqlMergeBuilder<TTarget, TSource> WhenNotMatchedBySourceDelete()

Description Adds WHEN NOT MATCHED BY SOURCE THEN DELETE. Removes target rows that have no corresponding source row.


Tag (Merge)

Signature

SqlMergeBuilder<TTarget, TSource> Tag(string description)

Description Adds a SQL comment header and enables console tag for traceability.


Build (Merge)

Signature

SqlQueryResult Build()

Description Builds and returns the parameterized MERGE statement. Throws if Into(), Using(), On(), and at least one WHEN clause have not been called.

Full example

var result = new SqlMergeBuilder<User, UserDto>(new SqlServerDialect())
.Into("Users", schema: "dbo")
.Using("Staging", "src")
.On(t => t.Email, s => s.Email)
.WhenMatchedUpdate(m => m
.MatchedSetColumn(t => t.Name, s => s.Name)
.MatchedSetColumn(t => t.UpdatedAt, s => s.UpdatedAt))
.WhenNotMatchedInsert(i => i
.NotMatchedInsertColumn(t => t.Email, s => s.Email)
.NotMatchedInsertColumn(t => t.Name, s => s.Name)
.NotMatchedInsertValue(t => t.CreatedAt, DateTime.UtcNow))
.WhenNotMatchedBySourceDelete()
.Tag("Sync users from staging")
.Build();

// → -- Sync users from staging
// MERGE INTO [dbo].[Users] AS target
// USING [Staging] AS src ON target.[Email] = src.[Email]
// WHEN MATCHED THEN
// UPDATE SET target.[Name] = src.[Name], target.[UpdatedAt] = src.[UpdatedAt]
// WHEN NOT MATCHED BY TARGET THEN
// INSERT ([Email], [Name], [CreatedAt]) VALUES (src.[Email], src.[Name], @pm0)
// WHEN NOT MATCHED BY SOURCE THEN DELETE;