Skip to content

Commit 5ae9bb7

Browse files
ajcvickersroji
andauthored
Attribute for configuring composite primary keys (#27571)
Co-authored-by: Shay Rojansky <[email protected]>
1 parent 9270925 commit 5ae9bb7

File tree

16 files changed

+746
-111
lines changed

16 files changed

+746
-111
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.ComponentModel.DataAnnotations;
5+
using Microsoft.EntityFrameworkCore.Utilities;
6+
7+
namespace Microsoft.EntityFrameworkCore;
8+
9+
/// <summary>
10+
/// Specifies a primary key for the entity type mapped to this CLR type.
11+
/// </summary>
12+
/// <remarks>
13+
/// <para>
14+
/// This attribute can be used for both keys made up of a
15+
/// single property, and for composite keys made up of multiple properties. <see cref="KeyAttribute" />
16+
/// can be used instead for single-property keys, in which case the behavior is identical. If both attributes are used, then
17+
/// this attribute takes precedence.
18+
/// </para>
19+
/// <para>
20+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and
21+
/// examples.
22+
/// </para>
23+
/// </remarks>
24+
[AttributeUsage(AttributeTargets.Class)]
25+
public sealed class PrimaryKeyAttribute : Attribute
26+
{
27+
/// <summary>
28+
/// Initializes a new instance of the <see cref="PrimaryKeyAttribute" /> class.
29+
/// </summary>
30+
/// <param name="propertyName">The first (or only) property in the primary key.</param>
31+
/// <param name="additionalPropertyNames">The additional properties which constitute the primary key, if any, in order.</param>
32+
public PrimaryKeyAttribute(string propertyName, params string[] additionalPropertyNames)
33+
{
34+
Check.NotEmpty(propertyName, nameof(propertyName));
35+
Check.HasNoEmptyElements(additionalPropertyNames, nameof(additionalPropertyNames));
36+
37+
PropertyNames = new List<string> { propertyName };
38+
((List<string>)PropertyNames).AddRange(additionalPropertyNames);
39+
}
40+
41+
/// <summary>
42+
/// The properties which constitute the primary key, in order.
43+
/// </summary>
44+
public IReadOnlyList<string> PropertyNames { get; }
45+
}

src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,18 @@ bool CanHaveIndexerProperty(
230230
/// </returns>
231231
IConventionKeyBuilder? PrimaryKey(IReadOnlyList<IConventionProperty>? properties, bool fromDataAnnotation = false);
232232

233+
/// <summary>
234+
/// Sets the properties that make up the primary key for this entity type.
235+
/// </summary>
236+
/// <param name="propertyNames">The names of the properties that make up the primary key.</param>
237+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
238+
/// <returns>An object that can be used to configure the primary key.</returns>
239+
/// <returns>
240+
/// An object that can be used to configure the primary key if it was set on the entity type,
241+
/// <see langword="null" /> otherwise.
242+
/// </returns>
243+
IConventionKeyBuilder? PrimaryKey(IReadOnlyList<string>? propertyNames, bool fromDataAnnotation = false);
244+
233245
/// <summary>
234246
/// Returns a value indicating whether the given properties can be set as the primary key for this entity type.
235247
/// </summary>

src/EFCore/Metadata/Conventions/IndexAttributeConvention.cs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public IndexAttributeConvention(ProviderConventionSetBuilderDependencies depende
2929
public virtual void ProcessEntityTypeAdded(
3030
IConventionEntityTypeBuilder entityTypeBuilder,
3131
IConventionContext<IConventionEntityTypeBuilder> context)
32-
=> CheckIndexAttributesAndEnsureIndex(entityTypeBuilder.Metadata, false);
32+
=> CheckIndexAttributesAndEnsureIndex(entityTypeBuilder.Metadata, shouldThrow: false);
3333

3434
/// <inheritdoc />
3535
public virtual void ProcessEntityTypeBaseTypeChanged(
@@ -43,7 +43,7 @@ public virtual void ProcessEntityTypeBaseTypeChanged(
4343
return;
4444
}
4545

46-
CheckIndexAttributesAndEnsureIndex(entityTypeBuilder.Metadata, false);
46+
CheckIndexAttributesAndEnsureIndex(entityTypeBuilder.Metadata, shouldThrow: false);
4747
}
4848

4949
/// <inheritdoc />
@@ -53,7 +53,7 @@ public virtual void ProcessModelFinalizing(
5353
{
5454
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
5555
{
56-
CheckIndexAttributesAndEnsureIndex(entityType, true);
56+
CheckIndexAttributesAndEnsureIndex(entityType, shouldThrow: true);
5757
}
5858
}
5959

@@ -62,7 +62,7 @@ private static void CheckIndexAttributesAndEnsureIndex(
6262
bool shouldThrow)
6363
{
6464
foreach (var indexAttribute in
65-
entityType.ClrType.GetCustomAttributes<IndexAttribute>(true))
65+
entityType.ClrType.GetCustomAttributes<IndexAttribute>(inherit: true))
6666
{
6767
IConventionIndexBuilder? indexBuilder;
6868
if (!shouldThrow)
@@ -100,15 +100,18 @@ private static void CheckIndexAttributesAndEnsureIndex(
100100
}
101101
catch (InvalidOperationException exception)
102102
{
103-
CheckMissingProperties(indexAttribute, entityType, exception);
103+
CheckMissingProperties(entityType, indexAttribute, exception);
104104

105105
throw;
106106
}
107107
}
108108

109109
if (indexBuilder == null)
110110
{
111-
CheckIgnoredProperties(indexAttribute, entityType);
111+
if (shouldThrow)
112+
{
113+
CheckIgnoredProperties(entityType, indexAttribute);
114+
}
112115
}
113116
else
114117
{
@@ -119,15 +122,13 @@ private static void CheckIndexAttributesAndEnsureIndex(
119122

120123
if (indexBuilder is not null && indexAttribute.IsDescending is not null)
121124
{
122-
indexBuilder = indexBuilder.IsDescending(indexAttribute.IsDescending, fromDataAnnotation: true);
125+
indexBuilder.IsDescending(indexAttribute.IsDescending, fromDataAnnotation: true);
123126
}
124127
}
125128
}
126129
}
127130

128-
private static void CheckIgnoredProperties(
129-
IndexAttribute indexAttribute,
130-
IConventionEntityType entityType)
131+
private static void CheckIgnoredProperties(IConventionEntityType entityType, IndexAttribute indexAttribute)
131132
{
132133
foreach (var propertyName in indexAttribute.PropertyNames)
133134
{
@@ -153,9 +154,9 @@ private static void CheckIgnoredProperties(
153154
}
154155

155156
private static void CheckMissingProperties(
156-
IndexAttribute indexAttribute,
157157
IConventionEntityType entityType,
158-
InvalidOperationException innerException)
158+
IndexAttribute indexAttribute,
159+
InvalidOperationException exception)
159160
{
160161
foreach (var propertyName in indexAttribute.PropertyNames)
161162
{
@@ -169,7 +170,7 @@ private static void CheckMissingProperties(
169170
entityType.DisplayName(),
170171
indexAttribute.PropertyNames.Format(),
171172
propertyName),
172-
innerException);
173+
exception);
173174
}
174175

175176
throw new InvalidOperationException(
@@ -178,7 +179,7 @@ private static void CheckMissingProperties(
178179
entityType.DisplayName(),
179180
indexAttribute.PropertyNames.Format(),
180181
propertyName),
181-
innerException);
182+
exception);
182183
}
183184
}
184185
}

src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public virtual ConventionSet CreateConventionSet()
6060
var foreignKeyAttributeConvention = new ForeignKeyAttributeConvention(Dependencies);
6161
var relationshipDiscoveryConvention = new RelationshipDiscoveryConvention(Dependencies);
6262
var servicePropertyDiscoveryConvention = new ServicePropertyDiscoveryConvention(Dependencies);
63+
var keyAttributeConvention = new KeyAttributeConvention(Dependencies);
6364
var indexAttributeConvention = new IndexAttributeConvention(Dependencies);
6465
var baseTypeDiscoveryConvention = new BaseTypeDiscoveryConvention(Dependencies);
6566
conventionSet.EntityTypeAddedConventions.Add(new NotMappedEntityTypeAttributeConvention(Dependencies));
@@ -70,6 +71,7 @@ public virtual ConventionSet CreateConventionSet()
7071
conventionSet.EntityTypeAddedConventions.Add(baseTypeDiscoveryConvention);
7172
conventionSet.EntityTypeAddedConventions.Add(propertyDiscoveryConvention);
7273
conventionSet.EntityTypeAddedConventions.Add(servicePropertyDiscoveryConvention);
74+
conventionSet.EntityTypeAddedConventions.Add(keyAttributeConvention);
7375
conventionSet.EntityTypeAddedConventions.Add(keyDiscoveryConvention);
7476
conventionSet.EntityTypeAddedConventions.Add(indexAttributeConvention);
7577
conventionSet.EntityTypeAddedConventions.Add(inversePropertyAttributeConvention);
@@ -87,6 +89,7 @@ public virtual ConventionSet CreateConventionSet()
8789

8890
conventionSet.EntityTypeBaseTypeChangedConventions.Add(propertyDiscoveryConvention);
8991
conventionSet.EntityTypeBaseTypeChangedConventions.Add(servicePropertyDiscoveryConvention);
92+
conventionSet.EntityTypeBaseTypeChangedConventions.Add(keyAttributeConvention);
9093
conventionSet.EntityTypeBaseTypeChangedConventions.Add(keyDiscoveryConvention);
9194
conventionSet.EntityTypeBaseTypeChangedConventions.Add(indexAttributeConvention);
9295
conventionSet.EntityTypeBaseTypeChangedConventions.Add(inversePropertyAttributeConvention);
@@ -102,7 +105,6 @@ public virtual ConventionSet CreateConventionSet()
102105
conventionSet.EntityTypeMemberIgnoredConventions.Add(keyDiscoveryConvention);
103106
conventionSet.EntityTypeMemberIgnoredConventions.Add(foreignKeyPropertyDiscoveryConvention);
104107

105-
var keyAttributeConvention = new KeyAttributeConvention(Dependencies);
106108
var backingFieldConvention = new BackingFieldConvention(Dependencies);
107109
var concurrencyCheckAttributeConvention = new ConcurrencyCheckAttributeConvention(Dependencies);
108110
var databaseGeneratedAttributeConvention = new DatabaseGeneratedAttributeConvention(Dependencies);

0 commit comments

Comments
 (0)