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

Information
Line coverage
98%
Covered lines: 62
Uncovered lines: 1
Coverable lines: 63
Total lines: 157
Line coverage: 98.4%
Branch coverage
93%
Covered branches: 59
Total branches: 63
Branch coverage: 93.6%
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_Item(...)100%11100%
Resolve(...)92.1%383896.66%
Implements(...)100%11100%
Check(...)96%2525100%

File(s)

/home/runner/work/ZhEaIsNsAaBn.Libraries.V1/ZhEaIsNsAaBn.Libraries.V1/src/DataBasePackages/ZhEaIsNsAaBn.DbSeeder/Configuration/SeedEntityRoles.cs

#LineLine coverage
 1namespace ZhEaIsNsAaBn.DbSeeder;
 2
 3/// <summary>
 4/// The resolved seed behaviour of one CLR entity type, per field in this order: 1. seed configuration → 2. Domain attri
 5/// 3. Domain interfaces → 4. nothing (no managed field; deletes are real, FK-checked).
 6/// </summary>
 7internal sealed class SeedEntityRoles
 8{
 109    private static readonly (Type Attribute, SeedRole Role)[] RoleAttributes =
 1010    [
 1011        ( typeof( SoftDeleteFlagAttribute ), SeedRole.SoftDeleteFlag ), ( typeof( DeletedDateAttribute ), SeedRole.Delet
 1012        ( typeof( DeletedByIdAttribute ), SeedRole.DeletedById ), ( typeof( DeletedByUserAttribute ), SeedRole.DeletedBy
 1013        ( typeof( CreatedDateAttribute ), SeedRole.CreatedDate ), ( typeof( CreatedByIdAttribute ), SeedRole.CreatedById
 1014        ( typeof( CreatedByUserAttribute ), SeedRole.CreatedByUser ), ( typeof( UpdatedDateAttribute ), SeedRole.Updated
 1015        ( typeof( UpdatedByIdAttribute ), SeedRole.UpdatedById ), ( typeof( UpdatedByUserAttribute ), SeedRole.UpdatedBy
 1016    ];
 17
 18
 193319    private SeedEntityRoles(
 193320            Type type,
 193321            IReadOnlyDictionary<SeedRole, string> roles,
 193322            IReadOnlySet<string> ignored,
 193323            IReadOnlyList<string>? matchBy,
 193324            SeedDeleteMode deleteMode,
 193325            IReadOnlyList<string> warnings
 193326        )
 27    {
 28        Type       = type;
 29        Roles      = roles;
 30        Ignored    = ignored;
 31        MatchBy    = matchBy;
 32        DeleteMode = deleteMode;
 33        Warnings   = warnings;
 193334    }
 35
 36
 37    public Type Type { get; }
 38
 39    /// Managed field → property name.
 40    public IReadOnlyDictionary<SeedRole, string> Roles { get; }
 41
 42    /// <c>Ignore(…)</c> plus <c>[SeedIgnore]</c>.
 43    public IReadOnlySet<string> Ignored { get; }
 44
 45    /// The configured business key; <see langword="null"/> for the primary key.
 46    public IReadOnlyList<string>? MatchBy { get; }
 47
 48    public SeedDeleteMode DeleteMode { get; }
 49
 50    /// "Set twice" warnings from the configuration (shown by the CLI).
 51    public IReadOnlyList<string> Warnings { get; }
 52
 53
 2154    public string? this[ SeedRole role ] => Roles.GetValueOrDefault( role );
 55
 56
 57    /// <summary>Resolves the roles of <paramref name="type"/>.</summary>
 58    /// <exception cref="InvalidOperationException">Two properties claim one role, one property claims two roles, or a p
 59    public static SeedEntityRoles Resolve( Type type, SeedEntitySettings? settings )
 60    {
 193961        var properties = type.GetProperties( BindingFlags.Public | BindingFlags.Instance );
 193962        var attributed = new Dictionary<SeedRole, string>();
 193963        var ignored    = new HashSet<string>( StringComparer.Ordinal );
 64
 65
 66        // 2. Attributes.
 2909067        foreach ( var property in properties )
 68        {
 1261569            if ( Attribute.IsDefined( property, typeof( SeedIgnoreAttribute ), inherit: true ) ) { ignored.Add( property
 70
 2524871            foreach ( var (attribute, role) in RoleAttributes.Where( a => Attribute.IsDefined( property, a.Attribute, in
 72            {
 1873                if ( attributed.TryGetValue( role, out var other ) && other != property.Name )
 74                {
 075                    throw new InvalidOperationException( $"{type.Name} has two {role} properties: {other} and {property.
 76                }
 77
 1878                attributed[ role ] = property.Name;
 79            }
 80        }
 81
 82
 83        // 3. Interfaces.
 193984        var interfaced = new Dictionary<SeedRole, string>();
 85
 193986        if ( typeof( IBaseTrackableEntity ).IsAssignableFrom( type ) )
 87        {
 101188            interfaced[ SeedRole.CreatedDate ] = nameof( IBaseTrackableEntity.CreatedDate );
 101189            interfaced[ SeedRole.UpdatedDate ] = nameof( IBaseTrackableEntity.UpdatedDate );
 90        }
 91
 193992        if ( Implements( type, typeof( IBaseTransactionEntity<> ) ) )
 93        {
 52594            interfaced[ SeedRole.CreatedById ]   = nameof( IBaseTransactionEntity<int>.CreatedById );
 52595            interfaced[ SeedRole.CreatedByUser ] = nameof( IBaseTransactionEntity<int>.CreatedByUser );
 52596            interfaced[ SeedRole.UpdatedById ]   = nameof( IBaseTransactionEntity<int>.UpdatedById );
 52597            interfaced[ SeedRole.UpdatedByUser ] = nameof( IBaseTransactionEntity<int>.UpdatedByUser );
 98        }
 99
 1939100        if ( Implements( type, typeof( IDeletableEntity<> ) ) )
 101        {
 525102            interfaced[ SeedRole.SoftDeleteFlag ] = nameof( IDeletableEntity<int>.IsDeleted );
 525103            interfaced[ SeedRole.DeletedDate ]    = nameof( IDeletableEntity<int>.DeletedDate );
 525104            interfaced[ SeedRole.DeletedById ]    = nameof( IDeletableEntity<int>.DeletedById );
 525105            interfaced[ SeedRole.DeletedByUser ]  = nameof( IDeletableEntity<int>.DeletedByUser );
 106        }
 107
 108
 109        // 1. Configuration wins, then attributes, then interfaces.
 1939110        var roles = new Dictionary<SeedRole, string>();
 111
 42658112        foreach ( var role in Enum.GetValues<SeedRole>() )
 113        {
 19390114            var name = settings?.Roles.GetValueOrDefault( role ) ?? attributed.GetValueOrDefault( role ) ?? interfaced.G
 115
 25621116            if ( name is not null ) { roles[ role ] = name; }
 117        }
 118
 1970119        if ( settings is not null ) { ignored.UnionWith( settings.Ignored ); }
 120
 121
 1939122        Check( type, properties, roles );
 123
 1933124        return new SeedEntityRoles( type, roles, ignored, settings?.MatchBy, settings?.DeleteMode ?? SeedDeleteMode.Auto
 125    }
 126
 127
 128    private static bool Implements( Type type, Type openInterface )
 3878129        => type.GetInterfaces().Any( i => i.IsGenericType && i.GetGenericTypeDefinition() == openInterface );
 130
 131    private static void Check( Type type, PropertyInfo[] properties, Dictionary<SeedRole, string> roles )
 132    {
 3881133        foreach ( var group in roles.GroupBy( r => r.Value, StringComparer.Ordinal ).Where( g => g.Count() > 1 ) )
 134        {
 3135            throw new InvalidOperationException( $"{type.Name}.{group.Key} plays more than one seed role: {string.Join( 
 136        }
 137
 138
 16313139        foreach ( var (role, name) in roles )
 140        {
 6222141            var propertyType = properties.FirstOrDefault( p => p.Name == name )?.PropertyType;
 142
 6222143            if ( propertyType is null ) { continue; }   // a shadow property; the EF model checks it
 144
 145
 6222146            var valid = role switch
 6222147                            {
 534148                                SeedRole.SoftDeleteFlag                                                  => propertyType
 2538149                                SeedRole.CreatedDate or SeedRole.UpdatedDate or SeedRole.DeletedDate       => propertyTy
 1575150                                SeedRole.CreatedByUser or SeedRole.UpdatedByUser or SeedRole.DeletedByUser => propertyTy
 1575151                                _                                                                        => ( Nullable.G
 6222152                            };
 153
 6225154            if ( !valid ) { throw new InvalidOperationException( $"{type.Name}.{name} can't be the {role} field: its typ
 155        }
 1933156    }
 157}