├── EntityFramework.PrimaryKey ├── packages.config ├── App.config ├── Properties │ └── AssemblyInfo.cs └── EntityFramework.PrimaryKey.csproj ├── Tests ├── packages.config ├── App.config └── Tests.csproj ├── LICENSE ├── TestsCore ├── TestsCore.csproj └── Tests.cs ├── EntityFrameworkCore.PrimaryKey ├── Extensions.cs ├── EntityFrameworkCore.PrimaryKey.csproj ├── PrimaryKeyDictionary.cs └── PrimaryKey.cs ├── README.md ├── EntityFramework.PrimaryKey.sln └── .gitignore /EntityFramework.PrimaryKey/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Tests/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /EntityFramework.PrimaryKey/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nick Strupat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /TestsCore/TestsCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | TestsCore 6 | TestsCore 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | TRACE;DEBUG;NETCOREAPP2_0;NETSTANDARD2_0 22 | 23 | 24 | 25 | TRACE;RELEASE;NETCOREAPP2_0;NETSTANDARD2_0 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /EntityFrameworkCore.PrimaryKey/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | #if NETSTANDARD2_0 4 | using Microsoft.EntityFrameworkCore; 5 | namespace EntityFrameworkCore.PrimaryKey { 6 | #else 7 | using System.Data.Entity; 8 | namespace EntityFramework.PrimaryKey { 9 | #endif 10 | 11 | public static class Extensions { 12 | public static PrimaryKeyDictionary GetPrimaryKey(this DbContext context, TEntity entity) 13 | where TEntity : class { 14 | return PrimaryKey.GetFunc(context).Invoke(entity); 15 | } 16 | 17 | public static PrimaryKeyDictionary GetPrimaryKey(this TEntity entity) 18 | where TEntity : class { 19 | return PrimaryKey.GetFunc(typeof(TEntity).GetTypeInfo().Assembly).Invoke(entity); 20 | } 21 | 22 | public static PrimaryKeyDictionary GetPrimaryKey(this TEntity entity, DbContext context) 23 | where TEntity : class { 24 | return PrimaryKey.GetFunc(context).Invoke(entity); 25 | } 26 | 27 | public static PrimaryKeyDictionary GetPrimaryKey(this TEntity entity) 28 | where TEntity : class 29 | where TDbContext : DbContext, new() { 30 | return PrimaryKey.GetFunc().Invoke(entity); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /EntityFramework.PrimaryKey/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("EntityFramework.PrimaryKey")] 9 | [assembly: AssemblyProduct("EntityFramework.PrimaryKey")] 10 | 11 | // Setting ComVisible to false makes the types in this assembly not visible 12 | // to COM components. If you need to access a type in this assembly from 13 | // COM, set the ComVisible attribute to true on that type. 14 | [assembly: ComVisible(false)] 15 | 16 | // The following GUID is for the ID of the typelib if this project is exposed to COM 17 | [assembly: Guid("67f49bd8-f4ee-43be-949f-27df2a172e11")] 18 | 19 | // Version information for an assembly consists of the following four values: 20 | // 21 | // Major Version 22 | // Minor Version 23 | // Build Number 24 | // Revision 25 | // 26 | // You can specify all the values or you can default the Build and Revision Numbers 27 | // by using the '*' as shown below: 28 | // [assembly: AssemblyVersion("1.0.*")] 29 | [assembly: AssemblyVersion("1.2.0.0")] 30 | [assembly: AssemblyFileVersion("1.2.0.0")] 31 | -------------------------------------------------------------------------------- /EntityFrameworkCore.PrimaryKey/EntityFrameworkCore.PrimaryKey.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | Retrieve the primary key (including composite keys) from any entity 6 | EntityFrameworkCore.PrimaryKey 7 | 1.1.2 8 | Nick Strupat 9 | EntityFrameworkCore.PrimaryKey 10 | EntityFrameworkCore.PrimaryKey 11 | entity-framework-core;entityframeworkcore;originalvalues 12 | https://github.com/NickStrupat/EntityFramework.PrimaryKey 13 | https://raw.githubusercontent.com/NickStrupat/EntityFramework.PrimaryKey/master/LICENSE 14 | 1.2.0.0 15 | 1.2.0.0 16 | 1.2.0 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /EntityFrameworkCore.PrimaryKey/PrimaryKeyDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | 5 | #if NETSTANDARD2_0 6 | namespace EntityFrameworkCore.PrimaryKey { 7 | #else 8 | namespace EntityFramework.PrimaryKey { 9 | #endif 10 | public class PrimaryKeyDictionary : ReadOnlyDictionary, IEquatable> where TEntity : class { 11 | public Type EntityType { get; } = typeof(TEntity); 12 | 13 | internal PrimaryKeyDictionary(Dictionary dictionary) : base(dictionary) { } 14 | 15 | public override Boolean Equals(Object other) { 16 | if (other == null) 17 | return false; 18 | if (ReferenceEquals(this, other)) 19 | return true; 20 | if (!(other is Dictionary pkd)) 21 | return false; 22 | return EqualsKeysAndValues(pkd); 23 | } 24 | 25 | public Boolean Equals(Dictionary other) => ReferenceEquals(Dictionary, other) || EqualsKeysAndValues(other); 26 | 27 | private Boolean EqualsKeysAndValues(Dictionary other) { 28 | if (Keys.Count != other.Keys.Count) 29 | return false; 30 | foreach (var key in Keys) 31 | if (!other.TryGetValue(key, out var otherValue) || !this[key].Equals(otherValue)) 32 | return false; 33 | return true; 34 | } 35 | 36 | public override Int32 GetHashCode() { 37 | var hashCode = 0x51ed270b; 38 | unchecked { 39 | foreach (var key in Keys) 40 | if (key != null) 41 | hashCode = (hashCode * -1521134295) + key.GetHashCode(); 42 | foreach (var value in Values) 43 | if (value != null) 44 | hashCode = (hashCode * -1521134295) + value.GetHashCode(); 45 | } 46 | return hashCode; 47 | } 48 | 49 | public class EqualityComparer : IEqualityComparer> { 50 | public Boolean Equals(PrimaryKeyDictionary x, PrimaryKeyDictionary y) => x.Equals(y); 51 | public Int32 GetHashCode(PrimaryKeyDictionary obj) => obj.GetHashCode(); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EntityFramework.PrimaryKey 2 | Retrieve the primary key (including composite keys) from any entity as a dictionary. 3 | 4 | | EF version | .NET support | NuGet package | 5 | |:-----------|:------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| 6 | | 6.1.3 | >= Framework 4.6.1 | [![NuGet Status](http://img.shields.io/nuget/v/EntityFramework.PrimaryKey.svg?style=flat)](https://www.nuget.org/packages/EntityFramework.PrimaryKey/) | 7 | | Core 2.0 | >= Framework 4.6.1 || >= Standard 2.0 | [![NuGet Status](http://img.shields.io/nuget/v/EntityFrameworkCore.PrimaryKey.svg?style=flat)](https://www.nuget.org/packages/EntityFrameworkCore.PrimaryKey/) | 8 | 9 | ## Usage 10 | 11 | ```csharp 12 | public class QuestionTag { 13 | [Key, Column(Order = 0)] public Guid QuestionId { get; set; } 14 | [Key, Column(Order = 1)] public Int64 TagId { get; set; } 15 | public DateTime Inserted { get; set; } 16 | } 17 | 18 | var primaryKey = questionTag.GetPrimaryKey(); 19 | var questionId = (Guid) primaryKey[nameof(QuestionTag.QuestionId)]; 20 | var tagId = (Int64) primaryKey[nameof(QuestionTag.TagId)]; 21 | ``` 22 | 23 | ## API 24 | 25 | ```csharp 26 | var primaryKeyDict = context.GetPrimaryKey(entity); 27 | var primaryKeyDict = entity.GetPrimaryKey(); // auto-detects your DbContext class 28 | var primaryKeyDict = entity.GetPrimaryKey(context); 29 | var primaryKeyDict = entity.GetPrimaryKey(); 30 | ``` 31 | 32 | ## How it works 33 | 34 | The extension methods provide four different ways to get the primary key, but internally they run mostly the same code. The names of the properties are retrieved from the DbContext metadata, and a fast delegate is cached for each entity type when that type is first used. When the fast delegate is invoked with an entity, it creates a `ReadOnlyDictionary` where the keys are the names of the properties which comprise that entity's primary key and the values are the value of each corresponding property. 35 | 36 | ## Contributing 37 | 38 | 1. [Create an issue](https://github.com/NickStrupat/EntityFramework.PrimaryKey/issues/new) 39 | 2. Let's find some point of agreement on your suggestion. 40 | 3. Fork it! 41 | 4. Create your feature branch: `git checkout -b my-new-feature` 42 | 5. Commit your changes: `git commit -am 'Add some feature'` 43 | 6. Push to the branch: `git push origin my-new-feature` 44 | 7. Submit a pull request :D 45 | 46 | ## History 47 | 48 | [Commit history](https://github.com/NickStrupat/EntityFramework.PrimaryKey/commits/master) 49 | 50 | ## License 51 | 52 | [MIT License](https://github.com/NickStrupat/EntityFramework.PrimaryKey/blob/master/README.md) -------------------------------------------------------------------------------- /EntityFramework.PrimaryKey.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.10 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1034B2B3-DDA6-4B2E-827C-9AABFA195555}" 7 | ProjectSection(SolutionItems) = preProject 8 | LICENSE = LICENSE 9 | README.md = README.md 10 | EndProjectSection 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFramework.PrimaryKey", "EntityFramework.PrimaryKey\EntityFramework.PrimaryKey.csproj", "{67F49BD8-F4EE-43BE-949F-27DF2A172E11}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkCore.PrimaryKey", "EntityFrameworkCore.PrimaryKey\EntityFrameworkCore.PrimaryKey.csproj", "{84D09584-E032-42A4-A3B7-48A40EB2FEBB}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestsCore", "TestsCore\TestsCore.csproj", "{B8265899-BA46-46A7-8A3E-25A080809B45}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{8D7EAE93-015B-4FCC-B671-5374649FB764}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {67F49BD8-F4EE-43BE-949F-27DF2A172E11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {67F49BD8-F4EE-43BE-949F-27DF2A172E11}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {67F49BD8-F4EE-43BE-949F-27DF2A172E11}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {67F49BD8-F4EE-43BE-949F-27DF2A172E11}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {84D09584-E032-42A4-A3B7-48A40EB2FEBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {84D09584-E032-42A4-A3B7-48A40EB2FEBB}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {84D09584-E032-42A4-A3B7-48A40EB2FEBB}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {84D09584-E032-42A4-A3B7-48A40EB2FEBB}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {B8265899-BA46-46A7-8A3E-25A080809B45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {B8265899-BA46-46A7-8A3E-25A080809B45}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {B8265899-BA46-46A7-8A3E-25A080809B45}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {B8265899-BA46-46A7-8A3E-25A080809B45}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {8D7EAE93-015B-4FCC-B671-5374649FB764}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {8D7EAE93-015B-4FCC-B671-5374649FB764}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {8D7EAE93-015B-4FCC-B671-5374649FB764}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {8D7EAE93-015B-4FCC-B671-5374649FB764}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {D369B697-6E70-4BA9-B4F1-8800BA0CAB89} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /EntityFramework.PrimaryKey/EntityFramework.PrimaryKey.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {67F49BD8-F4EE-43BE-949F-27DF2A172E11} 8 | Library 9 | Properties 10 | EntityFramework.PrimaryKey 11 | EntityFramework.PrimaryKey 12 | v4.6.1 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | false 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | false 34 | 35 | 36 | 37 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll 38 | True 39 | 40 | 41 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll 42 | True 43 | 44 | 45 | 46 | 47 | 48 | 49 | Extensions.cs 50 | 51 | 52 | PrimaryKey.cs 53 | 54 | 55 | PrimaryKeyDictionary.cs 56 | 57 | 58 | 59 | 60 | 61 | Designer 62 | 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | -------------------------------------------------------------------------------- /EntityFrameworkCore.PrimaryKey/PrimaryKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | 8 | #if NETSTANDARD2_0 9 | using Microsoft.EntityFrameworkCore; 10 | namespace EntityFrameworkCore.PrimaryKey { 11 | #else 12 | using System.Data.Entity; 13 | using System.Data.Entity.Infrastructure; 14 | namespace EntityFramework.PrimaryKey { 15 | #endif 16 | internal static class PrimaryKey { 17 | public static Func> GetFunc() where TEntity : class where TDbContext : DbContext, new() { 18 | return PerDbContextTypeCache.Map.GetOrAdd(typeof(TDbContext), 19 | type => { 20 | using (var context = new TDbContext()) 21 | return GetFunc(context); 22 | }); 23 | } 24 | 25 | public static Func> GetFunc(DbContext context) where TEntity : class { 26 | return PerDbContextTypeCache.Map.GetOrAdd(context.GetType(), 27 | type => { 28 | PropertyInfo[] keyProperties; 29 | using (var con = (DbContext) Activator.CreateInstance(type)) { // Make a new instance inside the lambda so we don't capture the parameter 30 | #if NETSTANDARD2_0 31 | var keyNames = con.Model.FindEntityType(typeof(TEntity)).FindPrimaryKey().Properties.Select(x => x.Name); 32 | #else 33 | IObjectContextAdapter oca = con; 34 | var keyNames = oca.ObjectContext.CreateObjectSet().EntitySet.ElementType.KeyMembers.Select(x => x.Name); 35 | #endif 36 | keyProperties = typeof(TEntity).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => keyNames.Contains(x.Name)).ToArray(); 37 | } 38 | return entity => { 39 | var dictionary = new Dictionary(keyProperties.Length); 40 | foreach (var keyProperty in keyProperties) 41 | dictionary.Add(keyProperty.Name, GetPropertyGetterFunc(keyProperty).Invoke(entity)); 42 | return new PrimaryKeyDictionary(dictionary); 43 | }; 44 | }); 45 | } 46 | 47 | private static Func GetPropertyGetterFunc(PropertyInfo propertyInfo) { 48 | var instance = Expression.Parameter(typeof(TEntity)); 49 | var call = Expression.Call(instance, propertyInfo.GetGetMethod()); 50 | return Expression.Lambda>(Expression.Convert(call, typeof(Object)), instance).Compile(); 51 | } 52 | 53 | private static class PerDbContextTypeCache where TEntity : class { 54 | public static readonly ConcurrentDictionary>> Map = new ConcurrentDictionary>>(); 55 | } 56 | 57 | public static Func> GetFunc() where TEntity : class { 58 | return GetFunc(typeof(TEntity).GetTypeInfo().Assembly); 59 | } 60 | 61 | public static Func> GetFunc(Assembly assembly) where TEntity : class { 62 | return PerAssemblyCache.Map.GetOrAdd(assembly, 63 | a => { 64 | var contextType = a.GetTypes().SingleOrDefault(x => typeof(DbContext).IsAssignableFrom(x) && x.GetConstructor(Type.EmptyTypes) != null); 65 | if (contextType == null) 66 | throw new Exception("Detecting primary keys without supplying a DbContext class only works when called from an assembly which contains exactly one DbContext-derived class with a public parameterless constructor"); 67 | using (var context = (DbContext) Activator.CreateInstance(contextType)) 68 | return GetFunc(context); 69 | }); 70 | } 71 | 72 | private static class PerAssemblyCache where TEntity : class { 73 | public static readonly ConcurrentDictionary>> Map = new ConcurrentDictionary>>(); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8D7EAE93-015B-4FCC-B671-5374649FB764} 8 | Library 9 | Properties 10 | Tests 11 | Tests 12 | v4.6.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll 35 | 36 | 37 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll 50 | 51 | 52 | ..\packages\xunit.assert.2.2.0\lib\netstandard1.1\xunit.assert.dll 53 | 54 | 55 | ..\packages\xunit.extensibility.core.2.2.0\lib\netstandard1.1\xunit.core.dll 56 | 57 | 58 | ..\packages\xunit.extensibility.execution.2.2.0\lib\net452\xunit.execution.desktop.dll 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | {67f49bd8-f4ee-43be-949f-27df2a172e11} 67 | EntityFramework.PrimaryKey 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | Tests.cs 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /TestsCore/Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using Xunit; 6 | 7 | #if NETSTANDARD2_0 8 | using Microsoft.EntityFrameworkCore; 9 | namespace EntityFrameworkCore.PrimaryKey.TestsCore { 10 | #else 11 | using System.Data.Entity; 12 | using EntityFramework.PrimaryKey; 13 | namespace EntityFramework.PrimaryKey.Tests { 14 | #endif 15 | 16 | public abstract class TestBase : IDisposable { 17 | protected readonly Context context = new Context(); 18 | public void Dispose() => context.Dispose(); 19 | } 20 | 21 | public class SingleColumnByConvention : TestBase { 22 | A e = new A { Id = 42L }; 23 | 24 | [Fact] public void GetKeyWithoutContext() => Asserts(e.GetPrimaryKey()); 25 | [Fact] public void GetKeyWithContextInstance() => Asserts(e.GetPrimaryKey(context)); 26 | [Fact] public void GetKeyWithContextType() => Asserts(e.GetPrimaryKey()); 27 | 28 | void Asserts(IDictionary key) { 29 | Assert.Equal(1, key.Count); 30 | Assert.Equal(e.Id, key[nameof(A.Id)]); 31 | Assert.True(key.Equals(new Dictionary { [nameof(A.Id)] = e.Id })); 32 | } 33 | } 34 | 35 | public class SingleColumnByAttribute : TestBase { 36 | B e = new B { KeyColumn = 42L }; 37 | 38 | [Fact] public void GetKeyWithoutContext() => Asserts(e.GetPrimaryKey()); 39 | [Fact] public void GetKeyWithContextInstance() => Asserts(e.GetPrimaryKey(context)); 40 | [Fact] public void GetKeyWithContextType() => Asserts(e.GetPrimaryKey()); 41 | 42 | void Asserts(IDictionary key) { 43 | Assert.Equal(1, key.Count); 44 | Assert.Equal(e.KeyColumn, key[nameof(B.KeyColumn)]); 45 | Assert.True(key.Equals(new Dictionary { [nameof(B.KeyColumn)] = e.KeyColumn })); 46 | } 47 | } 48 | 49 | public class SingleColumnByFluentMapping : TestBase { 50 | C e = new C { IdColumn = 42L }; 51 | 52 | [Fact] public void GetKeyWithoutContext() => Asserts(e.GetPrimaryKey()); 53 | [Fact] public void GetKeyWithContextInstance() => Asserts(e.GetPrimaryKey(context)); 54 | [Fact] public void GetKeyWithContextType() => Asserts(e.GetPrimaryKey()); 55 | 56 | void Asserts(IDictionary key) { 57 | Assert.Equal(1, key.Count); 58 | Assert.Equal(e.IdColumn, key[nameof(C.IdColumn)]); 59 | Assert.True(key.Equals(new Dictionary { [nameof(C.IdColumn)] = e.IdColumn })); 60 | } 61 | } 62 | 63 | public class MultipleColumnByFluentMapping : TestBase { 64 | D e = new D { Id = 42L, Id2 = Guid.NewGuid(), Id3 = Guid.NewGuid(), Id4 = Guid.NewGuid() }; 65 | 66 | [Fact] public void GetKeyWithoutContext() => Asserts(e.GetPrimaryKey()); 67 | [Fact] public void GetKeyWithContextInstance() => Asserts(e.GetPrimaryKey(context)); 68 | [Fact] public void GetKeyWithContextType() => Asserts(e.GetPrimaryKey()); 69 | 70 | void Asserts(IDictionary key) { 71 | Assert.Equal(4, key.Count); 72 | Assert.Equal(e.Id, key[nameof(D.Id)]); 73 | Assert.Equal(e.Id2, key[nameof(D.Id2)]); 74 | Assert.Equal(e.Id3, key[nameof(D.Id3)]); 75 | Assert.Equal(e.Id4, key[nameof(D.Id4)]); 76 | Assert.True(key.Equals(new Dictionary { [nameof(D.Id)] = e.Id, [nameof(D.Id2)] = e.Id2, [nameof(D.Id3)] = e.Id3, [nameof(D.Id4)] = e.Id4 })); 77 | } 78 | } 79 | 80 | #if !NETSTANDARD2_0 81 | public class MultipleColumnByAttribute : TestBase { 82 | E e = new E { Id = 42L, Id2 = Guid.NewGuid() }; 83 | 84 | [Fact] public void GetKeyWithoutContext() => Asserts(e.GetPrimaryKey()); 85 | [Fact] public void GetKeyWithContextInstance() => Asserts(e.GetPrimaryKey(context)); 86 | [Fact] public void GetKeyWithContextType() => Asserts(e.GetPrimaryKey()); 87 | 88 | void Asserts(IDictionary key) { 89 | Assert.Equal(2, key.Count); 90 | Assert.Equal(e.Id, key[nameof(E.Id)]); 91 | Assert.Equal(e.Id2, key[nameof(E.Id2)]); 92 | Assert.True(key.Equals(new Dictionary { [nameof(E.Id)] = e.Id, [nameof(E.Id2)] = e.Id2 })); 93 | } 94 | } 95 | #endif 96 | 97 | public class A { 98 | public virtual Int64 Id { get; set; } 99 | public virtual String Name { get; set; } 100 | } 101 | 102 | public class B { 103 | [Key] 104 | public virtual Int64 KeyColumn { get; set; } 105 | public virtual String Name { get; set; } 106 | } 107 | 108 | public class C { 109 | public virtual Int64 IdColumn { get; set; } 110 | public virtual String Name { get; set; } 111 | } 112 | 113 | public class D { 114 | public virtual Int64 Id { get; set; } 115 | public virtual Guid Id2 { get; set; } 116 | public virtual Guid Id3 { get; set; } 117 | public virtual Guid Id4 { get; set; } 118 | public virtual String Name { get; set; } 119 | } 120 | 121 | #if !NETSTANDARD2_0 122 | public class E { 123 | [Key, Column(Order = 0)] 124 | public virtual Int64 Id { get; set; } 125 | [Key, Column("OtherColumnNameInDb", Order = 1)] 126 | public virtual Guid Id2 { get; set; } 127 | public virtual String Name { get; set; } 128 | } 129 | #endif 130 | 131 | public class Context : DbContext { 132 | public virtual DbSet As { get; protected set; } 133 | public virtual DbSet Bs { get; protected set; } 134 | public virtual DbSet Cs { get; protected set; } 135 | public virtual DbSet Ds { get; protected set; } 136 | #if !NETSTANDARD2_0 137 | public virtual DbSet Es { get; protected set; } 138 | #endif 139 | 140 | #if NETSTANDARD2_0 141 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { 142 | base.OnConfiguring(optionsBuilder); 143 | optionsBuilder.UseInMemoryDatabase("Tests"); 144 | } 145 | protected override void OnModelCreating(ModelBuilder modelBuilder) { 146 | #else 147 | protected override void OnModelCreating(DbModelBuilder modelBuilder) { 148 | #endif 149 | modelBuilder.Entity().HasKey(t => t.IdColumn); 150 | modelBuilder.Entity().HasKey(t => new { t.Id, t.Id2, t.Id3, t.Id4 }); 151 | base.OnModelCreating(modelBuilder); 152 | } 153 | } 154 | } 155 | --------------------------------------------------------------------------------