diff --git a/src/EFCore.Relational/Extensions/RelationalKeyBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalKeyBuilderExtensions.cs index c7ee6eead7e..e88fcafc250 100644 --- a/src/EFCore.Relational/Extensions/RelationalKeyBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalKeyBuilderExtensions.cs @@ -30,6 +30,17 @@ public static KeyBuilder HasName([NotNull] this KeyBuilder keyBuilder, [CanBeNul return keyBuilder; } + /// + /// Configures the name of the key constraint in the database when targeting a relational database. + /// + /// The builder for the key being configured. + /// The name of the key. + /// The same builder instance so that multiple calls can be chained. + public static KeyBuilder HasName( + [NotNull] this KeyBuilder keyBuilder, + [CanBeNull] string name) + => (KeyBuilder)HasName((KeyBuilder)keyBuilder, name); + /// /// Configures the name of the key constraint in the database when targeting a relational database. /// diff --git a/src/EFCore.SqlServer/Extensions/SqlServerKeyBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerKeyBuilderExtensions.cs index beb40ecc6da..7168df2b1b1 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerKeyBuilderExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerKeyBuilderExtensions.cs @@ -29,6 +29,16 @@ public static KeyBuilder IsClustered([NotNull] this KeyBuilder keyBuilder, bool return keyBuilder; } + /// + /// Configures whether the key is clustered when targeting SQL Server. + /// + /// The builder for the key being configured. + /// A value indicating whether the key is clustered. + /// The same builder instance so that multiple calls can be chained. + public static KeyBuilder IsClustered( + [NotNull] this KeyBuilder keyBuilder, bool clustered = true) + => (KeyBuilder)IsClustered((KeyBuilder)keyBuilder, clustered); + /// /// Configures whether the key is clustered when targeting SQL Server. /// diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs index 92dee289d1a..1fa0f0ce634 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs @@ -85,12 +85,22 @@ public virtual EntityTypeBuilder HasBaseType() /// /// /// An object that can be used to configure the primary key. - public virtual KeyBuilder HasKey([NotNull] Expression> keyExpression) - => new KeyBuilder( + public virtual KeyBuilder HasKey([NotNull] Expression> keyExpression) + => new KeyBuilder( Builder.PrimaryKey( Check.NotNull(keyExpression, nameof(keyExpression)).GetPropertyAccessList(), ConfigurationSource.Explicit).Metadata); + /// + /// Sets the properties that make up the primary key for this entity type. + /// + /// The names of the properties that make up the primary key. + /// An object that can be used to configure the primary key. + public new virtual KeyBuilder HasKey([NotNull] params string[] propertyNames) + => new KeyBuilder( + Builder.PrimaryKey( + Check.NotEmpty(propertyNames, nameof(propertyNames)), ConfigurationSource.Explicit).Metadata); + /// /// Creates an alternate key in the model for this entity type if one does not already exist over the specified /// properties. This will force the properties to be read-only. Use to specify uniqueness @@ -106,12 +116,24 @@ public virtual KeyBuilder HasKey([NotNull] Expression> key /// /// /// An object that can be used to configure the key. - public virtual KeyBuilder HasAlternateKey([NotNull] Expression> keyExpression) - => new KeyBuilder( + public virtual KeyBuilder HasAlternateKey([NotNull] Expression> keyExpression) + => new KeyBuilder( Builder.HasKey( Check.NotNull(keyExpression, nameof(keyExpression)).GetPropertyAccessList(), ConfigurationSource.Explicit).Metadata); + /// + /// Creates an alternate key in the model for this entity type if one does not already exist over the specified + /// properties. This will force the properties to be read-only. Use to specify uniqueness + /// in the model that does not force properties to be read-only. + /// + /// The names of the properties that make up the key. + /// An object that can be used to configure the key. + public new virtual KeyBuilder HasAlternateKey([NotNull] params string[] propertyNames) + => new KeyBuilder( + Builder.HasKey( + Check.NotEmpty(propertyNames, nameof(propertyNames)), ConfigurationSource.Explicit).Metadata); + /// /// Configures the entity type to have no keys. It will only be usable for queries. /// diff --git a/src/EFCore/Metadata/Builders/KeyBuilder`.cs b/src/EFCore/Metadata/Builders/KeyBuilder`.cs new file mode 100644 index 00000000000..2f548a6cf0d --- /dev/null +++ b/src/EFCore/Metadata/Builders/KeyBuilder`.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders +{ + /// + /// + /// Provides a simple API for configuring a . + /// + /// + /// Instances of this class are returned from methods when using the API + /// and it is not designed to be directly constructed in your application code. + /// + /// + // ReSharper disable once UnusedTypeParameter + public class KeyBuilder : KeyBuilder + { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public KeyBuilder([NotNull] IMutableKey key) + : base(key) + { + } + + /// + /// Adds or updates an annotation on the key. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual KeyBuilder HasAnnotation([NotNull] string annotation, [NotNull] object value) + => (KeyBuilder)base.HasAnnotation(annotation, value); + } +} diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs index a7e391e8e64..300fe68989b 100644 --- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs +++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs @@ -59,11 +59,21 @@ public OwnedNavigationBuilder( /// /// /// An object that can be used to configure the primary key. - public virtual KeyBuilder HasKey([NotNull] Expression> keyExpression) - => new KeyBuilder( + public virtual KeyBuilder HasKey([NotNull] Expression> keyExpression) + => new KeyBuilder( DependentEntityType.Builder.PrimaryKey( Check.NotNull(keyExpression, nameof(keyExpression)).GetPropertyAccessList(), ConfigurationSource.Explicit).Metadata); + /// + /// Sets the properties that make up the primary key for this owned entity type. + /// + /// The names of the properties that make up the primary key. + /// An object that can be used to configure the primary key. + public new virtual KeyBuilder HasKey([NotNull] params string[] propertyNames) + => new KeyBuilder( + DependentEntityType.Builder.PrimaryKey( + Check.NotEmpty(propertyNames, nameof(propertyNames)), ConfigurationSource.Explicit).Metadata); + /// /// /// Returns an object that can be used to configure a property of the owned entity type. diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs index 15195f14e90..44512ce978a 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs @@ -247,11 +247,19 @@ public static ModelBuilderTest.TestIndexBuilder HasName( return builder; } - public static ModelBuilderTest.TestKeyBuilder HasName( - this ModelBuilderTest.TestKeyBuilder builder, string name) + public static ModelBuilderTest.TestKeyBuilder HasName( + this ModelBuilderTest.TestKeyBuilder builder, string name) { - var keyBuilder = builder.GetInfrastructure(); - keyBuilder.HasName(name); + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.HasName(name); + break; + case IInfrastructure nongenericBuilder: + nongenericBuilder.Instance.HasName(name); + break; + } + return builder; } } diff --git a/test/EFCore.Tests/Metadata/MetadataBuilderTest.cs b/test/EFCore.Tests/Metadata/MetadataBuilderTest.cs index b91d353e7ce..827cb68c12b 100644 --- a/test/EFCore.Tests/Metadata/MetadataBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/MetadataBuilderTest.cs @@ -100,7 +100,7 @@ public void Can_write_convention_key_builder_extension() .KeyBuilderExtension("V1") .KeyBuilderExtension("V2"); - Assert.IsType(returnedBuilder); + Assert.IsType>(returnedBuilder); var model = builder.Model; var key = model.FindEntityType(typeof(Gunter)).FindPrimaryKey(); @@ -298,7 +298,7 @@ public void Can_write_convention_key_builder_extension_with_common_name() .SharedNameExtension("V1") .SharedNameExtension("V2"); - Assert.IsType(returnedBuilder); + Assert.IsType>(returnedBuilder); var model = builder.Model; var key = model.FindEntityType(typeof(Gunter)).FindPrimaryKey(); diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs index 799b6514257..2af3faea903 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs @@ -181,17 +181,17 @@ public override TestEntityTypeBuilder HasBaseType() public override TestEntityTypeBuilder HasBaseType(string baseEntityTypeName) => Wrap(EntityTypeBuilder.HasBaseType(baseEntityTypeName)); - public override TestKeyBuilder HasKey(Expression> keyExpression) - => new TestKeyBuilder(EntityTypeBuilder.HasKey(keyExpression)); + public override TestKeyBuilder HasKey(Expression> keyExpression) + => new GenericTestKeyBuilder(EntityTypeBuilder.HasKey(keyExpression)); - public override TestKeyBuilder HasKey(params string[] propertyNames) - => new TestKeyBuilder(EntityTypeBuilder.HasKey(propertyNames)); + public override TestKeyBuilder HasKey(params string[] propertyNames) + => new GenericTestKeyBuilder(EntityTypeBuilder.HasKey(propertyNames)); - public override TestKeyBuilder HasAlternateKey(Expression> keyExpression) - => new TestKeyBuilder(EntityTypeBuilder.HasAlternateKey(keyExpression)); + public override TestKeyBuilder HasAlternateKey(Expression> keyExpression) + => new GenericTestKeyBuilder(EntityTypeBuilder.HasAlternateKey(keyExpression)); - public override TestKeyBuilder HasAlternateKey(params string[] propertyNames) - => new TestKeyBuilder(EntityTypeBuilder.HasAlternateKey(propertyNames)); + public override TestKeyBuilder HasAlternateKey(params string[] propertyNames) + => new GenericTestKeyBuilder(EntityTypeBuilder.HasAlternateKey(propertyNames)); public override TestEntityTypeBuilder HasNoKey() => Wrap(EntityTypeBuilder.HasNoKey()); @@ -441,6 +441,23 @@ public override TestPropertyBuilder HasConversion(ValueConverter conv PropertyBuilder IInfrastructure>.Instance => PropertyBuilder; } + protected class GenericTestKeyBuilder : TestKeyBuilder, IInfrastructure> + { + public GenericTestKeyBuilder(KeyBuilder keyBuilder) + { + KeyBuilder = keyBuilder; + } + + private KeyBuilder KeyBuilder { get; } + + public override IMutableKey Metadata => KeyBuilder.Metadata; + + public override TestKeyBuilder HasAnnotation(string annotation, object value) + => new GenericTestKeyBuilder(KeyBuilder.HasAnnotation(annotation, value)); + + KeyBuilder IInfrastructure>.Instance => KeyBuilder; + } + protected class GenericTestNavigationBuilder : TestNavigationBuilder { public GenericTestNavigationBuilder(NavigationBuilder navigationBuilder) @@ -713,11 +730,11 @@ public override TestOwnedNavigationBuilder HasAnnotat string annotation, object value) => Wrap(OwnedNavigationBuilder.HasAnnotation(annotation, value)); - public override TestKeyBuilder HasKey(Expression> keyExpression) - => new TestKeyBuilder(OwnedNavigationBuilder.HasKey(keyExpression)); + public override TestKeyBuilder HasKey(Expression> keyExpression) + => new GenericTestKeyBuilder(OwnedNavigationBuilder.HasKey(keyExpression)); - public override TestKeyBuilder HasKey(params string[] propertyNames) - => new TestKeyBuilder(OwnedNavigationBuilder.HasKey(propertyNames)); + public override TestKeyBuilder HasKey(params string[] propertyNames) + => new GenericTestKeyBuilder(OwnedNavigationBuilder.HasKey(propertyNames)); public override TestPropertyBuilder Property(string propertyName) => new GenericTestPropertyBuilder(OwnedNavigationBuilder.Property(propertyName)); diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs index b1f04120b6c..15a80b862a8 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs @@ -116,20 +116,20 @@ public override TestEntityTypeBuilder HasBaseType() public override TestEntityTypeBuilder HasBaseType(string baseEntityTypeName) => Wrap(EntityTypeBuilder.HasBaseType(baseEntityTypeName)); - public override TestKeyBuilder HasKey(Expression> keyExpression) - => new TestKeyBuilder( + public override TestKeyBuilder HasKey(Expression> keyExpression) + => new NonGenericTestKeyBuilder( EntityTypeBuilder.HasKey(keyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); - public override TestKeyBuilder HasKey(params string[] propertyNames) - => new TestKeyBuilder(EntityTypeBuilder.HasKey(propertyNames)); + public override TestKeyBuilder HasKey(params string[] propertyNames) + => new NonGenericTestKeyBuilder(EntityTypeBuilder.HasKey(propertyNames)); - public override TestKeyBuilder HasAlternateKey(Expression> keyExpression) - => new TestKeyBuilder( + public override TestKeyBuilder HasAlternateKey(Expression> keyExpression) + => new NonGenericTestKeyBuilder( EntityTypeBuilder.HasAlternateKey( keyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); - public override TestKeyBuilder HasAlternateKey(params string[] propertyNames) - => new TestKeyBuilder(EntityTypeBuilder.HasAlternateKey(propertyNames)); + public override TestKeyBuilder HasAlternateKey(params string[] propertyNames) + => new NonGenericTestKeyBuilder(EntityTypeBuilder.HasAlternateKey(propertyNames)); public override TestEntityTypeBuilder HasNoKey() => Wrap(EntityTypeBuilder.HasNoKey()); @@ -429,6 +429,23 @@ public override TestNavigationBuilder UsePropertyAccessMode(PropertyAccessMode p => new NonGenericTestNavigationBuilder(NavigationBuilder.UsePropertyAccessMode(propertyAccessMode)); } + protected class NonGenericTestKeyBuilder : TestKeyBuilder, IInfrastructure + { + public NonGenericTestKeyBuilder(KeyBuilder keyBuilder) + { + KeyBuilder = keyBuilder; + } + + private KeyBuilder KeyBuilder { get; } + + public override IMutableKey Metadata => KeyBuilder.Metadata; + + public override TestKeyBuilder HasAnnotation(string annotation, object value) + => new NonGenericTestKeyBuilder(KeyBuilder.HasAnnotation(annotation, value)); + + KeyBuilder IInfrastructure.Instance => KeyBuilder; + } + protected class NonGenericTestReferenceNavigationBuilder : TestReferenceNavigationBuilder where TEntity : class @@ -700,13 +717,13 @@ public override TestOwnedNavigationBuilder HasAnnotat string annotation, object value) => Wrap(OwnedNavigationBuilder.HasAnnotation(annotation, value)); - public override TestKeyBuilder HasKey(Expression> keyExpression) - => new TestKeyBuilder( + public override TestKeyBuilder HasKey(Expression> keyExpression) + => new NonGenericTestKeyBuilder( OwnedNavigationBuilder.HasKey( keyExpression.GetPropertyAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); - public override TestKeyBuilder HasKey(params string[] propertyNames) - => new TestKeyBuilder(OwnedNavigationBuilder.HasKey(propertyNames)); + public override TestKeyBuilder HasKey(params string[] propertyNames) + => new NonGenericTestKeyBuilder(OwnedNavigationBuilder.HasKey(propertyNames)); public override TestPropertyBuilder Property(string propertyName) => new NonGenericTestPropertyBuilder(OwnedNavigationBuilder.Property(propertyName)); diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs index 314291556d7..9c51910ac41 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs @@ -182,10 +182,10 @@ public abstract TestEntityTypeBuilder HasBaseType() where TBaseEntity : class; public abstract TestEntityTypeBuilder HasBaseType(string baseEntityTypeName); - public abstract TestKeyBuilder HasKey(Expression> keyExpression); - public abstract TestKeyBuilder HasKey(params string[] propertyNames); - public abstract TestKeyBuilder HasAlternateKey(Expression> keyExpression); - public abstract TestKeyBuilder HasAlternateKey(params string[] propertyNames); + public abstract TestKeyBuilder HasKey(Expression> keyExpression); + public abstract TestKeyBuilder HasKey(params string[] propertyNames); + public abstract TestKeyBuilder HasAlternateKey(Expression> keyExpression); + public abstract TestKeyBuilder HasAlternateKey(params string[] propertyNames); public abstract TestEntityTypeBuilder HasNoKey(); public abstract TestPropertyBuilder Property( @@ -294,20 +294,11 @@ public abstract class TestOwnedEntityTypeBuilder { } - public class TestKeyBuilder : IInfrastructure + public abstract class TestKeyBuilder { - public TestKeyBuilder(KeyBuilder keyBuilder) - { - KeyBuilder = keyBuilder; - } - - private KeyBuilder KeyBuilder { get; } - public IMutableKey Metadata => KeyBuilder.Metadata; - - public virtual TestKeyBuilder HasAnnotation(string annotation, object value) - => new TestKeyBuilder(KeyBuilder.HasAnnotation(annotation, value)); + public abstract IMutableKey Metadata { get; } - KeyBuilder IInfrastructure.Instance => KeyBuilder; + public abstract TestKeyBuilder HasAnnotation(string annotation, object value); } public class TestIndexBuilder : IInfrastructure @@ -508,8 +499,8 @@ public abstract class TestOwnedNavigationBuilder public abstract TestOwnedNavigationBuilder HasAnnotation( string annotation, object value); - public abstract TestKeyBuilder HasKey(Expression> keyExpression); - public abstract TestKeyBuilder HasKey(params string[] propertyNames); + public abstract TestKeyBuilder HasKey(Expression> keyExpression); + public abstract TestKeyBuilder HasKey(params string[] propertyNames); public abstract TestPropertyBuilder Property(string propertyName); public abstract TestPropertyBuilder IndexerProperty(string propertyName);