25

I am using the following code to insert records to a table in SQL Server 2014

using (SqlConnection conn = new SqlConnection(ConfigurationManager.AppSettings["myConnString"]))
{

   conn.Execute("INSERT statement here", insertList);

}

The insertList is a list that has 1 million items in it. I tested this insert on a i5 desktop and it took about 65 minutes to insert a million records to SQL Server on the same machine. I am not sure how dapper is doing the inserts behind the scenes. I certainly dont want to open and close the database connection a million times!

Is this the best way to do bulk inserts in dapper or should I try something else or go with plain ADO.Net using Enterprise library?

EDIT

In hindsight, I know using ADO.Net will be better, so will rephrase my question. I still would like to know if this is the best that dapper can do or am I missing a better way to do it in dapper itself?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
user20358
  • 14,182
  • 36
  • 114
  • 186
  • 1
    [A bulk insert](https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlbulkcopy.aspx) works with an `IDataReader` ... so the main challenge is to create a scheme and a dataReader which gives access with column names to the properties ... –  Mar 04 '15 at 14:21
  • 1
    use a sp and pass list to user defined table type in DB – Ehsan Sajjad Mar 04 '15 at 14:21
  • https://github.com/tmsmith/Dapper-Extensions/issues/18: http://stackoverflow.com/questions/9946287/correct-method-of-deleting-over-2100-rows-by-id-with-dapper/9947259#9947259 (which has the major downside of creating a memory-monster called `DataTable` ...) –  Mar 04 '15 at 14:33
  • http://stackoverflow.com/questions/10689779/bulk-inserts-taking-longer-than-expected-using-dapper – Alex Erygin Mar 19 '15 at 09:25

4 Answers4

10

Building on Ehsan Sajjad's comment, one of the ways is to write a stored procedure that has a READONLY parameter of a user-defined TABLE type.

Say you want to bulk insert contacts that consist of a first name and last name, this is how you would go about it: 1) Create a table type:

CREATE TYPE [dbo].[MyTableType] AS TABLE(
    [FirstName] [varchar](50) NULL,
    [LastName] [varchar](50) NULL
)
GO

2) Now create a stored proc that uses the above table type:

CREATE PROC [dbo].[YourProc]
/*other params here*/
@Names AS MyTableType READONLY
AS
/* proc body here 
 */
GO

3) On the .NET side, pass the parameter as System.Data.SqlDbType.Structured This usually involves creating an in-memory data-table, then adding rows to it and then using this DataTable object as the @Names parameter. NOTE: The DataTable is considered to be memory intensive - be careful and profile your code to be sure that it does not cause resource issues on your server.

ALTENATIVE SOLUTION Use the approach outlined here: https://stackoverflow.com/a/9947259/190476 The solution is for DELETE but can be adapted for an insert or update as well.

Community
  • 1
  • 1
Sudhanshu Mishra
  • 6,523
  • 2
  • 59
  • 76
4

The first choice should be SQL Bulk Copy, cause it's safe from SQL injection.

However, there is a way to drastically improve performance. You could merge multiple inserts into one SQL and have only one call instead of multiple. So instead of this:

enter image description here

You can have this:

enter image description here

Code for inserting Users in bulk can look like this:

public async Task InsertInBulk(IList<string> userNames)
{
    var sqls = GetSqlsInBatches(userNames);
    using (var connection = new SqlConnection(ConnectionString))
    {
        foreach (var sql in sqls)
        {
            await connection.ExecuteAsync(sql);
        }
    }
}

private IList<string> GetSqlsInBatches(IList<string> userNames)
{
    var insertSql = "INSERT INTO [Users] (Name, LastUpdatedAt) VALUES ";
    var valuesSql = "('{0}', getdate())";
    var batchSize = 1000;

    var sqlsToExecute = new List<string>();
    var numberOfBatches = (int)Math.Ceiling((double)userNames.Count / batchSize);

    for (int i = 0; i < numberOfBatches; i++)
    {
        var userToInsert = userNames.Skip(i * batchSize).Take(batchSize);
        var valuesToInsert = userToInsert.Select(u => string.Format(valuesSql, u));
        sqlsToExecute.Add(insertSql + string.Join(',', valuesToInsert));
    }

    return sqlsToExecute;
}

Whole article and performance comparison is available here: http://www.michalbialecki.com/2019/05/21/bulk-insert-in-dapper/

Mik
  • 3,998
  • 2
  • 26
  • 16
-1

I faced an issue of a solution wich should work with ADO, Entity and Dapper, so a made this lib; it generates batches in form of

IEnumerable<(string SqlQuery, IEnumerable<SqlParameter> SqlParameters)>  
IEnumerable<(string SqlQuery, DynamicParameters DapperDynamicParameters)> 

this link contains instructions. It's safe against SQL Injection, because the usage of parameters instead concatenation.

Usage with Dapper:

using MsSqlHelpers;
var mapper = new MapperBuilder<Person>()
    .SetTableName("People")
    .AddMapping(person => person.FirstName, columnName: "Name")
    .AddMapping(person => person.LastName, columnName: "Surename")
    .AddMapping(person => person.DateOfBirth, columnName: "Birthday")
    .Build();
var people = new List<Person>()
{ 
    new Person() { FirstName = "John", LastName = "Lennon", DateOfBirth = new DateTime(1940, 10, 9) },
    new Person() { FirstName = "Paul", LastName = "McCartney", DateOfBirth = new DateTime(1942, 6, 18) },
};
var connectionString = "Server=SERVER_ADDRESS;Database=DATABASE_NAME;User Id=USERNAME;Password=PASSWORD;";
var sqlQueriesAndDapperParameters = new MsSqlQueryGenerator().GenerateDapperParametrizedBulkInserts(mapper, people);
using (var sqlConnection = new SqlConnection(connectionString))
{
    // Default batch size: 1000 rows or (2100-1) parameters per insert.
    foreach (var (SqlQuery, DapperDynamicParameters) in sqlQueriesAndDapperParameters)
    {
        sqlConnection.Execute(SqlQuery, DapperDynamicParameters);
    }
}
Raksha Saini
  • 604
  • 12
  • 28
-5

The best free way to insert with excellent performance is using the SqlBulkCopy class directly as Alex and Andreas suggested.

Disclaimer: I'm the owner of the project Dapper Plus

This project is not free but supports the following operations:

  • BulkInsert
  • BulkUpdate
  • BulkDelete
  • BulkMerge

By using mapping and allowing to output value like identity columns.

// CONFIGURE & MAP entity
DapperPlusManager.Entity<Order>()
                 .Table("Orders")
                 .Identity(x => x.ID);

// CHAIN & SAVE entity
connection.BulkInsert(orders)
          .AlsoInsert(order => order.Items);
          .Include(x => x.ThenMerge(order => order.Invoice)
                         .AlsoMerge(invoice => invoice.Items))
          .AlsoMerge(x => x.ShippingAddress);   
Jonathan Magnan
  • 10,874
  • 2
  • 38
  • 60
  • 2
    @JonathanMagnan, not clear if Dapper Plus has free version, and how it differ from Pro version? – Arsen Mkrtchyan Feb 09 '17 at 15:26
  • Hello @ArsenMkrtchyan, there is no "FREE" version. You can, however, try it for free for an unlimited time until you are production ready. We will soon revisit all our products to make it clear. – Jonathan Magnan Feb 09 '17 at 19:20
  • ah, so for going in prod, anyway product should be bought? – Arsen Mkrtchyan Feb 09 '17 at 19:24
  • 2
    Yes, the product must be bought for the production. The trial ends at the end of every month, so you simply need to download it once per month to extend the trial which work perfectly for all non-production environment. – Jonathan Magnan Feb 09 '17 at 20:15
  • @JonathanMagnan I suppose you use in you library OUTPUT Clause and than use merge into for inserting. And merge statement always locks the tables. Does it causes often locks in the database? I just did not find any explanation of how it works in your site. – sinitram Jan 15 '20 at 21:03
  • 3
    RE:DapperPlus - The license fees start at $799/yr for one developer. A contractor built some software using Dapper Plus without being aware of it. For people who get it from Nuget there is no way to know that it is only a trial and the verbiage is misleading stating "free and prime features". There are no free features. " Features: BulkInsert, BulkDelete, BulkUpdate, BulkMerge, and more! Support: SQL Server, MySQL, Oracle, PostgreSQL, SQLite, and more! Online Example: https://dotnetfiddle.net/ltIqrC Include free and prime features." – brianc Nov 17 '20 at 22:34
  • 3
    There's an open source project with bulk inserts here. It's not really that hard. https://github.com/KostovMartin/Dapper.Bulk – Damien Sawyer Sep 20 '21 at 01:21
  • @DamienSawyer thanks, I kinda see these kind of answers as advertising for commercial libraries, piggybacking on SO as official documentation. – DARKGuy May 12 '23 at 18:16