If you only want to do one call to the db to do all inserts then one option is to use a stored proc that takes a list:
The sql to create the type that will store our list:
create type [dbo].CampaignList as table (CampaignId int, CookieId int, [Url] varchar(255))
The stored proc to do the inserts
create procedure [dbo].[spSaveCampaigns]
@CampaignList CampaignList readonly
as
insert into tblCampaigns (CampaignId, CookieId, [Url])
select CampaignId, CookieId, [Url] from @CampaignList;
The C# to call it:
public async Task InsertCampigns()
{
var campaigns = new List<Campaign> {new Campaign(1, 1, "bar"), new Campaign(2, 2, "foo") };
using (var sqlConnection = new SqlConnection(_connectionString))
{
using (var cmd = new SqlCommand("exec [dbo].[spSaveCampaigns] @CampaignList", sqlConnection))
{
await sqlConnection.OpenAsync().ConfigureAwait(false);
using (var table = new DataTable())
{
table.Columns.Add("CampaignId", typeof(int));
table.Columns.Add("CookieId", typeof(int));
table.Columns.Add("Url", typeof(string));
foreach (var campaign in campaigns)
table.Rows.Add(campaign.CampaignId, campaign.CookieId, $"{campaign.Url}");
var parameters = new SqlParameter("@CampaignList", SqlDbType.Structured)
{
TypeName = "dbo.CampaignList",
Value = table
};
cmd.Parameters.Add(parameters);
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
}
}
}
}
You could pull the code that creates the data table from your type out to a helper to make this smaller.
Advantages:
It's parameterised properly.
I prefer to call stored procs rather than run sql against the db (but you might have a different opinion on this.)
Result calling
await InsertCampigns();
CampaignId | CookieId | Url
1 | 1 | bar
2 | 2 | foo
To do this without the stored proc, see this link from @Magnus in comment
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/table-valued-parameters#passing-a-table-valued-parameter-to-a-parameterized-sql-statement