using System.Linq.Expressions; using BuildingBlocks.Core.Model; using BuildingBlocks.Web; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Query; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace BuildingBlocks.EFCore; using Ardalis.GuardClauses; using Humanizer; using Microsoft.EntityFrameworkCore.Metadata; public static class Extensions { public static IServiceCollection AddCustomDbContext( this IServiceCollection services) where TContext : DbContext, IDbContext { AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); services.AddValidateOptions(); services.AddDbContext((sp, options) => { var postgresOptions = sp.GetRequiredService(); Guard.Against.Null(options, nameof(postgresOptions)); options.UseNpgsql(postgresOptions?.ConnectionString, dbOptions => { dbOptions.MigrationsAssembly(typeof(TContext).Assembly.GetName().Name); //ref: https://learn.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency // dbOptions.EnableRetryOnFailure(3, TimeSpan.FromSeconds(1), null); }) // https://github.com/efcore/EFCore.NamingConventions .UseSnakeCaseNamingConvention(); }); services.AddScoped(provider => provider.GetService()); return services; } public static IApplicationBuilder UseMigration(this IApplicationBuilder app, IWebHostEnvironment env) where TContext : DbContext, IDbContext { MigrateDatabaseAsync(app.ApplicationServices).GetAwaiter().GetResult(); if (!env.IsEnvironment("test")) { SeedDataAsync(app.ApplicationServices).GetAwaiter().GetResult(); } return app; } // ref: https://github.com/pdevito3/MessageBusTestingInMemHarness/blob/main/RecipeManagement/src/RecipeManagement/Databases/RecipesDbContext.cs public static void FilterSoftDeletedProperties(this ModelBuilder modelBuilder) { Expression> filterExpr = e => !e.IsDeleted; foreach (var mutableEntityType in modelBuilder.Model.GetEntityTypes() .Where(m => m.ClrType.IsAssignableTo(typeof(IEntity)))) { // modify expression to handle correct child type var parameter = Expression.Parameter(mutableEntityType.ClrType); var body = ReplacingExpressionVisitor .Replace(filterExpr.Parameters.First(), parameter, filterExpr.Body); var lambdaExpression = Expression.Lambda(body, parameter); // set filter mutableEntityType.SetQueryFilter(lambdaExpression); } } //ref: https://andrewlock.net/customising-asp-net-core-identity-ef-core-naming-conventions-for-postgresql/ public static void ToSnakeCaseTables(this ModelBuilder modelBuilder) { foreach (var entity in modelBuilder.Model.GetEntityTypes()) { // Replace table names entity.SetTableName(entity.GetTableName()?.Underscore()); var tableObjectIdentifier = StoreObjectIdentifier.Table(entity.GetTableName()?.Underscore()!, entity.GetSchema()); // Replace column names foreach (var property in entity.GetProperties()) { property.SetColumnName(property.GetColumnName(tableObjectIdentifier)?.Underscore()); } foreach (var key in entity.GetKeys()) { key.SetName(key.GetName()?.Underscore()); } foreach (var key in entity.GetForeignKeys()) { key.SetConstraintName(key.GetConstraintName()?.Underscore()); } } } private static async Task MigrateDatabaseAsync(IServiceProvider serviceProvider) where TContext : DbContext, IDbContext { using var scope = serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); await context.Database.MigrateAsync(); } private static async Task SeedDataAsync(IServiceProvider serviceProvider) { using var scope = serviceProvider.CreateScope(); var seeders = scope.ServiceProvider.GetServices(); foreach (var seeder in seeders) { await seeder.SeedAllAsync(); } } }