@@ -42,18 +42,33 @@ public override string BuildMoveDataSql<T>(
4242 }
4343
4444 IEnumerable < string > matchColumns ;
45+ IReadOnlyList < string > matchColumnsList ;
4546 if ( onConflictTyped . Match != null )
4647 {
47- matchColumns = GetColumns ( target , onConflictTyped . Match ) ;
48+ matchColumnsList = GetColumns ( target , onConflictTyped . Match ) . ToList ( ) ;
4849 }
4950 else if ( target . PrimaryKey . Length > 0 )
5051 {
51- matchColumns = target . PrimaryKey . Select ( x => x . QuotedColumName ) ;
52+ matchColumnsList = target . PrimaryKey . Select ( x => x . QuotedColumName ) . ToList ( ) ;
5253 }
5354 else
5455 {
5556 throw new InvalidOperationException ( "Table has no primary key that can be used for conflict detection." ) ;
5657 }
58+ matchColumns = matchColumnsList ;
59+
60+ // Validate that all match columns are available in the source subquery
61+ var insertedColumnNames = insertedColumns . Select ( c => c . QuotedColumName ) . ToHashSet ( ) ;
62+ var missingMatchColumns = matchColumnsList . Where ( c => ! insertedColumnNames . Contains ( c ) ) . ToList ( ) ;
63+ if ( missingMatchColumns . Count != 0 )
64+ {
65+ throw new InvalidOperationException (
66+ $ "Oracle MERGE requires match columns to be present in the source data. " +
67+ $ "The following match columns are not available: { string . Join ( ", " , missingMatchColumns ) } . " +
68+ $ "This can happen when using auto-generated primary key columns for conflict detection. " +
69+ $ "Use the 'Match' option to specify non-generated columns for conflict detection, " +
70+ $ "or set 'CopyGeneratedColumns = true' if the generated column values are provided.") ;
71+ }
5772
5873 // Oracle MERGE syntax does NOT use AS for table aliases
5974 q . AppendLine ( $ "MERGE INTO { target . QuotedTableName } { PseudoTableInserted } ") ;
@@ -80,8 +95,6 @@ public override string BuildMoveDataSql<T>(
8095
8196 if ( onConflictTyped . Update != null )
8297 {
83- var columns = target . GetColumns ( false ) ;
84-
8598 q . Append ( "WHEN MATCHED " ) ;
8699
87100 if ( onConflictTyped . RawWhere != null || onConflictTyped . Where != null )
@@ -96,7 +109,17 @@ public override string BuildMoveDataSql<T>(
96109 }
97110
98111 q . AppendLine ( "THEN UPDATE SET " ) ;
99- q . AppendJoin ( ", " , GetUpdates ( context , target , columns , onConflictTyped . Update ) ) ;
112+ // Oracle MERGE: columns in ON clause cannot be updated, so exclude match columns
113+ // Use insertedColumns instead of all columns because the USING subquery only contains insertedColumns
114+ var matchColumnSet = matchColumnsList . ToHashSet ( ) ;
115+ var updateableColumns = insertedColumns . Where ( c => ! matchColumnSet . Contains ( c . QuotedColumName ) ) . ToList ( ) ;
116+ if ( updateableColumns . Count == 0 )
117+ {
118+ throw new InvalidOperationException (
119+ "Oracle MERGE cannot update any columns because all available columns are used in the ON clause for conflict detection. " +
120+ "Specify different columns in the 'Match' option or use specific columns in the 'Update' expression." ) ;
121+ }
122+ q . AppendJoin ( ", " , GetUpdates ( context , target , updateableColumns , onConflictTyped . Update ) ) ;
100123 q . AppendLine ( ) ;
101124 }
102125 }
0 commit comments