@@ -176,6 +176,92 @@ public void ExecuteDelete_BulkDelete_EmitsMultiTableSyntax()
176176 CollectionAssert . AreEqual ( new [ ] { "alice" , "carol" } , names ) ;
177177 }
178178
179+ [ TestMethod ]
180+ public void ExecuteUpdate_MultipleProperties_BothApplied ( )
181+ {
182+ // Chained SetProperty calls compile to a single UPDATE with multiple SET clauses.
183+ using var context = SeededContext ( ) ;
184+ var rows = context . People . Where ( p => p . Id == 1 )
185+ . ExecuteUpdate ( setters => setters
186+ . SetProperty ( p => p . Name , "renamed" )
187+ . SetProperty ( p => p . Code , "X" ) ) ;
188+ Assert . AreEqual ( 1 , rows ) ;
189+
190+ using var fresh = new TestDbContext ( context . Simulation ) ;
191+ var reloaded = fresh . People . Single ( p => p . Id == 1 ) ;
192+ Assert . AreEqual ( "renamed" , reloaded . Name ) ;
193+ Assert . AreEqual ( "X" , reloaded . Code ) ;
194+ }
195+
196+ [ TestMethod ]
197+ public void ExecuteUpdate_TopNSubqueryFilter_OrderByTake ( )
198+ {
199+ // .OrderBy(...).Take(n).ExecuteUpdate(...) emits the
200+ // UPDATE [c] ... WHERE [c].[Id] IN (SELECT TOP(@p) [c0].[Id] FROM ... ORDER BY ...)
201+ // shape — different from the flat WHERE form. Confirms the subquery
202+ // filter through bulk-DML works against the TOP-N pattern.
203+ using var context = SeededContext ( ) ;
204+ var rows = context . People . OrderBy ( p => p . Id ) . Take ( 2 )
205+ . ExecuteUpdate ( setters => setters . SetProperty ( p => p . Code , "TOP" ) ) ;
206+ Assert . AreEqual ( 2 , rows ) ;
207+
208+ using var fresh = new TestDbContext ( context . Simulation ) ;
209+ var codes = fresh . People . OrderBy ( p => p . Id ) . Select ( p => p . Code ) . ToArray ( ) ;
210+ CollectionAssert . AreEqual ( new [ ] { "TOP" , "TOP" , "C" , "D" } , codes ) ;
211+ }
212+
213+ [ TestMethod ]
214+ public void ExecuteDelete_TopNSubqueryFilter_OrderByTake ( )
215+ {
216+ // .OrderBy(...).Take(n).ExecuteDelete() emits the
217+ // DELETE [c] ... WHERE [c].[Id] IN (SELECT TOP(@p) [c0].[Id] FROM ... ORDER BY ...) shape.
218+ using var context = SeededContext ( ) ;
219+ var rows = context . People . OrderBy ( p => p . Id ) . Take ( 2 ) . ExecuteDelete ( ) ;
220+ Assert . AreEqual ( 2 , rows ) ;
221+
222+ using var fresh = new TestDbContext ( context . Simulation ) ;
223+ var ids = fresh . People . OrderBy ( p => p . Id ) . Select ( p => p . Id ) . ToArray ( ) ;
224+ CollectionAssert . AreEqual ( new [ ] { 3 , 4 } , ids ) ;
225+ }
226+
227+ [ TestMethod ]
228+ public void ExecuteUpdate_ColumnDerivedArithmetic_AppliesPerRow ( )
229+ {
230+ // SetProperty(col, c => c.OtherCol + literal) compiles to a setter
231+ // that references the row's other columns — a different shape from
232+ // Name.ToUpper() because the right-hand expression is a binary op.
233+ var simulation = new Simulation ( ) ;
234+ _ = simulation . ExecuteNonQuery ( "create table Scores (Id int primary key, Value int)" ) ;
235+ using var context = new ScoreContext ( simulation ) ;
236+ context . Scores . AddRange (
237+ new Score { Id = 1 , Value = 10 } ,
238+ new Score { Id = 2 , Value = 20 } ) ;
239+ _ = context . SaveChanges ( ) ;
240+
241+ var rows = context . Scores . ExecuteUpdate ( s => s . SetProperty ( x => x . Value , x => ( x . Value * 3 ) + 1 ) ) ;
242+ Assert . AreEqual ( 2 , rows ) ;
243+
244+ using var fresh = new ScoreContext ( simulation ) ;
245+ var values = fresh . Scores . OrderBy ( x => x . Id ) . Select ( x => x . Value ) . ToArray ( ) ;
246+ CollectionAssert . AreEqual ( new [ ] { 31 , 61 } , values ) ;
247+ }
248+
249+ [ TestMethod ]
250+ public void ExecuteUpdate_AnySubqueryFilter_ScopesAffectedRows ( )
251+ {
252+ // WHERE EXISTS / WHERE col IN (SELECT ...) reach bulk DML through
253+ // EF Core's standard subquery translation. This case translates Any
254+ // into EXISTS — confirms ExecuteUpdate's WHERE accepts subquery shapes.
255+ using var context = SeededContext ( ) ;
256+ var rows = context . People . Where ( p => context . People . Any ( o => o . Id == p . Id - 1 ) )
257+ . ExecuteUpdate ( setters => setters . SetProperty ( p => p . Code , "NEXT" ) ) ;
258+ Assert . AreEqual ( 3 , rows ) ;
259+
260+ using var fresh = new TestDbContext ( context . Simulation ) ;
261+ var codes = fresh . People . OrderBy ( p => p . Id ) . Select ( p => p . Code ) . ToArray ( ) ;
262+ CollectionAssert . AreEqual ( new [ ] { "A" , "NEXT" , "NEXT" , "NEXT" } , codes ) ;
263+ }
264+
179265 [ TestMethod ]
180266 public void SaveChanges_TimestampEntity_ReadsBackAutoBumpedRowVersion ( )
181267 {
@@ -199,6 +285,25 @@ public void SaveChanges_TimestampEntity_ReadsBackAutoBumpedRowVersion()
199285 }
200286}
201287
288+ internal class Score
289+ {
290+ public int Id { get ; set ; }
291+
292+ public int Value { get ; set ; }
293+ }
294+
295+ internal class ScoreContext ( Simulation simulation ) : DbContext
296+ {
297+ public Simulation Simulation { get ; set ; } = simulation ;
298+
299+ protected override void OnConfiguring ( DbContextOptionsBuilder optionsBuilder )
300+ {
301+ _ = optionsBuilder . UseSqlServer ( this . Simulation . CreateDbConnection ( ) ) ;
302+ }
303+
304+ public DbSet < Score > Scores => Set < Score > ( ) ;
305+ }
306+
202307internal class TimestampedItem
203308{
204309 public int Id { get ; set ; }
0 commit comments