diff --git a/StronglyTypedId.sln b/StronglyTypedId.sln index 2056da679..f5e826793 100644 --- a/StronglyTypedId.sln +++ b/StronglyTypedId.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28803.352 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32602.215 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EE1258BD-3422-4F55-B9CF-B4D6C95DAD68}" EndProject @@ -15,19 +15,21 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution version.props = version.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StronglyTypedIds", "src\StronglyTypedIds\StronglyTypedIds.csproj", "{9C0F3A36-ED47-4D0F-B736-EFC559C9E2DA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StronglyTypedIds", "src\StronglyTypedIds\StronglyTypedIds.csproj", "{9C0F3A36-ED47-4D0F-B736-EFC559C9E2DA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StronglyTypedIds.Tests", "test\StronglyTypedIds.Tests\StronglyTypedIds.Tests.csproj", "{00B5ED3F-827D-41CD-9AF2-A9A20A6604E1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StronglyTypedIds.Tests", "test\StronglyTypedIds.Tests\StronglyTypedIds.Tests.csproj", "{00B5ED3F-827D-41CD-9AF2-A9A20A6604E1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StronglyTypedIds.IntegrationTests", "test\StronglyTypedIds.IntegrationTests\StronglyTypedIds.IntegrationTests.csproj", "{09F7364F-8CE9-4E9D-9BB7-B4CEBF682904}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StronglyTypedIds.IntegrationTests", "test\StronglyTypedIds.IntegrationTests\StronglyTypedIds.IntegrationTests.csproj", "{09F7364F-8CE9-4E9D-9BB7-B4CEBF682904}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{E13FB452-2D47-4719-8BAA-7B695D79AF3A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StronglyTypedIds.Attributes", "src\StronglyTypedIds.Attributes\StronglyTypedIds.Attributes.csproj", "{F25F6E67-E62A-4075-86CF-4C4EDD7E4883}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StronglyTypedIds.Attributes", "src\StronglyTypedIds.Attributes\StronglyTypedIds.Attributes.csproj", "{F25F6E67-E62A-4075-86CF-4C4EDD7E4883}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StronglyTypedIds.Nuget.IntegrationTests", "test\StronglyTypedIds.Nuget.IntegrationTests\StronglyTypedIds.Nuget.IntegrationTests.csproj", "{A7355210-7DDC-4968-84B7-79002113EA6E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StronglyTypedIds.Nuget.IntegrationTests", "test\StronglyTypedIds.Nuget.IntegrationTests\StronglyTypedIds.Nuget.IntegrationTests.csproj", "{A7355210-7DDC-4968-84B7-79002113EA6E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StronglyTypedIds.Nuget.Attributes.IntegrationTests", "test\StronglyTypedIds.Nuget.Attributes.IntegrationTests\StronglyTypedIds.Nuget.Attributes.IntegrationTests.csproj", "{19A9B323-8C0B-4D1B-A20C-8CECFFD37F23}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StronglyTypedIds.Nuget.Attributes.IntegrationTests", "test\StronglyTypedIds.Nuget.Attributes.IntegrationTests\StronglyTypedIds.Nuget.Attributes.IntegrationTests.csproj", "{19A9B323-8C0B-4D1B-A20C-8CECFFD37F23}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StronglyTypedIds.EFCore", "src\StronglyTypedIds.EFCore\StronglyTypedIds.EFCore.csproj", "{133967D3-7A93-41A1-8B29-0D916FB3E00F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,8 +41,6 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E13FB452-2D47-4719-8BAA-7B695D79AF3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E13FB452-2D47-4719-8BAA-7B695D79AF3A}.Release|Any CPU.ActiveCfg = Release|Any CPU {9C0F3A36-ED47-4D0F-B736-EFC559C9E2DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9C0F3A36-ED47-4D0F-B736-EFC559C9E2DA}.Debug|Any CPU.Build.0 = Debug|Any CPU {9C0F3A36-ED47-4D0F-B736-EFC559C9E2DA}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -77,6 +77,16 @@ Global {09F7364F-8CE9-4E9D-9BB7-B4CEBF682904}.Release|x64.Build.0 = Release|Any CPU {09F7364F-8CE9-4E9D-9BB7-B4CEBF682904}.Release|x86.ActiveCfg = Release|Any CPU {09F7364F-8CE9-4E9D-9BB7-B4CEBF682904}.Release|x86.Build.0 = Release|Any CPU + {E13FB452-2D47-4719-8BAA-7B695D79AF3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E13FB452-2D47-4719-8BAA-7B695D79AF3A}.Debug|x64.ActiveCfg = Debug|Any CPU + {E13FB452-2D47-4719-8BAA-7B695D79AF3A}.Debug|x64.Build.0 = Debug|Any CPU + {E13FB452-2D47-4719-8BAA-7B695D79AF3A}.Debug|x86.ActiveCfg = Debug|Any CPU + {E13FB452-2D47-4719-8BAA-7B695D79AF3A}.Debug|x86.Build.0 = Debug|Any CPU + {E13FB452-2D47-4719-8BAA-7B695D79AF3A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E13FB452-2D47-4719-8BAA-7B695D79AF3A}.Release|x64.ActiveCfg = Release|Any CPU + {E13FB452-2D47-4719-8BAA-7B695D79AF3A}.Release|x64.Build.0 = Release|Any CPU + {E13FB452-2D47-4719-8BAA-7B695D79AF3A}.Release|x86.ActiveCfg = Release|Any CPU + {E13FB452-2D47-4719-8BAA-7B695D79AF3A}.Release|x86.Build.0 = Release|Any CPU {F25F6E67-E62A-4075-86CF-4C4EDD7E4883}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F25F6E67-E62A-4075-86CF-4C4EDD7E4883}.Debug|Any CPU.Build.0 = Debug|Any CPU {F25F6E67-E62A-4075-86CF-4C4EDD7E4883}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -101,6 +111,18 @@ Global {19A9B323-8C0B-4D1B-A20C-8CECFFD37F23}.Release|Any CPU.ActiveCfg = Release|Any CPU {19A9B323-8C0B-4D1B-A20C-8CECFFD37F23}.Release|x64.ActiveCfg = Release|Any CPU {19A9B323-8C0B-4D1B-A20C-8CECFFD37F23}.Release|x86.ActiveCfg = Release|Any CPU + {133967D3-7A93-41A1-8B29-0D916FB3E00F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {133967D3-7A93-41A1-8B29-0D916FB3E00F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {133967D3-7A93-41A1-8B29-0D916FB3E00F}.Debug|x64.ActiveCfg = Debug|Any CPU + {133967D3-7A93-41A1-8B29-0D916FB3E00F}.Debug|x64.Build.0 = Debug|Any CPU + {133967D3-7A93-41A1-8B29-0D916FB3E00F}.Debug|x86.ActiveCfg = Debug|Any CPU + {133967D3-7A93-41A1-8B29-0D916FB3E00F}.Debug|x86.Build.0 = Debug|Any CPU + {133967D3-7A93-41A1-8B29-0D916FB3E00F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {133967D3-7A93-41A1-8B29-0D916FB3E00F}.Release|Any CPU.Build.0 = Release|Any CPU + {133967D3-7A93-41A1-8B29-0D916FB3E00F}.Release|x64.ActiveCfg = Release|Any CPU + {133967D3-7A93-41A1-8B29-0D916FB3E00F}.Release|x64.Build.0 = Release|Any CPU + {133967D3-7A93-41A1-8B29-0D916FB3E00F}.Release|x86.ActiveCfg = Release|Any CPU + {133967D3-7A93-41A1-8B29-0D916FB3E00F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -112,6 +134,7 @@ Global {F25F6E67-E62A-4075-86CF-4C4EDD7E4883} = {EE1258BD-3422-4F55-B9CF-B4D6C95DAD68} {A7355210-7DDC-4968-84B7-79002113EA6E} = {D1907D86-8FFC-4178-A3DB-0ADBDD282C64} {19A9B323-8C0B-4D1B-A20C-8CECFFD37F23} = {D1907D86-8FFC-4178-A3DB-0ADBDD282C64} + {133967D3-7A93-41A1-8B29-0D916FB3E00F} = {EE1258BD-3422-4F55-B9CF-B4D6C95DAD68} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8D1F0534-B8AD-4CFA-9C14-CBC757BCB1E1} diff --git a/src/StronglyTypedIds.EFCore/Extensions.cs b/src/StronglyTypedIds.EFCore/Extensions.cs new file mode 100644 index 000000000..03f351c9d --- /dev/null +++ b/src/StronglyTypedIds.EFCore/Extensions.cs @@ -0,0 +1,86 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace StronglyTypedIds.EFCore; + +public static class Extensions +{ + public static void RegisterStrongTypedIdDynamically( + this ModelBuilder modelBuilder, + IMutableEntityType entityData, + DbContext context, + string idPropertyName = "Id" + ) + { + var entityMethod = typeof(ModelBuilder).GetMethods() + .First(m => m.IsGenericMethod && m.Name == (nameof(ModelBuilder.Entity))) + !.MakeGenericMethod(entityData.ClrType); + var propertyMethod = typeof(EntityTypeBuilder).GetMethods() + .First(m => !m.IsGenericMethod && m.Name == nameof(EntityTypeBuilder.Property)); + var hasConversionMethod = typeof(PropertyBuilder).GetMethods() + .First(m => !m.IsGenericMethod && m.Name == nameof(PropertyBuilder.HasConversion)); + + var entityTypeBuilder = entityMethod.Invoke(modelBuilder, Array.Empty()) as EntityTypeBuilder; + var propertyBuilder = propertyMethod!.Invoke(entityTypeBuilder, new[] { idPropertyName }) as PropertyBuilder; + + var idProperty = entityData.GetProperty(idPropertyName); + var idType = idProperty.ClrType; + var converterType = idType.GetNestedType("EfCoreValueConverter"); + + if (converterType is null) + { + return; + } + + hasConversionMethod!.Invoke(propertyBuilder, new[] { converterType }); + + var realIdType = idType.GetProperty("Value")?.PropertyType; + + if (new[] { typeof(int), typeof(long), typeof(short) }.Contains(realIdType)) + { + Type? propertyBuilderExtensionType = null; + var propertyBuilderExtensionTypes = ReflectionHelper.GetAllTypesEndsWith("PropertyBuilderExtensions"); + + if (propertyBuilderExtensionTypes is null || !propertyBuilderExtensionTypes.Any()) + { + return; + } + + if (propertyBuilderExtensionTypes?.Count() == 1) + { + propertyBuilderExtensionType = propertyBuilderExtensionTypes.First(); + } + else + { + propertyBuilderExtensionType = propertyBuilderExtensionTypes!.First(x => x.AssemblyQualifiedName!.Contains(context.Database.ProviderName!)); + } + + if (propertyBuilderExtensionType is null) + { + return; + } + + var useIdentityColumnMethod = propertyBuilderExtensionType.GetMethods() + .OrderBy(m => m.GetParameters().Length) + .First(m => !m.IsGenericMethod && m.Name == "UseIdentityColumn"); + + var useIdentityColumnParameters = new List() { propertyBuilder! }; + switch (context.Database.ProviderName) + { + case "Npgsql.EntityFrameworkCore.PostgreSQL": + break; + + case "Microsoft.EntityFrameworkCore.SqlServer": + useIdentityColumnParameters.Add(1); + useIdentityColumnParameters.Add(1); + break; + + case "Pomelo.EntityFrameworkCore.MySql": + case "MySql.EntityFrameworkCore": + break; + } + useIdentityColumnMethod!.Invoke(null, useIdentityColumnParameters.ToArray()); + } + } +} \ No newline at end of file diff --git a/src/StronglyTypedIds.EFCore/ReflectionHelper.cs b/src/StronglyTypedIds.EFCore/ReflectionHelper.cs new file mode 100644 index 000000000..1cd051388 --- /dev/null +++ b/src/StronglyTypedIds.EFCore/ReflectionHelper.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace StronglyTypedIds.EFCore +{ + public static class ReflectionHelper + { + public static IEnumerable? GetAllInheritedTypes() where T : class + { + return Assembly.GetAssembly(typeof(T)) + ?.GetTypes() + .Where(t => t.IsClass && + !t.IsAbstract && + typeof(T).IsAssignableFrom(t)); + } + + public static IEnumerable? GetAllInheritedTypesForGeneric(Type type) + { + return Assembly.GetAssembly(type) + ?.GetTypes() + .Where(t => t.IsClass && + !t.IsAbstract && + IsAssignableToGenericType(t, type)); + } + + public static IEnumerable? GetAllTypesEndsWith(string endsWith) + { + return AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => a.GetTypes()) + .Where(t => t.IsClass + && t.IsSealed + && t.IsAbstract + && t.Name.EndsWith(endsWith)); + } + + public static bool IsAssignableToGenericType(Type givenType, Type genericType) + { + var interfaceTypes = givenType.GetInterfaces(); + + foreach (var it in interfaceTypes) + { + if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType) + return true; + } + + if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) + return true; + + var baseType = givenType.BaseType; + if (baseType is null) return false; + + return IsAssignableToGenericType(baseType, genericType); + } + } +} diff --git a/src/StronglyTypedIds.EFCore/StronglyTypedIds.EFCore.csproj b/src/StronglyTypedIds.EFCore/StronglyTypedIds.EFCore.csproj new file mode 100644 index 000000000..e68b48588 --- /dev/null +++ b/src/StronglyTypedIds.EFCore/StronglyTypedIds.EFCore.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + +