< Summary - ZhEaIsNsAaBn.Libraries coverage (affected: 23 packages)

Information
Class: ZhEaIsNsAaBn.Infrastructure.Core.AuditMap
Assembly: ZhEaIsNsAaBn.Infrastructure.Core
File(s): /home/runner/work/ZhEaIsNsAaBn.Libraries.V1/ZhEaIsNsAaBn.Libraries.V1/src/DataBasePackages/ZhEaIsNsAaBn.Infrastructure.Core/Auditing/AuditMap.cs
Line coverage
97%
Covered lines: 46
Uncovered lines: 1
Coverable lines: 47
Total lines: 142
Line coverage: 97.8%
Branch coverage
90%
Covered branches: 18
Total branches: 20
Branch coverage: 90%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
get_IsEmpty()100%11100%
get_SoftDeleteFlag()100%11100%
get_Item(...)100%11100%
For(...)100%11100%
Build(...)87.5%161695.83%
Implements(...)100%11100%
Check()100%44100%

File(s)

/home/runner/work/ZhEaIsNsAaBn.Libraries.V1/ZhEaIsNsAaBn.Libraries.V1/src/DataBasePackages/ZhEaIsNsAaBn.Infrastructure.Core/Auditing/AuditMap.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using System.Reflection;
 3
 4namespace ZhEaIsNsAaBn.Infrastructure.Core;
 5
 6/// <summary>An audit field an entity can have.</summary>
 7internal enum AuditRole
 8{
 9    SoftDeleteFlag,
 10    DeletedDate,
 11    DeletedById,
 12    DeletedByUser,
 13    CreatedDate,
 14    CreatedById,
 15    CreatedByUser,
 16    UpdatedDate,
 17    UpdatedById,
 18    UpdatedByUser
 19}
 20
 21/// <summary>
 22/// Which property of an entity type plays which audit role: from the Domain attributes (<c>[CreatedDate]</c>, <c>[SoftD
 23/// first, then from the Domain interfaces (<see cref="IBaseTrackableEntity"/>, <see cref="IBaseTransactionEntity{TId}"/
 24/// <see cref="IDeletableEntity{TId}"/>). Built once per CLR type.
 25/// </summary>
 26internal sealed class AuditMap
 27{
 428    private static readonly ConcurrentDictionary<Type, AuditMap> Cache = new();
 29
 430    private static readonly (Type Attribute, AuditRole Role)[] Attributes =
 431    [
 432        ( typeof( SoftDeleteFlagAttribute ), AuditRole.SoftDeleteFlag ), ( typeof( DeletedDateAttribute ), AuditRole.Del
 433        ( typeof( DeletedByIdAttribute ), AuditRole.DeletedById ), ( typeof( DeletedByUserAttribute ), AuditRole.Deleted
 434        ( typeof( CreatedDateAttribute ), AuditRole.CreatedDate ), ( typeof( CreatedByIdAttribute ), AuditRole.CreatedBy
 435        ( typeof( CreatedByUserAttribute ), AuditRole.CreatedByUser ), ( typeof( UpdatedDateAttribute ), AuditRole.Updat
 436        ( typeof( UpdatedByIdAttribute ), AuditRole.UpdatedById ), ( typeof( UpdatedByUserAttribute ), AuditRole.Updated
 437    ];
 38
 39    private readonly PropertyInfo?[] _properties;
 40
 41
 3242    private AuditMap( Type type, PropertyInfo?[] properties )
 43    {
 44        Type        = type;
 3245        _properties = properties;
 3246    }
 47
 48
 49    public Type Type { get; }
 50
 19651    public bool IsEmpty => _properties.All( p => p is null );
 52
 13253    public PropertyInfo? SoftDeleteFlag => this[ AuditRole.SoftDeleteFlag ];
 54
 126055    public PropertyInfo? this[ AuditRole role ] => _properties[ (int) role ];
 56
 57
 25658    public static AuditMap For( Type type ) => Cache.GetOrAdd( type, Build );
 59
 60
 61    private static AuditMap Build( Type type )
 62    {
 3263        var properties = new PropertyInfo?[ Enum.GetValues<AuditRole>().Length ];
 3264        var candidates = type.GetProperties( BindingFlags.Public | BindingFlags.Instance );
 65
 66
 50467        foreach ( var property in candidates )
 68        {
 484069            foreach ( var (attribute, role) in Attributes )
 70            {
 220071                if ( !Attribute.IsDefined( property, attribute, inherit: true ) ) { continue; }
 72
 4073                if ( properties[ (int) role ] is { } other && other.Name != property.Name )
 74                {
 075                    throw new InvalidOperationException( $"{type.Name} has two {role} properties: {other.Name} and {prop
 76                }
 77
 4078                properties[ (int) role ] = property;
 79            }
 80        }
 81
 82
 3283        if ( typeof( IBaseTrackableEntity ).IsAssignableFrom( type ) )
 84        {
 1685            Fill( AuditRole.CreatedDate, nameof( IBaseTrackableEntity.CreatedDate ) );
 1686            Fill( AuditRole.UpdatedDate, nameof( IBaseTrackableEntity.UpdatedDate ) );
 87        }
 88
 3289        if ( Implements( type, typeof( IBaseTransactionEntity<> ) ) )
 90        {
 491            Fill( AuditRole.CreatedById, nameof( IBaseTransactionEntity<int>.CreatedById ) );
 492            Fill( AuditRole.CreatedByUser, nameof( IBaseTransactionEntity<int>.CreatedByUser ) );
 493            Fill( AuditRole.UpdatedById, nameof( IBaseTransactionEntity<int>.UpdatedById ) );
 494            Fill( AuditRole.UpdatedByUser, nameof( IBaseTransactionEntity<int>.UpdatedByUser ) );
 95        }
 96
 3297        if ( Implements( type, typeof( IDeletableEntity<> ) ) )
 98        {
 1299            Fill( AuditRole.SoftDeleteFlag, nameof( IDeletableEntity<int>.IsDeleted ) );
 12100            Fill( AuditRole.DeletedDate, nameof( IDeletableEntity<int>.DeletedDate ) );
 12101            Fill( AuditRole.DeletedById, nameof( IDeletableEntity<int>.DeletedById ) );
 12102            Fill( AuditRole.DeletedByUser, nameof( IDeletableEntity<int>.DeletedByUser ) );
 103        }
 104
 105
 32106        var map = new AuditMap( type, properties );
 32107        map.Check();
 108
 32109        return map;
 110
 111
 112        // An attribute wins over the interface property of the same role.
 113        void Fill( AuditRole role, string name ) => properties[ (int) role ] ??= candidates.FirstOrDefault( p => p.Name 
 114    }
 115
 116    private static bool Implements( Type type, Type openInterface )
 64117        => type.GetInterfaces().Any( i => i.IsGenericType && i.GetGenericTypeDefinition() == openInterface );
 118
 119    private void Check()
 120    {
 32121        Require( AuditRole.SoftDeleteFlag, t => t == typeof( bool ), "bool" );
 122
 256123        foreach ( var role in new[] { AuditRole.CreatedDate, AuditRole.UpdatedDate, AuditRole.DeletedDate } )
 124        {
 96125            Require( role, t => t == typeof( DateTime ) || t == typeof( DateTime? ), "DateTime or DateTime?" );
 126        }
 127
 256128        foreach ( var role in new[] { AuditRole.CreatedByUser, AuditRole.UpdatedByUser, AuditRole.DeletedByUser } )
 129        {
 96130            Require( role, t => t == typeof( string ), "string" );
 131        }
 132
 133
 134        void Require( AuditRole role, Func<Type, bool> valid, string expected )
 135        {
 136            if ( this[ role ] is { } property && !valid( property.PropertyType ) )
 137            {
 138                throw new InvalidOperationException( $"{Type.Name}.{property.Name} ({role}) must be {expected}, not {pro
 139            }
 140        }
 32141    }
 142}