edit
This commit is contained in:
73
XLAB2/Infrastructure/DatabaseConfiguration.cs
Normal file
73
XLAB2/Infrastructure/DatabaseConfiguration.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace XLAB2.Infrastructure;
|
||||
|
||||
internal static class DatabaseConfiguration
|
||||
{
|
||||
private static readonly Lazy<DatabaseOptions> Options = new(LoadOptions, LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
|
||||
public static DatabaseOptions Current => Options.Value;
|
||||
|
||||
private static DatabaseOptions LoadOptions()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.SetBasePath(AppContext.BaseDirectory)
|
||||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||
.AddEnvironmentVariables(prefix: "XLAB2_")
|
||||
.Build();
|
||||
|
||||
var options = configuration.GetSection("Database").Get<DatabaseOptions>() ?? new DatabaseOptions();
|
||||
Validate(options);
|
||||
return options;
|
||||
}
|
||||
|
||||
private static void Validate(DatabaseOptions options)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(options.Server))
|
||||
{
|
||||
throw new InvalidOperationException("Database:Server is required.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(options.Database))
|
||||
{
|
||||
throw new InvalidOperationException("Database:Database is required.");
|
||||
}
|
||||
|
||||
if (options.ConnectTimeoutSeconds <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Database:ConnectTimeoutSeconds must be greater than zero.");
|
||||
}
|
||||
|
||||
if (options.CommandTimeoutSeconds <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Database:CommandTimeoutSeconds must be greater than zero.");
|
||||
}
|
||||
|
||||
if (options.MinPoolSize < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Database:MinPoolSize cannot be negative.");
|
||||
}
|
||||
|
||||
if (options.MaxPoolSize <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Database:MaxPoolSize must be greater than zero.");
|
||||
}
|
||||
|
||||
if (options.MinPoolSize > options.MaxPoolSize)
|
||||
{
|
||||
throw new InvalidOperationException("Database:MinPoolSize cannot be greater than Database:MaxPoolSize.");
|
||||
}
|
||||
|
||||
if (options.ConnectRetryCount < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Database:ConnectRetryCount cannot be negative.");
|
||||
}
|
||||
|
||||
if (options.ConnectRetryIntervalSeconds < 1)
|
||||
{
|
||||
throw new InvalidOperationException("Database:ConnectRetryIntervalSeconds must be greater than zero.");
|
||||
}
|
||||
}
|
||||
}
|
||||
32
XLAB2/Infrastructure/DatabaseOptions.cs
Normal file
32
XLAB2/Infrastructure/DatabaseOptions.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace XLAB2.Infrastructure;
|
||||
|
||||
internal sealed class DatabaseOptions
|
||||
{
|
||||
public string ApplicationName { get; set; } = "XLAB2";
|
||||
|
||||
public int CommandTimeoutSeconds { get; set; } = 60;
|
||||
|
||||
public int ConnectRetryCount { get; set; } = 3;
|
||||
|
||||
public int ConnectRetryIntervalSeconds { get; set; } = 10;
|
||||
|
||||
public int ConnectTimeoutSeconds { get; set; } = 15;
|
||||
|
||||
public string Database { get; set; } = "ASUMS";
|
||||
|
||||
public bool Encrypt { get; set; } = false;
|
||||
|
||||
public bool IntegratedSecurity { get; set; } = true;
|
||||
|
||||
public bool MultipleActiveResultSets { get; set; } = true;
|
||||
|
||||
public bool Pooling { get; set; } = true;
|
||||
|
||||
public int MaxPoolSize { get; set; } = 100;
|
||||
|
||||
public int MinPoolSize { get; set; } = 0;
|
||||
|
||||
public string Server { get; set; } = @"SEVENHILL\SQLEXPRESS";
|
||||
|
||||
public bool TrustServerCertificate { get; set; } = true;
|
||||
}
|
||||
16
XLAB2/Infrastructure/IDatabaseConnectionFactory.cs
Normal file
16
XLAB2/Infrastructure/IDatabaseConnectionFactory.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace XLAB2.Infrastructure;
|
||||
|
||||
internal interface IDatabaseConnectionFactory
|
||||
{
|
||||
string ConnectionString { get; }
|
||||
|
||||
DatabaseOptions Options { get; }
|
||||
|
||||
SqlConnection CreateConnection();
|
||||
|
||||
Task<SqlConnection> OpenConnectionAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
172
XLAB2/Infrastructure/SqlAsync.cs
Normal file
172
XLAB2/Infrastructure/SqlAsync.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace XLAB2.Infrastructure;
|
||||
|
||||
internal static class SqlAsync
|
||||
{
|
||||
public static async Task<List<T>> QueryAsync<T>(
|
||||
this SqlConnection connection,
|
||||
string commandText,
|
||||
Func<SqlDataReader, T> map,
|
||||
Action<SqlCommand> configureCommand = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (connection == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(connection));
|
||||
}
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(map));
|
||||
}
|
||||
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = commandText;
|
||||
configureCommand?.Invoke(command);
|
||||
|
||||
using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
||||
var items = new List<T>();
|
||||
|
||||
while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
items.Add(map(reader));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public static async Task<List<T>> QueryAsync<T>(
|
||||
this SqlConnection connection,
|
||||
SqlTransaction transaction,
|
||||
string commandText,
|
||||
Func<SqlDataReader, T> map,
|
||||
Action<SqlCommand> configureCommand = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (connection == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(connection));
|
||||
}
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(map));
|
||||
}
|
||||
|
||||
using var command = new SqlCommand(commandText, connection, transaction);
|
||||
configureCommand?.Invoke(command);
|
||||
|
||||
using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
||||
var items = new List<T>();
|
||||
|
||||
while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
items.Add(map(reader));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public static async Task<int> ExecuteNonQueryAsync(
|
||||
this SqlConnection connection,
|
||||
string commandText,
|
||||
Action<SqlCommand> configureCommand = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (connection == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(connection));
|
||||
}
|
||||
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = commandText;
|
||||
configureCommand?.Invoke(command);
|
||||
return await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async Task<int> ExecuteNonQueryAsync(
|
||||
this SqlConnection connection,
|
||||
SqlTransaction transaction,
|
||||
string commandText,
|
||||
Action<SqlCommand> configureCommand = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (connection == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(connection));
|
||||
}
|
||||
|
||||
using var command = new SqlCommand(commandText, connection, transaction);
|
||||
configureCommand?.Invoke(command);
|
||||
return await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async Task<T> ExecuteScalarAsync<T>(
|
||||
this SqlConnection connection,
|
||||
string commandText,
|
||||
Action<SqlCommand> configureCommand = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (connection == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(connection));
|
||||
}
|
||||
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = commandText;
|
||||
configureCommand?.Invoke(command);
|
||||
|
||||
var result = await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (result == null || result is DBNull)
|
||||
{
|
||||
return default!;
|
||||
}
|
||||
|
||||
return (T)Convert.ChangeType(result, typeof(T));
|
||||
}
|
||||
|
||||
public static async Task<T> ExecuteScalarAsync<T>(
|
||||
this SqlConnection connection,
|
||||
SqlTransaction transaction,
|
||||
string commandText,
|
||||
Action<SqlCommand> configureCommand = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (connection == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(connection));
|
||||
}
|
||||
|
||||
using var command = new SqlCommand(commandText, connection, transaction);
|
||||
configureCommand?.Invoke(command);
|
||||
|
||||
var result = await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (result == null || result is DBNull)
|
||||
{
|
||||
return default!;
|
||||
}
|
||||
|
||||
return (T)Convert.ChangeType(result, typeof(T));
|
||||
}
|
||||
|
||||
public static async Task<List<T>> QueryOpenConnectionAsync<T>(
|
||||
this IDatabaseConnectionFactory connectionFactory,
|
||||
string commandText,
|
||||
Func<SqlDataReader, T> map,
|
||||
Action<SqlCommand> configureCommand = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (connectionFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(connectionFactory));
|
||||
}
|
||||
|
||||
await using var connection = await connectionFactory.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
return await connection.QueryAsync(commandText, map, configureCommand, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
68
XLAB2/Infrastructure/SqlServerConnectionFactory.cs
Normal file
68
XLAB2/Infrastructure/SqlServerConnectionFactory.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace XLAB2.Infrastructure;
|
||||
|
||||
internal sealed class SqlServerConnectionFactory : IDatabaseConnectionFactory
|
||||
{
|
||||
private static readonly Lazy<IDatabaseConnectionFactory> CurrentFactory = new(() => new SqlServerConnectionFactory(DatabaseConfiguration.Current), LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
|
||||
private readonly DatabaseOptions _options;
|
||||
|
||||
public SqlServerConnectionFactory(DatabaseOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
ConnectionString = BuildConnectionString(options);
|
||||
}
|
||||
|
||||
public static IDatabaseConnectionFactory Current => CurrentFactory.Value;
|
||||
|
||||
public string ConnectionString { get; }
|
||||
|
||||
public DatabaseOptions Options => _options;
|
||||
|
||||
public SqlConnection CreateConnection()
|
||||
{
|
||||
return new SqlConnection(ConnectionString);
|
||||
}
|
||||
|
||||
public async Task<SqlConnection> OpenConnectionAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var connection = CreateConnection();
|
||||
|
||||
try
|
||||
{
|
||||
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
return connection;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await connection.DisposeAsync().ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal static string BuildConnectionString(DatabaseOptions options)
|
||||
{
|
||||
var builder = new SqlConnectionStringBuilder
|
||||
{
|
||||
ApplicationName = string.IsNullOrWhiteSpace(options.ApplicationName) ? "XLAB2" : options.ApplicationName.Trim(),
|
||||
ConnectRetryCount = Math.Max(0, options.ConnectRetryCount),
|
||||
ConnectRetryInterval = Math.Max(1, options.ConnectRetryIntervalSeconds),
|
||||
ConnectTimeout = Math.Max(1, options.ConnectTimeoutSeconds),
|
||||
DataSource = options.Server.Trim(),
|
||||
Encrypt = options.Encrypt,
|
||||
InitialCatalog = options.Database.Trim(),
|
||||
IntegratedSecurity = options.IntegratedSecurity,
|
||||
MaxPoolSize = Math.Max(1, options.MaxPoolSize),
|
||||
MinPoolSize = Math.Max(0, options.MinPoolSize),
|
||||
MultipleActiveResultSets = options.MultipleActiveResultSets,
|
||||
Pooling = options.Pooling,
|
||||
TrustServerCertificate = options.TrustServerCertificate
|
||||
};
|
||||
|
||||
return builder.ConnectionString;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user