diff --git a/README.md b/README.md index 86c89cc..ab8be3d 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ The following is the table creation script: ```tsql CREATE TABLE [dbo].[Trace]( [Id] [bigint] IDENTITY(1,1) NOT NULL, - [TransactionId] [nvarchar](36) NULL, + [TransactionId] [nvarchar](MAX) NULL, [ServerId] [nvarchar](MAX) NULL, [ClientId] [nvarchar](MAX) NULL, [HttpMethod] [nvarchar](7) NULL, @@ -114,6 +114,7 @@ CREATE TABLE [dbo].[Trace]( [ExecutionTime] [numeric] NULL, ) ON [PRIMARY] ``` +From version 4.1.0 onwards, table creation is handled automatically, in case it is missing on the SQL instance. Remember to populate the **TraceDb** key within the SQL connection strings config file: diff --git a/src/OpenSharpTrace.Test/SQL/TraceTests.cs b/src/OpenSharpTrace.Test/SQL/TraceTests.cs index c068c68..455674c 100644 --- a/src/OpenSharpTrace.Test/SQL/TraceTests.cs +++ b/src/OpenSharpTrace.Test/SQL/TraceTests.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Threading.Tasks; namespace OpenSharpTrace.Test.SQL { @@ -34,7 +35,7 @@ public void Setup() } [Test] - public void CanAddManyTrace() + public async Task CanAddManyTrace() { var db = GetMemoryContext(); var traceFirst = new Trace @@ -63,7 +64,7 @@ public void CanAddManyTrace() }; var processor = new SqlTraceRepository(NullLoggerFactory.Instance, db); - Assert.DoesNotThrow(() => processor.InsertMany(new List { traceFirst, traceSecond })); + Assert.DoesNotThrowAsync(() => processor.InsertManyAsync(new List { traceFirst, traceSecond })); } public static TraceContext GetMemoryContext() diff --git a/src/OpenSharpTrace.TestApi/Controllers/WeatherForecastController.cs b/src/OpenSharpTrace.TestApi/Controllers/WeatherForecastController.cs index 56fece5..b19d527 100644 --- a/src/OpenSharpTrace.TestApi/Controllers/WeatherForecastController.cs +++ b/src/OpenSharpTrace.TestApi/Controllers/WeatherForecastController.cs @@ -1,8 +1,10 @@ -using Microsoft.AspNetCore.Mvc; +// (c) 2022 Francesco Del Re +// This code is licensed under MIT license (see LICENSE.txt for details) +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using OpenSharpTrace.Controllers; using OpenSharpTrace.Persistence.SQL.Entities; -using OpenSharpTrace.TraceQueue; +using OpenSharpTrace.TransactionQueue; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/OpenSharpTrace.TestApi/OpenSharpTrace.TestApi.csproj b/src/OpenSharpTrace.TestApi/OpenSharpTrace.TestApi.csproj index f5fbd80..065e91c 100644 --- a/src/OpenSharpTrace.TestApi/OpenSharpTrace.TestApi.csproj +++ b/src/OpenSharpTrace.TestApi/OpenSharpTrace.TestApi.csproj @@ -9,11 +9,17 @@ - + + + + Always + + + diff --git a/src/OpenSharpTrace.TestApi/Program.cs b/src/OpenSharpTrace.TestApi/Program.cs index ba8104b..2eae3f4 100644 --- a/src/OpenSharpTrace.TestApi/Program.cs +++ b/src/OpenSharpTrace.TestApi/Program.cs @@ -1,11 +1,7 @@ +// (c) 2022 Francesco Del Re +// This code is licensed under MIT license (see LICENSE.txt for details) using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace OpenSharpTrace.TestApi { diff --git a/src/OpenSharpTrace.TestApi/Startup.cs b/src/OpenSharpTrace.TestApi/Startup.cs index 9cf1c36..f42f9a4 100644 --- a/src/OpenSharpTrace.TestApi/Startup.cs +++ b/src/OpenSharpTrace.TestApi/Startup.cs @@ -1,3 +1,5 @@ +// (c) 2022 Francesco Del Re +// This code is licensed under MIT license (see LICENSE.txt for details) using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -26,6 +28,7 @@ public void ConfigureServices(IServiceCollection services) c.SwaggerDoc("v1", new OpenApiInfo { Title = "OpenSharpTrace.TestApi", Version = "v1" }); }); + // Register the OpenSharpTrace services.RegisterOpenSharpTrace(); } diff --git a/src/OpenSharpTrace.TestApi/WeatherForecast.cs b/src/OpenSharpTrace.TestApi/WeatherForecast.cs index 5eab5a5..c0b3841 100644 --- a/src/OpenSharpTrace.TestApi/WeatherForecast.cs +++ b/src/OpenSharpTrace.TestApi/WeatherForecast.cs @@ -1,3 +1,5 @@ +// (c) 2022 Francesco Del Re +// This code is licensed under MIT license (see LICENSE.txt for details) using System; namespace OpenSharpTrace.TestApi diff --git a/src/OpenSharpTrace.TestApi/appsettings.json b/src/OpenSharpTrace.TestApi/appsettings.json index 1d460ed..041173f 100644 --- a/src/OpenSharpTrace.TestApi/appsettings.json +++ b/src/OpenSharpTrace.TestApi/appsettings.json @@ -1,6 +1,8 @@ { "ConnectionStrings": { - "TraceDb": "Server=(***;Database=***;Trusted_Connection=True;MultipleActiveResultSets=true" + //"TraceDb": "Server=(***;Database=***;Trusted_Connection=True;MultipleActiveResultSets=true" + "TraceDb": "Server=ccsint.database.windows.net,1433;Initial Catalog=DS05084_TIS_CIGO;User ID=almavivasa;Password=CIGDev2021!/*", + //"DefaultConnection": "Server=ccsint.database.windows.net,1433;Initial Catalog=DS05084_TIS_CIGO;User ID=almavivasa;Password=CIGDev2021!/*" }, "Logging": { "LogLevel": { diff --git a/src/OpenSharpTrace/Abstractions/Persistence/ISqlTraceRepository.cs b/src/OpenSharpTrace/Abstractions/Persistence/ISqlTraceRepository.cs index 5e6bede..1461dda 100644 --- a/src/OpenSharpTrace/Abstractions/Persistence/ISqlTraceRepository.cs +++ b/src/OpenSharpTrace/Abstractions/Persistence/ISqlTraceRepository.cs @@ -2,11 +2,12 @@ // This code is licensed under MIT license (see LICENSE.txt for details) using OpenSharpTrace.Persistence.SQL.Entities; using System.Collections.Generic; +using System.Threading.Tasks; namespace OpenSharpTrace.Abstractions.Persistence { public interface ISqlTraceRepository { - void InsertMany(List entity); + Task InsertManyAsync(List entity); } } diff --git a/src/OpenSharpTrace/Controllers/OpenSharpTraceController.cs b/src/OpenSharpTrace/Controllers/OpenSharpTraceController.cs index 5c0231c..0945305 100644 --- a/src/OpenSharpTrace/Controllers/OpenSharpTraceController.cs +++ b/src/OpenSharpTrace/Controllers/OpenSharpTraceController.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; using OpenSharpTrace.Persistence.SQL.Entities; -using OpenSharpTrace.TraceQueue; +using OpenSharpTrace.TransactionQueue; using OpenSharpTrace.Utilities; using System; using System.Linq; diff --git a/src/OpenSharpTrace/Middleware/OpenSharpTraceServiceCollectionExtensions.cs b/src/OpenSharpTrace/Middleware/OpenSharpTraceServiceCollectionExtensions.cs index d6126f5..3b08751 100644 --- a/src/OpenSharpTrace/Middleware/OpenSharpTraceServiceCollectionExtensions.cs +++ b/src/OpenSharpTrace/Middleware/OpenSharpTraceServiceCollectionExtensions.cs @@ -6,8 +6,9 @@ using OpenSharpTrace.Abstractions.Persistence; using OpenSharpTrace.Persistence.SQL; using OpenSharpTrace.Persistence.SQL.Entities; -using OpenSharpTrace.TraceQueue; +using OpenSharpTrace.TransactionQueue; using OpenSharpTrace.TransactionScheduler; +using System; using System.IO; namespace OpenSharpTrace.Middleware @@ -22,19 +23,25 @@ public static void RegisterOpenSharpTrace(this IServiceCollection collection) { var configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true, true) + .AddJsonFile("appsettings.json", false, true) .AddEnvironmentVariables() .Build(); - collection.AddScoped(); + var connectionString = configuration.GetConnectionString("TraceDb"); + if (string.IsNullOrEmpty(connectionString)) + { + throw new InvalidOperationException("The connection string 'TraceDb' is not configured."); + } + collection.AddDbContext(options => { - options.UseSqlServer(configuration.GetConnectionString("TraceDb"), + options.UseSqlServer(connectionString, sqlServerOptionsAction: sqlOptions => { sqlOptions.EnableRetryOnFailure(); }); }); + collection.AddScoped(); collection.AddSingleton, TraceQueue>(); collection.AddSingleton(); collection.AddHostedService(); @@ -49,19 +56,25 @@ public static void RegisterOpenSharpTrace(this IServiceCollection collection, st { var configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true, true) + .AddJsonFile("appsettings.json", false, true) .AddEnvironmentVariables() .Build(); - collection.AddScoped(); + var connectionString = configuration.GetConnectionString(connectionKey); + if (string.IsNullOrEmpty(connectionString)) + { + throw new InvalidOperationException($"The connection string '{connectionKey}' is not configured."); + } + collection.AddDbContext(options => { - options.UseSqlServer(configuration.GetConnectionString(connectionKey), + options.UseSqlServer(connectionString, sqlServerOptionsAction: sqlOptions => { sqlOptions.EnableRetryOnFailure(); }); }); + collection.AddScoped(); collection.AddSingleton, TraceQueue>(); collection.AddSingleton(); collection.AddHostedService(); @@ -77,19 +90,25 @@ public static void RegisterOpenSharpTrace(this IServiceCollection collection, st { var configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile(jsonFileName, true, true) + .AddJsonFile(jsonFileName, false, true) .AddEnvironmentVariables() .Build(); - collection.AddScoped(); + var connectionString = configuration.GetConnectionString(connectionKey); + if (string.IsNullOrEmpty(connectionString)) + { + throw new InvalidOperationException($"The connection string '{connectionKey}' is not configured."); + } + collection.AddDbContext(options => { - options.UseSqlServer(configuration.GetConnectionString(connectionKey), + options.UseSqlServer(connectionString, sqlServerOptionsAction: sqlOptions => { sqlOptions.EnableRetryOnFailure(); }); }); + collection.AddScoped(); collection.AddSingleton, TraceQueue>(); collection.AddSingleton(); collection.AddHostedService(); diff --git a/src/OpenSharpTrace/Persistence/SQL/Scripts/CreateTable.sql b/src/OpenSharpTrace/Persistence/SQL/Scripts/CreateTable.sql index 78ca90e..9be03bd 100644 --- a/src/OpenSharpTrace/Persistence/SQL/Scripts/CreateTable.sql +++ b/src/OpenSharpTrace/Persistence/SQL/Scripts/CreateTable.sql @@ -1,6 +1,6 @@ CREATE TABLE [dbo].[Trace]( [Id] [bigint] IDENTITY(1,1) NOT NULL, - [TransactionId] [nvarchar](36) NULL, + [TransactionId] [nvarchar](MAX) NULL, [ServerId] [nvarchar](MAX) NULL, [ClientId] [nvarchar](MAX) NULL, [HttpMethod] [nvarchar](7) NULL, diff --git a/src/OpenSharpTrace/Persistence/SQL/SqlTraceRepository.cs b/src/OpenSharpTrace/Persistence/SQL/SqlTraceRepository.cs index b39aa89..8469f21 100644 --- a/src/OpenSharpTrace/Persistence/SQL/SqlTraceRepository.cs +++ b/src/OpenSharpTrace/Persistence/SQL/SqlTraceRepository.cs @@ -6,6 +6,7 @@ using OpenSharpTrace.Persistence.SQL.Entities; using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace OpenSharpTrace.Persistence.SQL { @@ -20,12 +21,12 @@ public SqlTraceRepository(ILoggerFactory loggerFactory, TraceContext context) _context = context ?? throw new ArgumentNullException(nameof(context)); _logger = loggerFactory.CreateLogger(GetType().ToString()); } - + /// /// Write the current trace entities /// /// - public void InsertMany(List entities) + public async Task InsertManyAsync(List entities) { if (entities == null || entities.Count == 0) { @@ -36,22 +37,28 @@ public void InsertMany(List entities) { var strategy = _context.Database.CreateExecutionStrategy(); - strategy.Execute(() => + await strategy.ExecuteAsync(async () => { - using (var transaction = _context.Database.BeginTransaction()) + using (var transaction = await _context.Database.BeginTransactionAsync()) { - foreach (var entity in entities) + try + { + await _context.Trace.AddRangeAsync(entities); + await _context.SaveChangesAsync(); + await transaction.CommitAsync(); + } + catch (Exception ex) { - _context.Trace.Add(entity); + _logger?.LogError(ex, "An error occurred while committing the transaction."); + await transaction.RollbackAsync(); + throw; } - _context.SaveChanges(); - transaction.Commit(); - } + } }); } catch (Exception ex) { - _logger?.LogError(ex.Message); + _logger?.LogError(ex, "An error occurred while inserting trace entities."); } } } diff --git a/src/OpenSharpTrace/Persistence/SQL/TraceContext.cs b/src/OpenSharpTrace/Persistence/SQL/TraceContext.cs index 1d1f702..a82ce86 100644 --- a/src/OpenSharpTrace/Persistence/SQL/TraceContext.cs +++ b/src/OpenSharpTrace/Persistence/SQL/TraceContext.cs @@ -8,10 +8,6 @@ namespace OpenSharpTrace.Persistence.SQL { public class TraceContext : DbContext, IDisposable { - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - } - public TraceContext(DbContextOptions options) : base(options) { @@ -29,55 +25,34 @@ public TraceContext(DbContextOptions options) public virtual DbSet Trace { get; set; } - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - } - private void EnsureTraceTableExists() { - using (var connection = Database.GetDbConnection()) + try { - try - { - if (connection.State != System.Data.ConnectionState.Open) - { - connection.Open(); - } - - using (var command = connection.CreateCommand()) - { - command.CommandText = @" - IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Trace') - BEGIN - CREATE TABLE [dbo].[Trace]( - [Id] [bigint] IDENTITY(1,1) NOT NULL, - [TransactionId] [nvarchar](36) NULL, - [ServerId] [nvarchar](MAX) NULL, - [ClientId] [nvarchar](MAX) NULL, - [HttpMethod] [nvarchar](7) NULL, - [HttpPath] [nvarchar](MAX) NULL, - [HttpStatusCode] [int] NULL, - [ActionDescriptor] [nvarchar](MAX) NULL, - [RemoteAddress] [nvarchar](MAX) NULL, - [JsonRequest] [nvarchar](MAX) NULL, - [JsonResponse] [nvarchar](MAX) NULL, - [TimeStamp] [datetime2](7) NULL, - [Exception] [nvarchar](MAX) NULL, - [ExecutionTime] [numeric] NULL, - ) ON [PRIMARY]; - END"; - command.ExecuteNonQuery(); - } - } - catch - { - // database is not ready or the connectionstring is wrong - } - finally - { - connection.Close(); - } + Database.ExecuteSqlRaw(@" + IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Trace') + BEGIN + CREATE TABLE [dbo].[Trace]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [TransactionId] [nvarchar](MAX) NULL, + [ServerId] [nvarchar](MAX) NULL, + [ClientId] [nvarchar](MAX) NULL, + [HttpMethod] [nvarchar](7) NULL, + [HttpPath] [nvarchar](MAX) NULL, + [HttpStatusCode] [int] NULL, + [ActionDescriptor] [nvarchar](MAX) NULL, + [RemoteAddress] [nvarchar](MAX) NULL, + [JsonRequest] [nvarchar](MAX) NULL, + [JsonResponse] [nvarchar](MAX) NULL, + [TimeStamp] [datetime2](7) NULL, + [Exception] [nvarchar](MAX) NULL, + [ExecutionTime] [numeric] NULL, + ) ON [PRIMARY]; + END"); + } + catch + { + // database is not ready or the connectionstring is wrong } } } diff --git a/src/OpenSharpTrace/TransactionQueue/ITraceQueue.cs b/src/OpenSharpTrace/TransactionQueue/ITraceQueue.cs index 93ac59e..0f18974 100644 --- a/src/OpenSharpTrace/TransactionQueue/ITraceQueue.cs +++ b/src/OpenSharpTrace/TransactionQueue/ITraceQueue.cs @@ -1,6 +1,6 @@ // (c) 2022 Francesco Del Re // This code is licensed under MIT license (see LICENSE.txt for details) -namespace OpenSharpTrace.TraceQueue +namespace OpenSharpTrace.TransactionQueue { public interface ITraceQueue { diff --git a/src/OpenSharpTrace/TransactionQueue/TraceQueue.cs b/src/OpenSharpTrace/TransactionQueue/TraceQueue.cs index d724292..dbd68df 100644 --- a/src/OpenSharpTrace/TransactionQueue/TraceQueue.cs +++ b/src/OpenSharpTrace/TransactionQueue/TraceQueue.cs @@ -2,7 +2,7 @@ // This code is licensed under MIT license (see LICENSE.txt for details) using System.Collections.Concurrent; -namespace OpenSharpTrace.TraceQueue +namespace OpenSharpTrace.TransactionQueue { /// /// Global queue for track persistence. diff --git a/src/OpenSharpTrace/TransactionScheduler/ScheduledServiceTransaction.cs b/src/OpenSharpTrace/TransactionScheduler/ScheduledServiceTransaction.cs index 18943d2..0babdb2 100644 --- a/src/OpenSharpTrace/TransactionScheduler/ScheduledServiceTransaction.cs +++ b/src/OpenSharpTrace/TransactionScheduler/ScheduledServiceTransaction.cs @@ -30,7 +30,7 @@ private async void DoWork(object state) { using var scope = _services.CreateScope(); var serviceTransaction = scope.ServiceProvider.GetRequiredService(); - await serviceTransaction.WriteTraceFromQueue(); + await serviceTransaction.WriteTraceFromQueueAsync(); } public Task StopAsync(CancellationToken cancellationToken) diff --git a/src/OpenSharpTrace/TransactionScheduler/ServiceTransaction.cs b/src/OpenSharpTrace/TransactionScheduler/ServiceTransaction.cs index a68dda8..8e88a48 100644 --- a/src/OpenSharpTrace/TransactionScheduler/ServiceTransaction.cs +++ b/src/OpenSharpTrace/TransactionScheduler/ServiceTransaction.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using OpenSharpTrace.Abstractions.Persistence; using OpenSharpTrace.Persistence.SQL.Entities; -using OpenSharpTrace.TraceQueue; +using OpenSharpTrace.TransactionQueue; using System.Collections.Generic; using System.Threading.Tasks; @@ -26,9 +26,9 @@ public ServiceTransaction( /// Persists all tracks collected since the last run /// /// - public Task WriteTraceFromQueue() + public async Task WriteTraceFromQueueAsync() { - if(_transactionQueue.Count() == 0) return Task.CompletedTask; + if(_transactionQueue.Count() == 0) return; using (var scope = _scopeFactory.CreateScope()) { @@ -40,10 +40,10 @@ public Task WriteTraceFromQueue() } var sqlTraceRepository = scope.ServiceProvider.GetRequiredService(); - sqlTraceRepository.InsertMany(currentTraceList); + await sqlTraceRepository.InsertManyAsync(currentTraceList); } - return Task.CompletedTask; + return; } } }