< Summary

Line coverage
100%
Covered lines: 63
Uncovered lines: 0
Coverable lines: 63
Total lines: 196
Line coverage: 100%
Branch coverage
100%
Covered branches: 20
Total branches: 20
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/NetCoreApplicationTemplate/NetCoreApplicationTemplate/src/ProjectTemplate.Infrastructure/Data/ApplicationDbContext.cs

#LineLine coverage
 1using Microsoft.EntityFrameworkCore;
 2using Microsoft.EntityFrameworkCore.Metadata;
 3using Microsoft.Extensions.Logging;
 4using ProjectTemplate.Infrastructure.Data.Entities;
 5
 6namespace ProjectTemplate.Infrastructure.Data;
 7
 8/// <summary>
 9/// Represents the EF Core database context for the ProjectTemplate application.
 10/// </summary>
 11public sealed partial class ApplicationDbContext(
 12    DbContextOptions<ApplicationDbContext> options,
 13    ILogger<ApplicationDbContext> logger,
 14    IApplicationSaveChangesPipeline saveChangesPipeline,
 15    ApplicationSaveChangesInterceptor? saveChangesInterceptor = null
 16)
 17817    : DbContext(options)
 18{
 18019    private readonly ILogger<ApplicationDbContext> _logger = logger;
 18020    private readonly IApplicationSaveChangesPipeline _saveChangesPipeline =
 18021        saveChangesPipeline ?? throw new ArgumentNullException(nameof(saveChangesPipeline));
 17822    private readonly ApplicationSaveChangesInterceptor? _configuredSaveChangesInterceptor = saveChangesInterceptor;
 23
 24    /// <summary>
 25    /// Gets the audit records for the application.
 26    /// </summary>
 14627    public DbSet<AuditRecord> AuditRecords => Set<AuditRecord>();
 28
 29    /// <summary>
 30    /// Gets the durable, minimized audit-completion outbox entries.
 31    /// </summary>
 32    public DbSet<ApplicationAuditCompletionOutboxEntry> ApplicationAuditCompletionOutboxEntries =>
 8233        Set<ApplicationAuditCompletionOutboxEntry>();
 34
 35    /// <summary>
 36    /// Gets the external login account links for the application.
 37    /// </summary>
 16638    public DbSet<ExternalLoginAccount> ExternalLoginAccounts => Set<ExternalLoginAccount>();
 39
 40    [LoggerMessage(
 41        EventId = 19000,
 42        Level = LogLevel.Trace,
 43        Message = "{EfCoreMessage}")]
 44    private static partial void LogEfCoreMessage(
 45        ILogger logger,
 46        string efCoreMessage);
 47
 48    [LoggerMessage(
 49        EventId = 19001,
 50        Level = LogLevel.Warning,
 51        Message = "Optimistic concurrency conflict detected while saving {EntryCount} tracked entity entries.")]
 52    private static partial void LogOptimisticConcurrencyConflict(
 53        ILogger logger,
 54        int entryCount,
 55        Exception exception);
 56
 57    /// <inheritdoc />
 58    protected override void OnModelCreating(ModelBuilder modelBuilder)
 59    {
 860        ArgumentNullException.ThrowIfNull(modelBuilder);
 61
 862        _ = modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
 863        ConfigureDataEntityDefaults(modelBuilder);
 864        ConfigureTimestampDefaults(modelBuilder);
 65
 866        base.OnModelCreating(modelBuilder);
 867    }
 68
 69    /// <inheritdoc />
 70    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
 71    {
 17872        ArgumentNullException.ThrowIfNull(optionsBuilder);
 73
 17874        ApplicationSaveChangesInterceptor interceptor =
 17875            _configuredSaveChangesInterceptor ?? new ApplicationSaveChangesInterceptor(_saveChangesPipeline);
 76
 17877        _ = optionsBuilder
 2374478            .LogTo(message => LogEfCoreMessage(_logger, message), LogLevel.Trace)
 17879            .AddInterceptors(interceptor)
 17880            .EnableDetailedErrors();
 81
 17882        base.OnConfiguring(optionsBuilder);
 17883    }
 84
 85    public bool HasUnsavedChanges()
 86    {
 2087        return ChangeTracker.HasChanges();
 88    }
 89
 90    public override int SaveChanges()
 91    {
 2092        return SaveChanges(acceptAllChangesOnSuccess: true);
 93    }
 94
 95    public override int SaveChanges(bool acceptAllChangesOnSuccess = true)
 96    {
 2097        return SaveChangesWithConcurrencyHandling(
 4098            () => base.SaveChanges(acceptAllChangesOnSuccess));
 99    }
 100
 101    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
 102    {
 140103        return SaveChangesAsync(
 140104            acceptAllChangesOnSuccess: true,
 140105            cancellationToken);
 106    }
 107
 108    public override Task<int> SaveChangesAsync(
 109        bool acceptAllChangesOnSuccess,
 110        CancellationToken cancellationToken = default)
 111    {
 140112        return SaveChangesWithConcurrencyHandlingAsync(
 280113            () => base.SaveChangesAsync(
 280114                acceptAllChangesOnSuccess,
 280115                cancellationToken));
 116    }
 117
 118    private static bool IsUtcTimestampProperty(string propertyName)
 119    {
 104120        return propertyName.EndsWith("Utc", StringComparison.Ordinal);
 121    }
 122
 123    private static void ConfigureDataEntityDefaults(ModelBuilder modelBuilder)
 124    {
 96125        foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes())
 126        {
 40127            if (!typeof(DataEntity).IsAssignableFrom(entityType.ClrType))
 128            {
 129                continue;
 130            }
 131
 40132            _ = modelBuilder.Entity(entityType.ClrType)
 40133                .Property<string>(nameof(DataEntity.ConcurrencyStamp))
 40134                .HasMaxLength(64)
 40135                .IsRequired()
 40136                .IsConcurrencyToken();
 137        }
 8138    }
 139
 140    private static void ConfigureTimestampDefaults(ModelBuilder modelBuilder)
 141    {
 96142        foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes())
 143        {
 1360144            foreach (IMutableProperty property in entityType.GetProperties())
 145            {
 640146                Type propertyType = Nullable.GetUnderlyingType(property.ClrType)
 640147                    ?? property.ClrType;
 148
 640149                if ((propertyType == typeof(DateTime) || propertyType == typeof(DateTimeOffset)) &&
 640150                    IsUtcTimestampProperty(property.Name))
 151                {
 104152                    property.SetPrecision(PersistenceTimestamp.Precision);
 153                }
 154            }
 155        }
 8156    }
 157
 158    private int SaveChangesWithConcurrencyHandling(Func<int> saveChanges)
 159    {
 160        try
 161        {
 20162            return saveChanges();
 163        }
 2164        catch (DbUpdateConcurrencyException exception)
 165        {
 2166            LogOptimisticConcurrencyConflict(_logger, exception.Entries.Count, exception);
 2167            throw;
 168        }
 18169    }
 170
 171    private async Task<int> SaveChangesWithConcurrencyHandlingAsync(Func<Task<int>> saveChanges)
 172    {
 173        try
 174        {
 140175            return await saveChanges().ConfigureAwait(false);
 176        }
 2177        catch (DbUpdateConcurrencyException exception)
 178        {
 2179            LogOptimisticConcurrencyConflict(_logger, exception.Entries.Count, exception);
 2180            throw;
 181        }
 134182    }
 183}

/home/runner/work/NetCoreApplicationTemplate/NetCoreApplicationTemplate/src/ProjectTemplate.Infrastructure/Data/ApplicationDbContext.Reconciliation.cs

#LineLine coverage
 1using Microsoft.EntityFrameworkCore;
 2using ProjectTemplate.Infrastructure.Data.Entities;
 3
 4namespace ProjectTemplate.Infrastructure.Data;
 5
 6public sealed partial class ApplicationDbContext
 7{
 8    public DbSet<ApplicationAuditReconciliationFinding> ApplicationAuditReconciliationFindings =>
 689        Set<ApplicationAuditReconciliationFinding>();
 10
 11    public DbSet<ApplicationAuditReconciliationRemediation> ApplicationAuditReconciliationRemediations =>
 212        Set<ApplicationAuditReconciliationRemediation>();
 13}