From 011e24d2cee734dcc5731bb0790db5c1f6199c95 Mon Sep 17 00:00:00 2001 From: "David G. Moore, Jr." Date: Tue, 20 Feb 2024 19:01:32 -0500 Subject: [PATCH] Add new utility features and refactorings Introduced Humanizer dependency for enhanced text manipulation capabilities in shared build configuration. Commented out Dgmjr.InterfaceGenerator references in Abstraction project, indicating a potential shift in code generation strategy or removal of dependency. Prepared IDbContext interface for future feature through commented interface generation directive. Implemented a DesignTimeDbContextFactory with environment-specific configuration and Azure SQL support for streamlined database context creation during design time. Additionally, a Slug value object, complete with comparison functionality and JSON/EF Core converters, was introduced to provide standardized identity handling across models. Removed version override for Dgmjr.System.Extensions, signaling a move to rely on the package's default available version for better compatibility or maintainability. Lastly, laid groundwork for CreateViewOperation in Migrations, which suggests upcoming migration operation support, albeit the feature remains commented out for future development. Note: The rationale behind commenting out code rather than removal hints at temporary suspensions for possible reevaluation. References: #12345 (if applicable) --- Directory.Build.props | 3 + ...jr.EntityFrameworkCore.Abstractions.csproj | 2 +- src/Abstractions/IDbContext.cs | 3 + src/Extensions/DesignTimeDbContextFactory.cs | 63 ++++++++++++++++ ...gmjr.EntityFrameworkCore.Extensions.csproj | 2 +- src/Migrations/CreateViewOperation.cs | 11 +++ .../Dgmjr.EntityFrameworkCore.Models.csproj | 1 + src/Models/Slug.cs | 72 +++++++++++++++++++ 8 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 src/Extensions/DesignTimeDbContextFactory.cs create mode 100644 src/Migrations/CreateViewOperation.cs create mode 100644 src/Models/Slug.cs diff --git a/Directory.Build.props b/Directory.Build.props index 9aacfc7..8fd10af 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,4 +4,7 @@ $([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../')) + + + diff --git a/src/Abstractions/Dgmjr.EntityFrameworkCore.Abstractions.csproj b/src/Abstractions/Dgmjr.EntityFrameworkCore.Abstractions.csproj index ce077a0..a28a1ef 100644 --- a/src/Abstractions/Dgmjr.EntityFrameworkCore.Abstractions.csproj +++ b/src/Abstractions/Dgmjr.EntityFrameworkCore.Abstractions.csproj @@ -37,7 +37,7 @@ - + diff --git a/src/Abstractions/IDbContext.cs b/src/Abstractions/IDbContext.cs index 00e997a..7623cab 100644 --- a/src/Abstractions/IDbContext.cs +++ b/src/Abstractions/IDbContext.cs @@ -14,6 +14,9 @@ namespace Microsoft.EntityFrameworkCore.Abstractions; using Microsoft.EntityFrameworkCore; +// [GenerateInterface(typeof(DbContext))] +// public partial interface IDbContext { } + /// An abstraction (interface) for a . public partial interface IDbContext //: IAsyncDisposable { diff --git a/src/Extensions/DesignTimeDbContextFactory.cs b/src/Extensions/DesignTimeDbContextFactory.cs new file mode 100644 index 0000000..fc54452 --- /dev/null +++ b/src/Extensions/DesignTimeDbContextFactory.cs @@ -0,0 +1,63 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.Extensions.Configuration; +using Microsoft.EntityFrameworkCore.SqlServer; + +namespace Microsoft.EntityFrameworkCore.Design; + +public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory + where TContext : DbContext +{ + public virtual IConfigurationBuilder Configure(IConfigurationBuilder configBuilder) + { + return configBuilder + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile( + $"appsettings.{env.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", + optional: true + ) + .AddEnvironmentVariables() + .AddUserSecrets() + .AddUserSecrets(GetType().Assembly, optional: true, reloadOnChange: true); + } + + public virtual SqlServerDbContextOptionsBuilder Configure( + SqlServerDbContextOptionsBuilder optionsBuilder + ) + { + #if NET8_0_OR_GREATER + return optionsBuilder = optionsBuilder.UseAzureSqlDefaults(); + #endif + return optionsBuilder; + } + + protected virtual string GetConnectionString(IConfiguration configuration) + { + return configuration.GetConnectionString(typeof(TContext).Name.Replace("Context", "")) + ?? configuration.GetConnectionString( + typeof(TContext).Name.Replace(nameof(DbContext), "") + ); + } + + protected virtual DbContextOptionsBuilder Configure(DbContextOptionsBuilder builder) + { + return builder; + } + + public virtual TContext CreateDbContext(string[] args) + { + var configuration = Configure(new ConfigurationBuilder()).AddCommandLine(args).Build(); + + return (Activator.CreateInstance( + typeof(TContext), + Configure(new DbContextOptionsBuilder() + .UseSqlServer( + GetConnectionString(configuration) + ?? configuration.GetConnectionString("DefaultConnection") + ?? "Server=(localdb)\\mssqllocaldb;Database=EFProviders.InMemory;Trusted_Connection=True;MultipleActiveResultSets=true", + x => Configure(x) + )) + .Options + ) as TContext)!; + } +} diff --git a/src/Extensions/Dgmjr.EntityFrameworkCore.Extensions.csproj b/src/Extensions/Dgmjr.EntityFrameworkCore.Extensions.csproj index 97cce03..652a387 100644 --- a/src/Extensions/Dgmjr.EntityFrameworkCore.Extensions.csproj +++ b/src/Extensions/Dgmjr.EntityFrameworkCore.Extensions.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/Migrations/CreateViewOperation.cs b/src/Migrations/CreateViewOperation.cs new file mode 100644 index 0000000..c5f8bbb --- /dev/null +++ b/src/Migrations/CreateViewOperation.cs @@ -0,0 +1,11 @@ +// namespace Dgmjr.EntityFrameworkCore.Migrations; + +// public class CreateViewOperation(string schema, string name, params Column[] columns) : SqlOperation +// { +// public string Schema { get; set; } = schema; +// public string Name { get; set; } = name; +// public Column[] Columns { get; set; } = columns; +// public Expression +// } + +// public record struct Column(string DataType, string SourceTable, string ColumnName); diff --git a/src/Models/Dgmjr.EntityFrameworkCore.Models.csproj b/src/Models/Dgmjr.EntityFrameworkCore.Models.csproj index 46d0c29..925e88b 100644 --- a/src/Models/Dgmjr.EntityFrameworkCore.Models.csproj +++ b/src/Models/Dgmjr.EntityFrameworkCore.Models.csproj @@ -21,6 +21,7 @@ + diff --git a/src/Models/Slug.cs b/src/Models/Slug.cs new file mode 100644 index 0000000..91b7e14 --- /dev/null +++ b/src/Models/Slug.cs @@ -0,0 +1,72 @@ +namespace Dgmjr.EntityFrameworkCore; +using static Vogen.Conversions; +using Vogen; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +public readonly partial struct Slug(string Value) : IComparable, IEquatable, IComparable +{ + public Slug() + : this(NewSlug()) { } + + public string Value { get; init; } = Value; + + public int CompareTo(Slug other) + => Compare(Value, other.Value, OrdinalIgnoreCase); + + public int CompareTo(object obj) + => obj is Slug other ? CompareTo(other) : 1; + + public static string NewSlug() + => guid.NewGuid().ToString("N").Substring(0, 6).ToLowerInvariant(); + + public override string ToString() + => Value; + + public bool Equals(Slug other) + => CompareTo(other) == 0; + + public override bool Equals(object? obj) + => obj is Slug other && Equals(other); + + public static bool operator==(Slug left, Slug right) + => left.Equals(right); + + public static bool operator!=(Slug left, Slug right) + => !left.Equals(right); +} + +public class SlugEfCoreConverter : ValueConverter +{ + public SlugEfCoreConverter() : this(null) { } + public SlugEfCoreConverter(ConverterMappingHints? mappingHints = null) + : base(v => v.Value, v => v.ToSlug(), mappingHints) { } +} + +public class SlugJsonConverter : JsonConverter +{ + public override Slug Read(ref Utf8JsonReader reader, type typeToConvert, Jso options) + => new(reader.GetString()); + + public override void Write(Utf8JsonWriter writer, Slug value, Jso options) + => writer.WriteStringValue(value.Value); +} + +public class SlugTypeConfiguration : IEntityTypeConfiguration + where TEntity : class, IIdentifiable +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(e => e.Id); + builder.Property(e => e.Id).ValueGeneratedNever(); + builder.Property(e => e.Id).HasConversion(v => v.Value, v => v.ToSlug()); + } +} + +public static class SlugExtensions +{ + public static Slug ToSlug(this string value) + => new(value); + + public static Slug NewSlug() + => new(); +}