< Summary

Line coverage
100%
Covered lines: 60
Uncovered lines: 0
Coverable lines: 60
Total lines: 185
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)
 18417    : DbContext(options)
 18{
 18619    private readonly ILogger<ApplicationDbContext> _logger = logger;
 18620    private readonly IApplicationSaveChangesPipeline _saveChangesPipeline =
 18621        saveChangesPipeline ?? throw new ArgumentNullException(nameof(saveChangesPipeline));
 18422    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 = 19001,
 42        Level = LogLevel.Warning,
 43        Message = "Optimistic concurrency conflict detected while saving {EntryCount} tracked entity entries.")]
 44    private static partial void LogOptimisticConcurrencyConflict(
 45        ILogger logger,
 46        int entryCount,
 47        Exception exception);
 48
 49    /// <inheritdoc />
 50    protected override void OnModelCreating(ModelBuilder modelBuilder)
 51    {
 1052        ArgumentNullException.ThrowIfNull(modelBuilder);
 53
 1054        _ = modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
 1055        ConfigureDataEntityDefaults(modelBuilder);
 1056        ConfigureTimestampDefaults(modelBuilder);
 57
 1058        base.OnModelCreating(modelBuilder);
 1059    }
 60
 61    /// <inheritdoc />
 62    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
 63    {
 18464        ArgumentNullException.ThrowIfNull(optionsBuilder);
 65
 18466        ApplicationSaveChangesInterceptor interceptor =
 18467            _configuredSaveChangesInterceptor ?? new ApplicationSaveChangesInterceptor(_saveChangesPipeline);
 68
 18469        _ = optionsBuilder.AddInterceptors(interceptor);
 70
 18471        base.OnConfiguring(optionsBuilder);
 18472    }
 73
 74    public bool HasUnsavedChanges()
 75    {
 2076        return ChangeTracker.HasChanges();
 77    }
 78
 79    public override int SaveChanges()
 80    {
 2081        return SaveChanges(acceptAllChangesOnSuccess: true);
 82    }
 83
 84    public override int SaveChanges(bool acceptAllChangesOnSuccess = true)
 85    {
 2086        return SaveChangesWithConcurrencyHandling(
 4087            () => base.SaveChanges(acceptAllChangesOnSuccess));
 88    }
 89
 90    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
 91    {
 14092        return SaveChangesAsync(
 14093            acceptAllChangesOnSuccess: true,
 14094            cancellationToken);
 95    }
 96
 97    public override Task<int> SaveChangesAsync(
 98        bool acceptAllChangesOnSuccess,
 99        CancellationToken cancellationToken = default)
 100    {
 140101        return SaveChangesWithConcurrencyHandlingAsync(
 280102            () => base.SaveChangesAsync(
 280103                acceptAllChangesOnSuccess,
 280104                cancellationToken));
 105    }
 106
 107    private static bool IsUtcTimestampProperty(string propertyName)
 108    {
 130109        return propertyName.EndsWith("Utc", StringComparison.Ordinal);
 110    }
 111
 112    private static void ConfigureDataEntityDefaults(ModelBuilder modelBuilder)
 113    {
 120114        foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes())
 115        {
 50116            if (!typeof(DataEntity).IsAssignableFrom(entityType.ClrType))
 117            {
 118                continue;
 119            }
 120
 50121            _ = modelBuilder.Entity(entityType.ClrType)
 50122                .Property<string>(nameof(DataEntity.ConcurrencyStamp))
 50123                .HasMaxLength(64)
 50124                .IsRequired()
 50125                .IsConcurrencyToken();
 126        }
 10127    }
 128
 129    private static void ConfigureTimestampDefaults(ModelBuilder modelBuilder)
 130    {
 120131        foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes())
 132        {
 1700133            foreach (IMutableProperty property in entityType.GetProperties())
 134            {
 800135                Type propertyType = Nullable.GetUnderlyingType(property.ClrType)
 800136                    ?? property.ClrType;
 137
 800138                if ((propertyType == typeof(DateTime) || propertyType == typeof(DateTimeOffset)) &&
 800139                    IsUtcTimestampProperty(property.Name))
 140                {
 130141                    property.SetPrecision(PersistenceTimestamp.Precision);
 142                }
 143            }
 144        }
 10145    }
 146
 147    private int SaveChangesWithConcurrencyHandling(Func<int> saveChanges)
 148    {
 149        try
 150        {
 20151            return saveChanges();
 152        }
 2153        catch (DbUpdateConcurrencyException exception)
 154        {
 2155            LogOptimisticConcurrencyConflict(_logger, exception.Entries.Count, exception);
 2156            throw;
 157        }
 18158    }
 159
 160    private async Task<int> SaveChangesWithConcurrencyHandlingAsync(Func<Task<int>> saveChanges)
 161    {
 162        try
 163        {
 140164            return await saveChanges().ConfigureAwait(false);
 165        }
 2166        catch (DbUpdateConcurrencyException exception)
 167        {
 2168            LogOptimisticConcurrencyConflict(_logger, exception.Entries.Count, exception);
 2169            throw;
 170        }
 134171    }
 172}

/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}