| | | 1 | | using System.Reflection; |
| | | 2 | | using Microsoft.EntityFrameworkCore.Metadata; |
| | | 3 | | using PhoneNumbers; |
| | | 4 | | |
| | | 5 | | namespace ZhEaIsNsAaBn.Infrastructure.Core; |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// Model conventions shared by <see cref="BaseDbContext{TAppDbContext}"/> and the Identity variants. Provider-neutral: |
| | | 9 | | /// Unicode and precision are facets; default-value SQL comes from the context's <see cref="IModelDefaultsProvider"/>. |
| | | 10 | | /// </summary> |
| | | 11 | | internal static class BaseDbContextConfiguration |
| | | 12 | | { |
| | | 13 | | private const int EnumLength = 50; |
| | | 14 | | private const int ULongLength = 20; |
| | | 15 | | |
| | | 16 | | |
| | | 17 | | internal static void OnModelCreating( ModelBuilder modelBuilder, Action<ModelBuilder> customConfiguration, Assembly |
| | | 18 | | { |
| | 44 | 19 | | ConfigureBaseEntityDefaults( modelBuilder, defaults?.CreatedDateDefaultSql ); |
| | 44 | 20 | | ConfigureDecimalProperties( modelBuilder ); |
| | 44 | 21 | | ConfigureDeleteBehaviors( modelBuilder ); |
| | | 22 | | |
| | 44 | 23 | | customConfiguration( modelBuilder ); |
| | 44 | 24 | | modelBuilder.ApplyConfigurationsFromAssembly( configurationAssembly ); |
| | | 25 | | |
| | 44 | 26 | | ConfigureOwnershipDeleteBehaviors( modelBuilder ); |
| | 44 | 27 | | ConfigurePhoneNumberProperties( modelBuilder ); |
| | 44 | 28 | | ConfigureStatus( modelBuilder ); |
| | 44 | 29 | | ConfigureGuidId( modelBuilder, defaults?.GuidKeyDefaultSql ); |
| | 44 | 30 | | } |
| | | 31 | | |
| | | 32 | | internal static void ConfigureEnumsProperties( ModelBuilder modelBuilder ) |
| | | 33 | | { |
| | 64 | 34 | | foreach ( var property in modelBuilder.Model.GetEntityTypes().SelectMany( t => t.GetProperties() ).Where( p => p |
| | | 35 | | { |
| | | 36 | | // Stored as the enum name: Unicode text of 50 (nvarchar(50) on SQL Server). |
| | 16 | 37 | | property.SetProviderClrType( typeof( string ) ); |
| | 16 | 38 | | property.SetMaxLength( EnumLength ); |
| | 16 | 39 | | property.SetIsUnicode( true ); |
| | | 40 | | } |
| | 16 | 41 | | } |
| | | 42 | | |
| | | 43 | | internal static void ConfigureUlongProperties( ModelBuilder modelBuilder ) |
| | | 44 | | { |
| | 96 | 45 | | foreach ( var property in modelBuilder.Model.GetEntityTypes() |
| | 16 | 46 | | .SelectMany( t => t.GetProperties() ) |
| | 16 | 47 | | .Where( p => p.ClrType == typeof( ulong ) || p.ClrType == typeof( ulong? ) |
| | | 48 | | { |
| | 32 | 49 | | property.SetMaxLength( ULongLength ); |
| | 32 | 50 | | property.SetIsUnicode( true ); |
| | 32 | 51 | | property.SetValueConverter( new ULongStringConverter() ); |
| | | 52 | | } |
| | 16 | 53 | | } |
| | | 54 | | |
| | | 55 | | private static void ConfigurePhoneNumberProperties( ModelBuilder modelBuilder ) |
| | | 56 | | { |
| | 88 | 57 | | foreach ( var property in modelBuilder.Model.GetEntityTypes() |
| | 44 | 58 | | .SelectMany( t => t.GetProperties() ) |
| | 44 | 59 | | .Where( p => p.ClrType == typeof( PhoneNumber ) ) ) |
| | | 60 | | { |
| | 0 | 61 | | property.SetValueConverter( property.IsNullable ? typeof( NullablePhoneNumberConverter ) : typeof( PhoneNumb |
| | | 62 | | } |
| | | 63 | | |
| | | 64 | | |
| | 44 | 65 | | modelBuilder.Ignore<PhoneNumber>(); |
| | 44 | 66 | | } |
| | | 67 | | |
| | | 68 | | private static void ConfigureBaseEntityDefaults( ModelBuilder modelBuilder, string? createdDateDefaultSql ) |
| | | 69 | | { |
| | 432 | 70 | | foreach ( var entity in modelBuilder.Model.GetEntityTypes() ) |
| | | 71 | | { |
| | 172 | 72 | | if ( entity.FindProperty( nameof( IBaseTrackableEntity.CreatedDate ) ) is { } createdDate ) |
| | | 73 | | { |
| | 96 | 74 | | createdDate.IsNullable = false; |
| | | 75 | | |
| | | 76 | | // The database fills it only when the provider supplies the SQL; otherwise the app sets it. |
| | 96 | 77 | | if ( createdDateDefaultSql is null ) { continue; } |
| | | 78 | | |
| | | 79 | | |
| | 76 | 80 | | createdDate.ValueGenerated = ValueGenerated.OnAdd; |
| | 76 | 81 | | createdDate.SetDefaultValueSql( createdDateDefaultSql ); |
| | | 82 | | } |
| | | 83 | | } |
| | 44 | 84 | | } |
| | | 85 | | |
| | | 86 | | private static void ConfigureDecimalProperties( ModelBuilder modelBuilder ) |
| | | 87 | | { |
| | 160 | 88 | | foreach ( var property in modelBuilder.Model.GetEntityTypes() |
| | 44 | 89 | | .SelectMany( t => t.GetProperties() ) |
| | 44 | 90 | | .Where( p => p.ClrType == typeof( decimal ) || p.ClrType == typeof( decima |
| | | 91 | | { |
| | 36 | 92 | | property.SetPrecision( 18 ); |
| | 36 | 93 | | property.SetScale( 2 ); |
| | | 94 | | } |
| | 44 | 95 | | } |
| | | 96 | | |
| | | 97 | | private static void ConfigureDeleteBehaviors( ModelBuilder modelBuilder ) |
| | | 98 | | { |
| | 296 | 99 | | foreach ( var foreignKey in modelBuilder.Model.GetEntityTypes().SelectMany( e => e.GetForeignKeys() ) ) |
| | | 100 | | { |
| | 104 | 101 | | foreignKey.DeleteBehavior = DeleteBehavior.Restrict; |
| | | 102 | | } |
| | 44 | 103 | | } |
| | | 104 | | |
| | | 105 | | /// An owned entity goes with its owner: a Restrict ownership makes <c>Remove( owner )</c> throw. ClientCascade dele |
| | | 106 | | /// entries and keeps the database foreign key at NO ACTION, so the schema (and the migration diff) doesn't change. |
| | | 107 | | private static void ConfigureOwnershipDeleteBehaviors( ModelBuilder modelBuilder ) |
| | | 108 | | { |
| | 112 | 109 | | foreach ( var foreignKey in modelBuilder.Model.GetEntityTypes() |
| | 44 | 110 | | .SelectMany( e => e.GetForeignKeys() ) |
| | 44 | 111 | | .Where( k => k.IsOwnership && k.DeleteBehavior == DeleteBehavior.Restric |
| | | 112 | | { |
| | 12 | 113 | | foreignKey.DeleteBehavior = DeleteBehavior.ClientCascade; |
| | | 114 | | } |
| | 44 | 115 | | } |
| | | 116 | | |
| | | 117 | | private static void ConfigureGuidId( ModelBuilder modelBuilder, string? guidKeyDefaultSql ) |
| | | 118 | | { |
| | 56 | 119 | | if ( guidKeyDefaultSql is null ) { return; } |
| | | 120 | | |
| | | 121 | | |
| | 80 | 122 | | foreach ( var property in modelBuilder.Model.GetEntityTypes() |
| | 32 | 123 | | .Select( e => e.FindPrimaryKey() ) |
| | 32 | 124 | | .OfType<IMutableKey>() |
| | 32 | 125 | | .SelectMany( k => k.Properties ) |
| | 32 | 126 | | .Where( p => p.ClrType == typeof( Guid ) ) ) |
| | | 127 | | { |
| | 8 | 128 | | property.SetDefaultValueSql( guidKeyDefaultSql ); |
| | | 129 | | } |
| | 32 | 130 | | } |
| | | 131 | | |
| | | 132 | | private static void ConfigureStatus( ModelBuilder modelBuilder ) |
| | | 133 | | { |
| | 504 | 134 | | foreach ( var entity in modelBuilder.Model.GetEntityTypes() ) |
| | | 135 | | { |
| | 208 | 136 | | entity.FindProperty( nameof( IStatusEntity.StatusUser ) )?.SetMaxLength( 50 ); |
| | | 137 | | } |
| | 44 | 138 | | } |
| | | 139 | | } |