├── 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 | [](https://www.nuget.org/packages/EntityFramework.PrimaryKey/) |
7 | | Core 2.0 | >= Framework 4.6.1 || >= Standard 2.0 | [](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 |
--------------------------------------------------------------------------------