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

loresoft / FluentCommand / 26923300515

04 Jun 2026 01:03AM UTC coverage: 65.014% (+9.9%) from 55.157%
26923300515

push

github

pwelter34
Merge branch 'master' of https://github.com/loresoft/FluentCommand

1728 of 3450 branches covered (50.09%)

Branch coverage included in aggregate %.

5510 of 7683 relevant lines covered (71.72%)

297.61 hits per line

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

69.32
/src/FluentCommand/DataConfigurationBuilder.cs
1
using System.Data.Common;
2
using System.Text.Json;
3

4
using FluentCommand.Extensions;
5
using FluentCommand.Query.Generators;
6

7
using Microsoft.Extensions.Configuration;
8
using Microsoft.Extensions.DependencyInjection;
9
using Microsoft.Extensions.DependencyInjection.Extensions;
10

11
namespace FluentCommand;
12

13
/// <summary>
14
/// A configuration builder class
15
/// </summary>
16
public class DataConfigurationBuilder
17
{
18
    private readonly IServiceCollection _services;
19
    private string? _nameOrConnectionString;
20
    private Type? _providerFactoryType;
21
    private Type? _dataCacheType;
22
    private Type? _queryGeneratorType;
23
    private Type? _queryLoggerType;
24
    private int? _commandTimeout;
25
    private Func<IServiceProvider, JsonSerializerOptions?>? _jsonSerializerOptionsFactory;
26

27
    /// <summary>
28
    /// Initializes a new instance of the <see cref="DataConfigurationBuilder"/> class.
29
    /// </summary>
30
    /// <param name="services">The services.</param>
31
    public DataConfigurationBuilder(IServiceCollection services)
9✔
32
    {
33
        _services = services;
9✔
34
    }
9✔
35

36

37
    /// <summary>
38
    /// Set the connection string or the name of connection string located in the application configuration
39
    /// </summary>
40
    /// <param name="nameOrConnectionString">The connection string or the name of connection string located in the application configuration.</param>
41
    /// <returns>
42
    /// The same configuration builder so that multiple calls can be chained.
43
    /// </returns>
44
    public DataConfigurationBuilder UseConnectionName(string nameOrConnectionString)
45
    {
46
        _nameOrConnectionString = nameOrConnectionString;
1✔
47
        return this;
1✔
48
    }
49

50
    /// <summary>
51
    /// The connection string to use with fluent command.
52
    /// </summary>
53
    /// <param name="connectionString">The connection string.</param>
54
    /// <returns>
55
    /// The same configuration builder so that multiple calls can be chained.
56
    /// </returns>
57
    public DataConfigurationBuilder UseConnectionString(string connectionString)
58
    {
59
        _nameOrConnectionString = connectionString;
8✔
60
        return this;
8✔
61
    }
62

63
    /// <summary>
64
    /// Sets the default command timeout in seconds.
65
    /// </summary>
66
    /// <param name="timeout">The time, in seconds, to wait for commands to execute.</param>
67
    /// <returns>
68
    /// The same configuration builder so that multiple calls can be chained.
69
    /// </returns>
70
    public DataConfigurationBuilder UseCommandTimeout(int timeout)
71
    {
72
        _commandTimeout = timeout;
1✔
73
        return this;
1✔
74
    }
75

76
    /// <summary>
77
    /// Sets the default command timeout.
78
    /// </summary>
79
    /// <param name="timeout">The time to wait for commands to execute.</param>
80
    /// <returns>
81
    /// The same configuration builder so that multiple calls can be chained.
82
    /// </returns>
83
    public DataConfigurationBuilder UseCommandTimeout(TimeSpan timeout)
84
    {
85
        _commandTimeout = (int)timeout.TotalSeconds;
1✔
86
        return this;
1✔
87
    }
88

89
    /// <summary>
90
    /// Sets the JSON serializer options used by generated JSON column readers.
91
    /// </summary>
92
    /// <param name="options">The JSON serializer options.</param>
93
    /// <returns>
94
    /// The same configuration builder so that multiple calls can be chained.
95
    /// </returns>
96
    public DataConfigurationBuilder UseJsonSerializerOptions(JsonSerializerOptions? options)
97
    {
98
        _jsonSerializerOptionsFactory = _ => options;
2✔
99
        return this;
2✔
100
    }
101

102
    /// <summary>
103
    /// Sets a factory for JSON serializer options used by generated JSON column readers.
104
    /// </summary>
105
    /// <param name="optionsFactory">The JSON serializer options factory.</param>
106
    /// <returns>
107
    /// The same configuration builder so that multiple calls can be chained.
108
    /// </returns>
109
    public DataConfigurationBuilder UseJsonSerializerOptions(Func<IServiceProvider, JsonSerializerOptions?> optionsFactory)
110
    {
111
        _jsonSerializerOptionsFactory = optionsFactory ?? throw new ArgumentNullException(nameof(optionsFactory));
1!
112
        return this;
1✔
113
    }
114

115

116
    /// <summary>
117
    /// Adds the provider factory to use with this configuration.
118
    /// </summary>
119
    /// <typeparam name="TService">The type of the service.</typeparam>
120
    /// <param name="providerFactory">The provider factory.</param>
121
    /// <returns>
122
    /// The same configuration builder so that multiple calls can be chained.
123
    /// </returns>
124
    /// <seealso cref="DbProviderFactory"/>
125
    public DataConfigurationBuilder AddProviderFactory<TService>(TService providerFactory)
126
        where TService : DbProviderFactory
127
    {
128
        _providerFactoryType = typeof(TService);
9✔
129
        _services.TryAddSingleton(providerFactory);
9✔
130
        return this;
9✔
131
    }
132

133
    /// <summary>
134
    /// Adds the provider factory to use with this configuration.
135
    /// </summary>
136
    /// <typeparam name="TService">The type of the service.</typeparam>
137
    /// <param name="implementationFactory">The implementation factory.</param>
138
    /// <returns>
139
    /// The same configuration builder so that multiple calls can be chained.
140
    /// </returns>
141
    /// <seealso cref="DbProviderFactory"/>
142
    public DataConfigurationBuilder AddProviderFactory<TService>(Func<IServiceProvider, TService> implementationFactory)
143
        where TService : DbProviderFactory
144
    {
145
        _providerFactoryType = typeof(TService);
×
146
        _services.TryAddSingleton(implementationFactory);
×
147
        return this;
×
148
    }
149

150
    /// <summary>
151
    /// Adds the provider factory to use with this configuration.
152
    /// </summary>
153
    /// <typeparam name="TService">The type of the service.</typeparam>
154
    /// <returns>
155
    /// The same configuration builder so that multiple calls can be chained.
156
    /// </returns>
157
    /// <seealso cref="DbProviderFactory"/>
158
    public DataConfigurationBuilder AddProviderFactory<TService>()
159
        where TService : DbProviderFactory
160
    {
161
        _providerFactoryType = typeof(TService);
×
162
        _services.TryAddSingleton<TService>();
×
163
        return this;
×
164
    }
165

166

167
    /// <summary>
168
    /// Adds the data cache service to use with this configuration.
169
    /// </summary>
170
    /// <typeparam name="TService">The type of the service.</typeparam>
171
    /// <param name="dataCache">The data cache.</param>
172
    /// <returns>
173
    /// The same configuration builder so that multiple calls can be chained.
174
    /// </returns>
175
    /// <seealso cref="IDataCache"/>
176
    public DataConfigurationBuilder AddDataCache<TService>(TService dataCache)
177
        where TService : class, IDataCache
178
    {
179
        _dataCacheType = typeof(TService);
×
180
        _services.TryAddSingleton(dataCache);
×
181
        return this;
×
182
    }
183

184
    /// <summary>
185
    /// Adds the data cache service to use with this configuration.
186
    /// </summary>
187
    /// <typeparam name="TService">The type of the service.</typeparam>
188
    /// <param name="implementationFactory">The implementation factory.</param>
189
    /// <returns>
190
    /// The same configuration builder so that multiple calls can be chained.
191
    /// </returns>
192
    /// <seealso cref="IDataCache"/>
193
    public DataConfigurationBuilder AddDataCache<TService>(Func<IServiceProvider, TService> implementationFactory)
194
        where TService : class, IDataCache
195
    {
196
        _dataCacheType = typeof(TService);
×
197
        _services.TryAddSingleton(implementationFactory);
×
198
        return this;
×
199
    }
200

201
    /// <summary>
202
    /// Adds the data cache service to use with this configuration.
203
    /// </summary>
204
    /// <typeparam name="TService">The type of the service.</typeparam>
205
    /// <returns>
206
    /// The same configuration builder so that multiple calls can be chained.
207
    /// </returns>
208
    /// <seealso cref="IDataCache"/>
209
    public DataConfigurationBuilder AddDataCache<TService>()
210
        where TService : class, IDataCache
211
    {
212
        _dataCacheType = typeof(TService);
3✔
213
        _services.TryAddSingleton<TService>();
3✔
214
        return this;
3✔
215
    }
216

217

218
    /// <summary>
219
    /// Adds the query generator service to use with this configuration.
220
    /// </summary>
221
    /// <typeparam name="TService">The type of the service.</typeparam>
222
    /// <param name="queryGenerator">The query generator.</param>
223
    /// <returns>
224
    /// The same configuration builder so that multiple calls can be chained.
225
    /// </returns>
226
    /// <seealso cref="IQueryGenerator"/>
227
    public DataConfigurationBuilder AddQueryGenerator<TService>(TService queryGenerator)
228
        where TService : class, IQueryGenerator
229
    {
230
        _queryGeneratorType = typeof(TService);
×
231
        _services.TryAddSingleton(queryGenerator);
×
232
        return this;
×
233
    }
234

235
    /// <summary>
236
    /// Adds the query generator service to use with this configuration.
237
    /// </summary>
238
    /// <typeparam name="TService">The type of the service.</typeparam>
239
    /// <returns>
240
    /// The same configuration builder so that multiple calls can be chained.
241
    /// </returns>
242
    /// <seealso cref="IQueryGenerator"/>
243
    public DataConfigurationBuilder AddQueryGenerator<TService>()
244
        where TService : class, IQueryGenerator
245
    {
246
        _queryGeneratorType = typeof(TService);
5✔
247
        _services.TryAddSingleton<TService>();
5✔
248
        return this;
5✔
249
    }
250

251
    /// <summary>
252
    /// Adds the query generator service to use with this configuration.
253
    /// </summary>
254
    /// <typeparam name="TService">The type of the service.</typeparam>
255
    /// <param name="implementationFactory">The implementation factory.</param>
256
    /// <returns>
257
    /// The same configuration builder so that multiple calls can be chained.
258
    /// </returns>
259
    /// <seealso cref="IQueryGenerator"/>
260
    public DataConfigurationBuilder AddQueryGenerator<TService>(Func<IServiceProvider, TService> implementationFactory)
261
        where TService : class, IQueryGenerator
262
    {
263
        _queryGeneratorType = typeof(TService);
×
264
        _services.TryAddSingleton(implementationFactory);
×
265
        return this;
×
266
    }
267

268
    /// <summary>
269
    /// Adds the SQL server generator to use with this configuration.
270
    /// </summary>
271
    /// <returns>
272
    /// The same configuration builder so that multiple calls can be chained.
273
    /// </returns>
274
    public DataConfigurationBuilder AddSqlServerGenerator()
275
    {
276
        AddQueryGenerator<SqlServerGenerator>();
3✔
277
        return this;
3✔
278
    }
279

280
    /// <summary>
281
    /// Adds the sqlite generator to use with this configuration.
282
    /// </summary>
283
    /// <returns>
284
    /// The same configuration builder so that multiple calls can be chained.
285
    /// </returns>
286
    public DataConfigurationBuilder AddSqliteGenerator()
287
    {
288
        AddQueryGenerator<SqliteGenerator>();
1✔
289
        return this;
1✔
290
    }
291

292
    /// <summary>
293
    /// Adds the PostgreSQL generator to use with this configuration.
294
    /// </summary>
295
    /// <returns>
296
    /// The same configuration builder so that multiple calls can be chained.
297
    /// </returns>
298
    public DataConfigurationBuilder AddPostgreSqlGenerator()
299
    {
300
        AddQueryGenerator<PostgreSqlGenerator>();
1✔
301
        return this;
1✔
302
    }
303

304

305
    /// <summary>
306
    /// Adds the query logger service to use with this configuration.
307
    /// </summary>
308
    /// <typeparam name="TService">The type of the service.</typeparam>
309
    /// <param name="queryLogger">The query logger.</param>
310
    /// <returns>
311
    /// The same configuration builder so that multiple calls can be chained.
312
    /// </returns>
313
    /// <seealso cref="IDataQueryLogger"/>
314
    public DataConfigurationBuilder AddQueryLogger<TService>(TService queryLogger)
315
        where TService : class, IDataQueryLogger
316
    {
317
        _queryLoggerType = typeof(TService);
×
318
        _services.TryAddSingleton(queryLogger);
×
319
        return this;
×
320
    }
321

322
    /// <summary>
323
    /// Adds the query logger service to use with this configuration.
324
    /// </summary>
325
    /// <typeparam name="TService">The type of the service.</typeparam>
326
    /// <returns>
327
    /// The same configuration builder so that multiple calls can be chained.
328
    /// </returns>
329
    /// <seealso cref="IDataQueryLogger"/>
330
    public DataConfigurationBuilder AddQueryLogger<TService>()
331
        where TService : class, IDataQueryLogger
332
    {
333
        _queryLoggerType = typeof(TService);
×
334
        _services.TryAddSingleton<TService>();
×
335
        return this;
×
336
    }
337

338
    /// <summary>
339
    /// Adds the query logger service to use with this configuration.
340
    /// </summary>
341
    /// <typeparam name="TService">The type of the service.</typeparam>
342
    /// <param name="implementationFactory">The implementation factory.</param>
343
    /// <returns>
344
    /// The same configuration builder so that multiple calls can be chained.
345
    /// </returns>
346
    /// <seealso cref="IDataQueryLogger"/>
347
    public DataConfigurationBuilder AddQueryLogger<TService>(Func<IServiceProvider, TService> implementationFactory)
348
        where TService : class, IDataQueryLogger
349
    {
350
        _queryLoggerType = typeof(TService);
×
351
        _services.TryAddSingleton(implementationFactory);
×
352
        return this;
×
353
    }
354

355

356
    /// <summary>
357
    /// Adds services via the configuration setup action.
358
    /// </summary>
359
    /// <param name="setupAction">The configuration setup action.</param>
360
    /// <returns>
361
    /// The same configuration builder so that multiple calls can be chained.
362
    /// </returns>
363
    public DataConfigurationBuilder AddService(Action<IServiceCollection> setupAction)
364
    {
365
        setupAction(_services);
3✔
366
        return this;
3✔
367
    }
368

369
    /// <summary>
370
    /// Adds an interceptor instance to the configuration.
371
    /// </summary>
372
    /// <typeparam name="TService">The type of the interceptor.</typeparam>
373
    /// <param name="interceptor">The interceptor instance.</param>
374
    /// <returns>
375
    /// The same configuration builder so that multiple calls can be chained.
376
    /// </returns>
377
    /// <seealso cref="IDataInterceptor"/>
378
    public DataConfigurationBuilder AddInterceptor<TService>(TService interceptor)
379
        where TService : class, IDataInterceptor
380
    {
381
        _services.AddSingleton<IDataInterceptor>(interceptor);
×
382
        return this;
×
383
    }
384

385
    /// <summary>
386
    /// Adds an interceptor using a factory to the configuration.
387
    /// </summary>
388
    /// <typeparam name="TService">The type of the interceptor.</typeparam>
389
    /// <param name="implementationFactory">The factory that creates the interceptor.</param>
390
    /// <returns>
391
    /// The same configuration builder so that multiple calls can be chained.
392
    /// </returns>
393
    /// <seealso cref="IDataInterceptor"/>
394
    public DataConfigurationBuilder AddInterceptor<TService>(Func<IServiceProvider, TService> implementationFactory)
395
        where TService : class, IDataInterceptor
396
    {
397
        _services.AddSingleton<IDataInterceptor>(implementationFactory);
×
398
        return this;
×
399
    }
400

401
    /// <summary>
402
    /// Adds an interceptor by type to the configuration.
403
    /// </summary>
404
    /// <typeparam name="TService">The type of the interceptor.</typeparam>
405
    /// <returns>
406
    /// The same configuration builder so that multiple calls can be chained.
407
    /// </returns>
408
    /// <seealso cref="IDataInterceptor"/>
409
    public DataConfigurationBuilder AddInterceptor<TService>()
410
        where TService : class, IDataInterceptor
411
    {
412
        _services.AddSingleton<IDataInterceptor, TService>();
×
413
        return this;
×
414
    }
415

416

417
    internal void AddConfiguration()
418
    {
419
        RegisterDefaults();
7✔
420

421
        // resolve using specific types to support multiple configurations
422
        var providerFactory = _providerFactoryType ?? typeof(DbProviderFactory);
7!
423
        var dataCache = _dataCacheType ?? typeof(IDataCache);
7✔
424
        var queryGenerator = _queryGeneratorType ?? typeof(IQueryGenerator);
7✔
425
        var queryLogger = _queryLoggerType ?? typeof(IDataQueryLogger);
7!
426

427
        _services.TryAddSingleton<IDataConfiguration>(sp =>
7✔
428
        {
7✔
429
            var connectionString = ResolveConnectionString(sp, _nameOrConnectionString)
7✔
430
                ?? throw new InvalidOperationException("Connection string could not be resolved.");
7✔
431

7✔
432
            return new DataConfiguration(
7✔
433
                (DbProviderFactory)sp.GetRequiredService(providerFactory),
7✔
434
                connectionString,
7✔
435
                sp.GetService(dataCache) as IDataCache,
7✔
436
                sp.GetService(queryGenerator) as IQueryGenerator,
7✔
437
                sp.GetService(queryLogger) as IDataQueryLogger,
7✔
438
                sp.GetServices<IDataInterceptor>(),
7✔
439
                _commandTimeout,
7✔
440
                _jsonSerializerOptionsFactory?.Invoke(sp)
7✔
441
            );
7✔
442
        });
7✔
443

444
        _services.TryAddTransient<IDataSessionFactory>(sp => sp.GetRequiredService<IDataConfiguration>());
7✔
445
        _services.TryAddTransient<IDataSession, DataSession>();
7✔
446
    }
7✔
447

448
    internal void AddConfiguration<TDiscriminator>()
449
    {
450
        RegisterDefaults();
2✔
451

452
        // resolve using specific types to support multiple configurations
453
        var providerFactory = _providerFactoryType ?? typeof(DbProviderFactory);
2!
454
        var dataCache = _dataCacheType ?? typeof(IDataCache);
2!
455
        var queryGenerator = _queryGeneratorType ?? typeof(IQueryGenerator);
2!
456
        var queryLogger = _queryLoggerType ?? typeof(IDataQueryLogger);
2!
457

458
        _services.TryAddSingleton<IDataConfiguration<TDiscriminator>>(sp =>
2✔
459
        {
2✔
460
            var connectionString = ResolveConnectionString(sp, _nameOrConnectionString)
2✔
461
                ?? throw new InvalidOperationException("Connection string could not be resolved.");
2✔
462

2✔
463
            return new DataConfiguration<TDiscriminator>(
2✔
464
                (DbProviderFactory)sp.GetRequiredService(providerFactory),
2✔
465
                connectionString,
2✔
466
                sp.GetService(dataCache) as IDataCache,
2✔
467
                sp.GetService(queryGenerator) as IQueryGenerator,
2✔
468
                sp.GetService(queryLogger) as IDataQueryLogger,
2✔
469
                sp.GetServices<IDataInterceptor>(),
2✔
470
                _commandTimeout,
2✔
471
                _jsonSerializerOptionsFactory?.Invoke(sp)
2✔
472
            );
2✔
473
        });
2✔
474

475
        _services.TryAddTransient<IDataSessionFactory<TDiscriminator>>(sp => sp.GetRequiredService<IDataConfiguration<TDiscriminator>>());
2!
476
        _services.TryAddTransient<IDataSession<TDiscriminator>, DataSession<TDiscriminator>>();
2✔
477
    }
2✔
478

479
    private void RegisterDefaults()
480
    {
481
        // add defaults if not already added
482
        _services.TryAddSingleton<IDataQueryFormatter, DataQueryFormatter>();
9✔
483

484
        // convert specific types to interfaces
485
        if (_providerFactoryType != null)
9!
486
            _services.TryAddSingleton(sp => (DbProviderFactory)sp.GetRequiredService(_providerFactoryType));
9✔
487

488
        if (_dataCacheType != null)
9✔
489
            _services.TryAddSingleton(sp => (IDataCache)sp.GetRequiredService(_dataCacheType));
3✔
490

491
        if (_queryGeneratorType != null)
9✔
492
            _services.TryAddSingleton(sp => (IQueryGenerator)sp.GetRequiredService(_queryGeneratorType));
5✔
493
        else
494
            _services.TryAddSingleton<IQueryGenerator, SqlServerGenerator>();
4✔
495

496
        if (_queryLoggerType != null)
9!
497
            _services.TryAddSingleton(sp => (IDataQueryLogger)sp.GetRequiredService(_queryLoggerType));
×
498
        else
499
            _services.TryAddSingleton<IDataQueryLogger, DataQueryLogger>();
9✔
500

501
    }
9✔
502

503
    private static string? ResolveConnectionString(IServiceProvider serviceProvider, string? nameOrConnectionString)
504
    {
505
        if (nameOrConnectionString is null || string.IsNullOrEmpty(nameOrConnectionString))
9!
506
            return null;
×
507

508
        var isConnectionString = nameOrConnectionString.IndexOfAny([';', '=']) > 0;
9✔
509
        if (isConnectionString)
9✔
510
            return nameOrConnectionString;
8✔
511

512
        var configuration = serviceProvider.GetRequiredService<IConfiguration>();
1✔
513

514
        // first try connection strings section
515
        var connectionString = configuration.GetConnectionString(nameOrConnectionString);
1✔
516
        if (connectionString.HasValue())
1!
517
            return connectionString;
1✔
518

519
        // next try root collection
520
        connectionString = configuration[nameOrConnectionString];
×
521
        if (connectionString.HasValue())
×
522
            return connectionString;
×
523

524
        return null;
×
525
    }
526
}
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