diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/ForeachExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/ForeachExecutor.cs index e6ab5e49a9..e88cd25ef6 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/ForeachExecutor.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/ForeachExecutor.cs @@ -49,7 +49,7 @@ public ForeachExecutor(Foreach model, WorkflowFormulaState state) EvaluationResult expressionResult = this.Evaluator.GetValue(this.Model.Items); if (expressionResult.Value is TableDataValue tableValue) { - this._values = [.. tableValue.Values.Select(value => value.Properties.Values.First().ToFormula())]; + this._values = [.. tableValue.Values.Select(value => value.ToFormula())]; } else { diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/ForeachExecutorTest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/ForeachExecutorTest.cs index 63d6e15bb8..0215aed659 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/ForeachExecutorTest.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/ForeachExecutorTest.cs @@ -291,6 +291,38 @@ public async Task ForeachRestoreWithNoSavedStateAsync() Assert.False(executor.HasValue); } + /// + /// Foreach over a table with multi-field records must preserve all fields in the loop value variable. + /// Regression test for GH-6183. + /// + [Fact] + public async Task ForeachPreservesMultiFieldRecordsAsync() + { + // Arrange + this.SetVariableState("CurrentValue"); + TableDataValue tableValue = DataValue.TableFromRecords( + DataValue.RecordFromFields( + new KeyValuePair("name", new StringDataValue("Alice")), + new KeyValuePair("role", new StringDataValue("Engineer"))), + DataValue.RecordFromFields( + new KeyValuePair("name", new StringDataValue("Bob")), + new KeyValuePair("role", new StringDataValue("Designer")))); + + Foreach model = this.CreateModel( + displayName: nameof(ForeachPreservesMultiFieldRecordsAsync), + items: ValueExpression.Literal(tableValue), + valueName: "CurrentValue", + indexName: null); + + ForeachExecutor action = new(model, this.State); + + // Act — execute the initialisation then take the first iteration. + await this.ExecuteAsync(action, ForeachExecutor.Steps.Next(action.Id), action.TakeNextAsync); + + // Assert — the value must be present (all fields preserved, not collapsed to first field). + Assert.True(action.HasValue); + } + /// /// Checkpoint/restore around a foreach over an empty source must roundtrip cleanly /// (zero-length PortableValue[] snapshot).