• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

loresoft / FluentCommand / 20087768166

10 Dec 2025 04:54AM UTC coverage: 56.372% (-0.001%) from 56.373%
20087768166

push

github

pwelter34
update packages

1255 of 2799 branches covered (44.84%)

Branch coverage included in aggregate %.

3854 of 6264 relevant lines covered (61.53%)

369.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

39.49
/src/FluentCommand.SqlServer/Bulk/DataBulkCopy.cs
1
using System.Data;
2
using System.Linq.Expressions;
3

4
using FluentCommand.Extensions;
5

6
using Microsoft.Data.SqlClient;
7

8
namespace FluentCommand.Bulk;
9

10
/// <summary>
11
/// Provides a fluent API for performing <see cref="SqlBulkCopy"/> operations to efficiently copy large amounts of data into a SQL Server table.
12
/// </summary>
13
public class DataBulkCopy : DisposableBase, IDataBulkCopy
14
{
15
    private readonly IDataSession _dataSession;
16
    private readonly string _destinationTable;
17

18
    private readonly List<SqlBulkCopyColumnMapping> _mapping;
19
    private readonly List<string> _ignoreColumns;
20
    private readonly List<int> _ignoreOrdinal;
21

22
    private SqlBulkCopyOptions _options = SqlBulkCopyOptions.Default;
23
    private int? _batchSize;
24
    private int? _bulkCopyTimeout;
25
    private bool? _enableStreaming;
26
    private int? _notifyAfter;
27
    private bool? _autoMap;
28

29
    /// <summary>
30
    /// Initializes a new instance of the <see cref="DataBulkCopy"/> class for the specified data session and destination table.
31
    /// </summary>
32
    /// <param name="dataSession">The data session used for the bulk copy operation.</param>
33
    /// <param name="destinationTable">The name of the destination table in SQL Server.</param>
34
    public DataBulkCopy(IDataSession dataSession, string destinationTable)
4✔
35
    {
36
        _mapping = new List<SqlBulkCopyColumnMapping>();
4✔
37
        _ignoreColumns = new List<string>();
4✔
38
        _ignoreOrdinal = new List<int>();
4✔
39

40
        _dataSession = dataSession;
4✔
41
        _destinationTable = destinationTable;
4✔
42
    }
4✔
43

44
    /// <inheritdoc/>
45
    public IDataBulkCopy AutoMap(bool value = true)
46
    {
47
        _autoMap = value;
2✔
48
        return this;
2✔
49
    }
50

51
    /// <inheritdoc/>
52
    public IDataBulkCopy BatchSize(int value)
53
    {
54
        _batchSize = value;
×
55
        return this;
×
56
    }
57

58
    /// <inheritdoc/>
59
    public IDataBulkCopy BulkCopyTimeout(int value)
60
    {
61
        _bulkCopyTimeout = value;
×
62
        return this;
×
63
    }
64

65
    /// <inheritdoc/>
66
    public IDataBulkCopy EnableStreaming(bool value = true)
67
    {
68
        _enableStreaming = value;
×
69
        return this;
×
70
    }
71

72
    /// <inheritdoc/>
73
    public IDataBulkCopy NotifyAfter(int value)
74
    {
75
        _notifyAfter = value;
×
76
        return this;
×
77
    }
78

79
    /// <inheritdoc/>
80
    public IDataBulkCopy KeepIdentity(bool value = true)
81
    {
82
        _options = value
×
83
            ? _options.SetFlagOn(SqlBulkCopyOptions.KeepIdentity)
×
84
            : _options.SetFlagOff(SqlBulkCopyOptions.KeepIdentity);
×
85

86
        return this;
×
87
    }
88

89
    /// <inheritdoc/>
90
    public IDataBulkCopy CheckConstraints(bool value = true)
91
    {
92
        _options = value
×
93
            ? _options.SetFlagOn(SqlBulkCopyOptions.CheckConstraints)
×
94
            : _options.SetFlagOff(SqlBulkCopyOptions.CheckConstraints);
×
95

96
        return this;
×
97
    }
98

99
    /// <inheritdoc/>
100
    public IDataBulkCopy TableLock(bool value = true)
101
    {
102
        _options = value
×
103
            ? _options.SetFlagOn(SqlBulkCopyOptions.TableLock)
×
104
            : _options.SetFlagOff(SqlBulkCopyOptions.TableLock);
×
105

106
        return this;
×
107
    }
108

109
    /// <inheritdoc/>
110
    public IDataBulkCopy KeepNulls(bool value = true)
111
    {
112
        _options = value
×
113
            ? _options.SetFlagOn(SqlBulkCopyOptions.KeepNulls)
×
114
            : _options.SetFlagOff(SqlBulkCopyOptions.KeepNulls);
×
115

116
        return this;
×
117
    }
118

119
    /// <inheritdoc/>
120
    public IDataBulkCopy FireTriggers(bool value = true)
121
    {
122
        _options = value
×
123
            ? _options.SetFlagOn(SqlBulkCopyOptions.FireTriggers)
×
124
            : _options.SetFlagOff(SqlBulkCopyOptions.FireTriggers);
×
125

126
        return this;
×
127
    }
128

129
    /// <inheritdoc/>
130
    public IDataBulkCopy UseInternalTransaction(bool value = true)
131
    {
132
        _options = value
×
133
            ? _options.SetFlagOn(SqlBulkCopyOptions.UseInternalTransaction)
×
134
            : _options.SetFlagOff(SqlBulkCopyOptions.UseInternalTransaction);
×
135

136
        return this;
×
137
    }
138

139
    /// <inheritdoc/>
140
    public IDataBulkCopy Mapping(string sourceColumn, string destinationColumn)
141
    {
142
        var map = new SqlBulkCopyColumnMapping(sourceColumn, destinationColumn);
82✔
143
        _mapping.Add(map);
82✔
144
        return this;
82✔
145
    }
146

147
    /// <inheritdoc/>
148
    public IDataBulkCopy Mapping(int sourceColumnOrdinal, string destinationColumn)
149
    {
150
        var map = new SqlBulkCopyColumnMapping(sourceColumnOrdinal, destinationColumn);
×
151
        _mapping.Add(map);
×
152
        return this;
×
153
    }
154

155
    /// <inheritdoc/>
156
    public IDataBulkCopy Mapping(string sourceColumn, int destinationOrdinal)
157
    {
158
        var map = new SqlBulkCopyColumnMapping(sourceColumn, destinationOrdinal);
×
159
        _mapping.Add(map);
×
160
        return this;
×
161
    }
162

163
    /// <inheritdoc/>
164
    public IDataBulkCopy Mapping(int sourceColumnOrdinal, int destinationOrdinal)
165
    {
166
        var map = new SqlBulkCopyColumnMapping(sourceColumnOrdinal, destinationOrdinal);
×
167
        _mapping.Add(map);
×
168
        return this;
×
169
    }
170

171
    /// <inheritdoc/>
172
    public IDataBulkCopy Mapping<TEntity>(Action<DataBulkCopyMapping<TEntity>> builder)
173
        where TEntity : class
174
    {
175
        if (builder == null)
2!
176
            throw new ArgumentNullException(nameof(builder));
×
177

178
        var dataMapping = new DataBulkCopyMapping<TEntity>(this);
2✔
179
        builder(dataMapping);
2✔
180

181
        return this;
2✔
182
    }
183

184
    /// <inheritdoc/>
185
    public IDataBulkCopy Ignore(string sourceColumn)
186
    {
187
        _ignoreColumns.Add(sourceColumn);
11✔
188
        return this;
11✔
189
    }
190

191
    /// <inheritdoc/>
192
    public IDataBulkCopy Ignore(int sourceColumnOrdinal)
193
    {
194
        _ignoreOrdinal.Add(sourceColumnOrdinal);
×
195
        return this;
×
196
    }
197

198
    /// <inheritdoc/>
199
    public IDataBulkCopy Ignore<TEntity, TValue>(Expression<Func<TEntity, TValue>> sourceProperty)
200
        where TEntity : class
201
    {
202
        return Mapping<TEntity>(b => b.Ignore(sourceProperty));
×
203
    }
204

205
    /// <inheritdoc/>
206
    public void WriteToServer<TEntity>(IEnumerable<TEntity> data)
207
        where TEntity : class
208
    {
209
        using var dataReader = new ListDataReader<TEntity>(data);
3✔
210
        WriteToServer(dataReader);
3✔
211
    }
6✔
212

213
    /// <inheritdoc/>
214
    public async Task WriteToServerAsync<TEntity>(IEnumerable<TEntity> data, CancellationToken cancellationToken = default)
215
        where TEntity : class
216
    {
217
        using var dataReader = new ListDataReader<TEntity>(data);
1✔
218
        await WriteToServerAsync(dataReader, cancellationToken);
1✔
219
    }
1✔
220

221

222
    /// <inheritdoc/>
223
    public void WriteToServer(DataRow[] rows)
224
    {
225
        AssertDisposed();
×
226

227
        try
228
        {
229
            _dataSession.EnsureConnection();
×
230

231
            using var bulkCopy = Create();
×
232

233
            bulkCopy.WriteToServer(rows);
×
234
            bulkCopy.Close();
×
235
        }
236
        finally
237
        {
238
            _dataSession.ReleaseConnection();
×
239
            Dispose();
×
240
        }
×
241
    }
×
242

243
    /// <inheritdoc/>
244
    public async Task WriteToServerAsync(DataRow[] rows, CancellationToken cancellationToken = default)
245
    {
246
        AssertDisposed();
×
247

248
        try
249
        {
250
            await _dataSession.EnsureConnectionAsync(cancellationToken);
×
251

252
            using var bulkCopy = Create();
×
253

254
            await bulkCopy.WriteToServerAsync(rows, cancellationToken);
×
255

256
            bulkCopy.Close();
×
257
        }
×
258
        finally
259
        {
260
            _dataSession.ReleaseConnection();
×
261
            Dispose();
×
262
        }
263
    }
×
264

265

266
    /// <inheritdoc/>
267
    public void WriteToServer(DataTable table)
268
    {
269
        WriteToServer(table, 0);
×
270
    }
×
271

272
    /// <inheritdoc/>
273
    public void WriteToServer(DataTable table, DataRowState rowState)
274
    {
275
        AssertDisposed();
×
276

277
        try
278
        {
279
            ApplyAutoMapping(table);
×
280

281
            _dataSession.EnsureConnection();
×
282

283
            using var bulkCopy = Create();
×
284

285
            bulkCopy.WriteToServer(table, rowState);
×
286
            bulkCopy.Close();
×
287
        }
288
        finally
289
        {
290
            _dataSession.ReleaseConnection();
×
291
            Dispose();
×
292
        }
×
293
    }
×
294

295
    /// <inheritdoc/>
296
    public async Task WriteToServerAsync(DataTable table, DataRowState rowState = 0, CancellationToken cancellationToken = default)
297
    {
298
        AssertDisposed();
×
299

300
        try
301
        {
302
            ApplyAutoMapping(table);
×
303

304
            await _dataSession.EnsureConnectionAsync(cancellationToken);
×
305

306
            using var bulkCopy = Create();
×
307

308
            await bulkCopy.WriteToServerAsync(table, rowState, cancellationToken);
×
309

310
            bulkCopy.Close();
×
311
        }
×
312
        finally
313
        {
314
            _dataSession.ReleaseConnection();
×
315
            Dispose();
×
316
        }
317
    }
×
318

319

320
    /// <inheritdoc/>
321
    public void WriteToServer(IDataReader reader)
322
    {
323
        AssertDisposed();
3✔
324

325
        try
326
        {
327
            ApplyAutoMapping(reader);
3✔
328

329
            _dataSession.EnsureConnection();
3✔
330

331
            using var bulkCopy = Create();
3✔
332

333
            bulkCopy.WriteToServer(reader);
3✔
334
            bulkCopy.Close();
3✔
335
        }
336
        finally
337
        {
338
            _dataSession.ReleaseConnection();
3✔
339
            Dispose();
3✔
340
        }
3✔
341
    }
3✔
342

343
    /// <inheritdoc/>
344
    public async Task WriteToServerAsync(IDataReader reader, CancellationToken cancellationToken = default)
345
    {
346
        AssertDisposed();
1✔
347

348
        try
349
        {
350
            ApplyAutoMapping(reader);
1✔
351

352
            await _dataSession.EnsureConnectionAsync(cancellationToken);
1✔
353

354
            using var bulkCopy = Create();
1✔
355

356
            await bulkCopy.WriteToServerAsync(reader, cancellationToken);
1✔
357

358
            bulkCopy.Close();
1✔
359
        }
1✔
360
        finally
361
        {
362
            _dataSession.ReleaseConnection();
1✔
363
            Dispose();
1✔
364
        }
365
    }
1✔
366

367

368
    /// <summary>
369
    /// Applies automatic column mappings based on the <see cref="IDataReader"/> field names.
370
    /// </summary>
371
    /// <param name="reader">The data reader containing the field schema.</param>
372
    private void ApplyAutoMapping(IDataReader reader)
373
    {
374
        if (_autoMap != true)
4✔
375
            return;
2✔
376

377
        for (int i = 0; i < reader.FieldCount; i++)
96✔
378
        {
379
            var name = reader.GetName(i);
46✔
380
            Mapping(name, name);
46✔
381
        }
382
    }
2✔
383

384
    /// <summary>
385
    /// Applies automatic column mappings based on the <see cref="DataTable"/> column names.
386
    /// </summary>
387
    /// <param name="table">The data table containing the column schema.</param>
388
    private void ApplyAutoMapping(DataTable table)
389
    {
390
        if (_autoMap != true)
×
391
            return;
×
392

393
        foreach (DataColumn column in table.Columns)
×
394
            Mapping(column.ColumnName, column.ColumnName);
×
395
    }
×
396

397

398
    /// <summary>
399
    /// Creates and configures a <see cref="SqlBulkCopy"/> instance based on the current settings and mappings.
400
    /// </summary>
401
    /// <returns>A configured <see cref="SqlBulkCopy"/> instance ready for bulk copy operations.</returns>
402
    /// <exception cref="InvalidOperationException">
403
    /// Thrown if the underlying connection is not a <see cref="SqlConnection"/>.
404
    /// </exception>
405
    private SqlBulkCopy Create()
406
    {
407
        var sqlConnection = _dataSession.Connection as SqlConnection;
4✔
408
        if (sqlConnection == null)
4!
409
            throw new InvalidOperationException(
×
410
                "Bulk-Copy only supported by SQL Server.  Make sure DataSession was created with a valid SqlConnection.");
×
411

412
        var sqlTransaction = _dataSession.Transaction as SqlTransaction;
4✔
413

414
        var bulkCopy = new SqlBulkCopy(sqlConnection, _options, sqlTransaction);
4✔
415
        bulkCopy.DestinationTableName = _destinationTable;
4✔
416

417
        if (_batchSize.HasValue)
4!
418
            bulkCopy.BatchSize = _batchSize.Value;
×
419

420
        if (_bulkCopyTimeout.HasValue)
4!
421
            bulkCopy.BulkCopyTimeout = _bulkCopyTimeout.Value;
×
422

423
        if (_enableStreaming.HasValue)
4!
424
            bulkCopy.EnableStreaming = _enableStreaming.Value;
×
425

426
        if (_notifyAfter.HasValue)
4!
427
            bulkCopy.NotifyAfter = _notifyAfter.Value;
×
428

429
        // filter out ignored columns
430
        var mappings = _mapping
4✔
431
            .Where(m => !_ignoreColumns.Contains(m.SourceColumn)
86✔
432
                && !_ignoreOrdinal.Contains(m.SourceOrdinal));
86✔
433

434
        foreach (var mapping in mappings)
150✔
435
            bulkCopy.ColumnMappings.Add(mapping);
71✔
436

437
        return bulkCopy;
4✔
438
    }
439
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc