Repository: fluentmigrator/fluentmigrator (2025-06-19T21:10:21Z)

Issue:
Support to add multiple rows from an IEnumerable<object>?
**Is your feature request related to a problem? Please describe.**
I often find myself writing code such as

```
Create.Table("FooType")
        .WithColumn("Id").AsInt64().NotNullable().PrimaryKey()
        .WithColumn("Name").AsString().NotNullable();

Insert.IntoTable("FooType").Row(new { Id = 1, Name = "TypeX" });
Insert.IntoTable("FooType").Row(new { Id = 2, Name = "TypeY" });
Insert.IntoTable("FooType").Row(new { Id = 3, Name = "TypeZ" });
```

alternatively, the data comes from some other API, and is some sort of array like collection:

```
foreach (var record in GetSomeRecordsFromSomewhere())
{
    Insert.IntoTable("FooType").Row(record);
}
```

**Describe the solution you'd like**
How about some sort of `Rows()` (note the plural in Method name) expression that takes an IEnumerable<object>, so that one can write:

```
Insert.IntoTable("FooType").Rows(
    [
        new { Id = 1, Name = "TypeX" },
        new { Id = 2, Name = "TypeY" },
        new { Id = 3, Name = "TypeZ" }
    ]);

Insert.IntoTable("FooType").Rows(GetSomeRecordsFromSomewhere());
```

**Additional context**
N/A

**Is this a feature you'd like to submit a PR for?**
Yes

I guess I could have a go at this, under the hood it would probably simply iterate over the collection and delegate to `Row()`.


Discussion:

Patch:
diff --git a/src/FluentMigrator.Abstractions/Builders/Insert/IInsertDataSyntax.cs b/src/FluentMigrator.Abstractions/Builders/Insert/IInsertDataSyntax.cs
index 3a7ad9da4..0322bb41d 100644
--- a/src/FluentMigrator.Abstractions/Builders/Insert/IInsertDataSyntax.cs
+++ b/src/FluentMigrator.Abstractions/Builders/Insert/IInsertDataSyntax.cs
@@ -30,18 +30,35 @@ public interface IInsertDataSyntax : IFluentSyntax
         /// <summary>
         /// Specify the data to insert
         /// </summary>
-        /// <param name="dataAsAnonymousType">An anonymous object that is used to insert data</param>
+        /// <param name="recordAsAnonymousType">An anonymous object that is used to insert data</param>
         /// <remarks>
         /// The properties are the column names and their values are the row values.
         /// </remarks>
         /// <returns>The next step</returns>
-        IInsertDataSyntax Row(object dataAsAnonymousType);
+        IInsertDataSyntax Row(object recordAsAnonymousType);
 
         /// <summary>
         /// Specify the data to insert
         /// </summary>
-        /// <param name="data">The dictionary containing column name/value combinations</param>
+        /// <param name="recordsAsAnonymousTypes">An array of anonymous objects that are used to insert data</param>
+        /// <remarks>
+        /// The properties are the column names and their values are the row values.
+        /// </remarks>
+        /// <returns>The next step</returns>
+        IInsertDataSyntax Rows(params object[] recordsAsAnonymousTypes);
+
+        /// <summary>
+        /// Specify the data to insert
+        /// </summary>
+        /// <param name="record">The dictionary containing column name/value combinations</param>
+        /// <returns>The next step</returns>
+        IInsertDataSyntax Row(IDictionary<string, object> record);
+
+        /// <summary>
+        /// Specify the data to insert
+        /// </summary>
+        /// <param name="records">An array of dictionaries each containing column name/value combinations</param>
         /// <returns>The next step</returns>
-        IInsertDataSyntax Row(IDictionary<string, object> data);
+        IInsertDataSyntax Rows(params IDictionary<string, object>[] records);
     }
 }
diff --git a/src/FluentMigrator/Builders/Insert/InsertDataExpressionBuilder.cs b/src/FluentMigrator/Builders/Insert/InsertDataExpressionBuilder.cs
index fb856a79f..9336d54be 100644
--- a/src/FluentMigrator/Builders/Insert/InsertDataExpressionBuilder.cs
+++ b/src/FluentMigrator/Builders/Insert/InsertDataExpressionBuilder.cs
@@ -18,6 +18,7 @@
 
 using System.Collections.Generic;
 using System.ComponentModel;
+using System.Linq;
 
 using FluentMigrator.Expressions;
 using FluentMigrator.Infrastructure;
@@ -45,21 +46,35 @@ public InsertDataExpressionBuilder(InsertDataExpression expression)
         public IDictionary<string, object> AdditionalFeatures => _expression.AdditionalFeatures;
 
         /// <inheritdoc />
-        public IInsertDataSyntax Row(object dataAsAnonymousType)
+        public IInsertDataSyntax Row(object recordAsAnonymousType)
         {
-            IDictionary<string, object> data = ExtractData(dataAsAnonymousType);
+            return Rows(recordAsAnonymousType);
+        }
 
-            return Row(data);
+        /// <inheritdoc />
+        public IInsertDataSyntax Rows(params object[] recordsAsAnonymousTypes)
+        {
+            var records = recordsAsAnonymousTypes.Select(ExtractData).ToArray();
+            return Rows(records);
         }
 
         /// <inheritdoc />
-        public IInsertDataSyntax Row(IDictionary<string, object> data)
+        public IInsertDataSyntax Row(IDictionary<string, object> record)
         {
-            var dataDefinition = new InsertionDataDefinition();
+            return Rows(record);
+        }
 
-            dataDefinition.AddRange(data);
+        /// <inheritdoc />
+        public IInsertDataSyntax Rows(params IDictionary<string, object>[] records)
+        {
+            var dataDefinitions = records.Select(record =>
+            {
+                var dataDefinition = new InsertionDataDefinition();
+                dataDefinition.AddRange(record);
+                return dataDefinition;
+            });
 
-            _expression.Rows.Add(dataDefinition);
+            _expression.Rows.AddRange(dataDefinitions);
 
             return this;
         }


Test patch:
diff --git a/test/FluentMigrator.Tests/Unit/Builders/Insert/InsertDataExpressionBuilderTests.cs b/test/FluentMigrator.Tests/Unit/Builders/Insert/InsertDataExpressionBuilderTests.cs
index d08cc78a3..3a17f6974 100644
--- a/test/FluentMigrator.Tests/Unit/Builders/Insert/InsertDataExpressionBuilderTests.cs
+++ b/test/FluentMigrator.Tests/Unit/Builders/Insert/InsertDataExpressionBuilderTests.cs
@@ -65,22 +65,83 @@ public void RowsGetSetWhenRowIsCalled()
         }
 
         [Test]
-        public void RowsGetPopulatedWhenRowWithDictionaryIsCalled()
+        public void RowsGetSetWhenRowsIsCalled()
         {
-            var values = new Dictionary<string, object>();
-            values["Data1"] = "Row1Data1";
-            values["Data2"] = "Row1Data2";
+            var expression = new InsertDataExpression();
+
+            var builder = new InsertDataExpressionBuilder(expression);
+            builder
+                .Rows(
+                    new { Data1 = "Row1Data1", Data2 = "Row1Data2" },
+                    new { Data1 = "Row2Data1", Data2 = "Row2Data2" }
+                );
+
+            expression.Rows.Count.ShouldBe(2);
+
+            expression.Rows[0][0].Key.ShouldBe("Data1");
+            expression.Rows[0][0].Value.ShouldBe("Row1Data1");
+
+            expression.Rows[0][1].Key.ShouldBe("Data2");
+            expression.Rows[0][1].Value.ShouldBe("Row1Data2");
+
+            expression.Rows[1][0].Key.ShouldBe("Data1");
+            expression.Rows[1][0].Value.ShouldBe("Row2Data1");
 
+            expression.Rows[1][1].Key.ShouldBe("Data2");
+            expression.Rows[1][1].Value.ShouldBe("Row2Data2");
+        }
+
+        [Test]
+        public void RowsGetPopulatedWhenRowWithDictionaryIsCalled()
+        {
             var expression = new InsertDataExpression();
 
-            new InsertDataExpressionBuilder(expression).Row(values);
+            new InsertDataExpressionBuilder(expression).Row(
+                new Dictionary<string, object>
+                {
+                    ["Data1"] = "Row1Data1",
+                    ["Data2"] = "Row1Data2"
+                });
 
             expression.Rows.Count.ShouldBe(1);
 
             expression.Rows[0][0].Key.ShouldBe("Data1");
             expression.Rows[0][0].Value.ShouldBe("Row1Data1");
+
+            expression.Rows[0][1].Key.ShouldBe("Data2");
+            expression.Rows[0][1].Value.ShouldBe("Row1Data2");
+        }
+
+        [Test]
+        public void RowsGetPopulatedWhenRowsWithDictionaryIsCalled()
+        {
+            var expression = new InsertDataExpression();
+
+            new InsertDataExpressionBuilder(expression).Rows(
+                new Dictionary<string, object>
+                {
+                    ["Data1"] = "Row1Data1",
+                    ["Data2"] = "Row1Data2"
+                },
+                new Dictionary<string, object>
+                {
+                    ["Data1"] = "Row2Data1",
+                    ["Data2"] = "Row2Data2"
+                });
+
+            expression.Rows.Count.ShouldBe(2);
+
+            expression.Rows[0][0].Key.ShouldBe("Data1");
+            expression.Rows[0][0].Value.ShouldBe("Row1Data1");
+
             expression.Rows[0][1].Key.ShouldBe("Data2");
             expression.Rows[0][1].Value.ShouldBe("Row1Data2");
+
+            expression.Rows[1][0].Key.ShouldBe("Data1");
+            expression.Rows[1][0].Value.ShouldBe("Row2Data1");
+
+            expression.Rows[1][1].Key.ShouldBe("Data2");
+            expression.Rows[1][1].Value.ShouldBe("Row2Data2");
         }
 
         [Test]