Home > Software design >  Transaction Scope and Task.When all execution
Transaction Scope and Task.When all execution

Time:03-18

Intention: speed up Sql insert operation with a transaction commit / rollback support

  • Have a pretty big amount of data (about 900 k entities)
  • Would like to put down the data to SQL table in parallel, gonna to split the data per connection (e.g. 3 active connections, thus 300 k per a connection)
  • Would like to have possibility to rollback if something goes wrong, so try to using Transaction Scope (nested-TransactionScopeOption.Required)

Issue:

It looks like Transaction scope couldn't support such kind of Task.WhenAll thus, if ever there will be an error during execution the intermediate writes won't be rollback

Q: So I've wondered if it can be work around, or it just not appropriate way to leverage the transaction scope ?

   public void Upsert(IEnumerable<IEnumerable<Item>> splitPerConnection, DateTime endDate)
    {
        using (var scope = _transactionScopeFactory.Create(TransactionScopeOption.Required))
        {
            try
            {
                UpdateEndDate(endDate);

                var insertTasks = splitPerConnection.Select(ch => Task.Run(() => Insert(ch)));

                Task.WhenAll(insertTasks).GetAwaiter().GetResult();

                scope.Complete();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }

    private int Insert(IEnumerable<Item> items)
    {
        int affectedRows;

        using (var scope = _transactionScopeFactory.Create(TransactionScopeOption.Required))
        using (var sqlConnection = new SqlConnection(DockerMsSqlServerConnectionString))
        {
            sqlConnection.Open();
            affectedRows = sqlConnection.Execute(ItemsQueries.Insert, items, commandTimeout: 0);

            scope.Complete();
        }

        return affectedRows;
    }

    private int UpdateEndDate(DateTime endDate)
    {
        int affectedRows;

        using (var scope = _transactionScopeFactory.Create(TransactionScopeOption.Required))
        using (var sqlConnection = new SqlConnection(DockerMsSqlServerConnectionString))
        {
            sqlConnection.Open();
            affectedRows = sqlConnection.Execute(ItemsQueries.UpdateEndDate, new { EndDate = endDate }, commandTimeout: 0);

            scope.Complete();
        }

        return affectedRows;
    }

CodePudding user response:

You can try to leverage SqlBulkCopy, instead of splitting the items over different connection objects.

Insert 2 million rows into SQL Server quickly

You can create a dataset by modifying IEnumerable<Item>. All the parallel operations can be applied here to convert from IEnumerable<Item> to dataset. Once dataset is created, SqlBulkCopy will help you. You can create 3-4 sets of datasets and execute the insert query 3-4 times.

In this approach, you can maintain one database connection that also helps to follow ACID compliance of the database transactions.

  • Related