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(); +}