From a1720c2e732293cf9d99e5a97a657d3b02324d05 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Sat, 13 Aug 2022 14:22:48 +0100 Subject: [PATCH] Reverse-engineer composite keys to PrimaryKeyAttribute when using data annotations Fixes #27637 --- .../Extensions/ScaffoldingModelExtensions.cs | 170 ++++---- .../Internal/CSharpDbContextGenerator.cs | 370 +++++++++++++++++- .../Internal/CSharpDbContextGenerator.tt | 1 - .../Internal/CSharpEntityTypeGeneratorTest.cs | 13 +- 4 files changed, 437 insertions(+), 117 deletions(-) diff --git a/src/EFCore.Design/Extensions/ScaffoldingModelExtensions.cs b/src/EFCore.Design/Extensions/ScaffoldingModelExtensions.cs index d0adcd46702..dc1007b0a0b 100644 --- a/src/EFCore.Design/Extensions/ScaffoldingModelExtensions.cs +++ b/src/EFCore.Design/Extensions/ScaffoldingModelExtensions.cs @@ -10,15 +10,15 @@ namespace Microsoft.EntityFrameworkCore; /// -/// Design-time model extensions. +/// Design-time model extensions. /// public static class ScaffoldingModelExtensions { /// - /// Check whether an entity type could be considered a many-to-many join entity type. + /// Check whether an entity type could be considered a many-to-many join entity type. /// /// The entity type to check. - /// if the entity type could be considered a join entity type. + /// if the entity type could be considered a join entity type. public static bool IsSimpleManyToManyJoinEntityType(this IEntityType entityType) { if (!entityType.GetNavigations().Any() @@ -46,18 +46,21 @@ public static bool IsSimpleManyToManyJoinEntityType(this IEntityType entityType) } /// - /// Gets a value indicating whether the specified skip navigation represents the left side of the relationship. + /// Gets a value indicating whether the specified skip navigation represents the left side of the relationship. /// /// The skip navigation to check. - /// if it represents the left side. + /// if it represents the left side. /// - /// The designation of left and right is arbitrary but deterministic. This method exists primarily to avoid configuring the same many-to-many relationship from both of its ends. + /// The designation of left and right is arbitrary but deterministic. This method exists primarily to avoid configuring the same + /// many-to-many relationship from both of its ends. /// public static bool IsLeftNavigation(this ISkipNavigation skipNavigation) - => skipNavigation.JoinEntityType.FindPrimaryKey()!.Properties[0].GetContainingForeignKeys().Single().PrincipalEntityType == skipNavigation.DeclaringEntityType; + => skipNavigation.JoinEntityType.FindPrimaryKey()!.Properties[0].GetContainingForeignKeys().Single().PrincipalEntityType + == skipNavigation.DeclaringEntityType; /// - /// Gets the name that should be used for the property on the class for this entity type. + /// Gets the name that should be used for the property on the class for this entity + /// type. /// /// The entity type. /// The property name. @@ -66,10 +69,10 @@ public static string GetDbSetName(this IReadOnlyEntityType entityType) ?? entityType.ShortName(); /// - /// Gets a value indicating whether the key would be configured by conventions. + /// Gets a value indicating whether the key would be configured by conventions. /// /// The key to check. - /// if the key would be configured by conventions. + /// if the key would be configured by conventions. public static bool IsHandledByConventions(this IKey key) => key is IConventionKey conventionKey && conventionKey.Properties.SequenceEqual( @@ -78,11 +81,11 @@ public static bool IsHandledByConventions(this IKey key) conventionKey.DeclaringEntityType.GetProperties())); /// - /// Gets value indicating whether this index can be entirely reperesented by a data annotation. + /// Gets value indicating whether this index can be entirely reperesented by a data annotation. /// /// The index. /// The provider's annotation code generator. - /// if this index can be reperesented by a data annotation. + /// if this index can be reperesented by a data annotation. public static bool HasDataAnnotation(this IIndex index, IAnnotationCodeGenerator annotationCodeGenerator) { var indexAnnotations = annotationCodeGenerator.FilterIgnoredAnnotations(index.GetAnnotations()) @@ -94,17 +97,26 @@ public static bool HasDataAnnotation(this IIndex index, IAnnotationCodeGenerator } /// - /// Gets the data annotations to configure an entity type. + /// Gets the data annotations to configure an entity type. /// /// The entity type. /// The provider's annotation code generator. /// The data annotations. - public static IEnumerable GetDataAnnotations(this IEntityType entityType, IAnnotationCodeGenerator annotationCodeGenerator) + public static IEnumerable GetDataAnnotations( + this IEntityType entityType, + IAnnotationCodeGenerator annotationCodeGenerator) { - if (entityType.FindPrimaryKey() == null) + var primaryKey = entityType.FindPrimaryKey(); + if (primaryKey == null) { yield return new AttributeCodeFragment(typeof(KeylessAttribute)); } + else if (primaryKey.Properties.Count > 1) + { + yield return new AttributeCodeFragment( + typeof(PrimaryKeyAttribute), + primaryKey.Properties.Select(p => p.Name).Cast().ToArray()); + } var tableName = entityType.GetTableName(); var schema = entityType.GetSchema(); @@ -118,14 +130,15 @@ public static IEnumerable GetDataAnnotations(this IEntity if (needsSchema) { tableNamedArgs.Add(nameof(TableAttribute.Schema), schema); - }; + } yield return new AttributeCodeFragment(typeof(TableAttribute), new object?[] { tableName }, tableNamedArgs); } foreach (var index in entityType.GetIndexes() - .Where(i => ((IConventionIndex)i).GetConfigurationSource() != ConfigurationSource.Convention - && i.HasDataAnnotation(annotationCodeGenerator))) + .Where( + i => ((IConventionIndex)i).GetConfigurationSource() != ConfigurationSource.Convention + && i.HasDataAnnotation(annotationCodeGenerator))) { var indexArgs = new List(); var indexNamedArgs = new Dictionary(); @@ -167,12 +180,14 @@ public static IEnumerable GetDataAnnotations(this IEntity } /// - /// Gets the data annotations to configure a property. + /// Gets the data annotations to configure a property. /// /// The property. /// The provider's annotation code generator. /// The data annotations. - public static IEnumerable GetDataAnnotations(this IProperty property, IAnnotationCodeGenerator annotationCodeGenerator) + public static IEnumerable GetDataAnnotations( + this IProperty property, + IAnnotationCodeGenerator annotationCodeGenerator) { if (property.FindContainingPrimaryKey() != null) { @@ -263,12 +278,14 @@ public static IEnumerable GetDataAnnotations(this IProper } /// - /// Gets the data annotations to configure a navigation property. + /// Gets the data annotations to configure a navigation property. /// /// The navigation property. /// The provider's annotation code generator. /// The data annotations. - public static IEnumerable GetDataAnnotations(this INavigation navigation, IAnnotationCodeGenerator annotationCodeGenerator) + public static IEnumerable GetDataAnnotations( + this INavigation navigation, + IAnnotationCodeGenerator annotationCodeGenerator) { if (navigation.IsOnDependent && navigation.ForeignKey.PrincipalKey.IsPrimaryKey()) @@ -286,12 +303,14 @@ public static IEnumerable GetDataAnnotations(this INaviga } /// - /// Gets the data annotations to configure a skip navigation property. + /// Gets the data annotations to configure a skip navigation property. /// /// The skip navigation property. /// The provider's annotation code generator. /// The data annotations. - public static IEnumerable GetDataAnnotations(this ISkipNavigation skipNavigation, IAnnotationCodeGenerator annotationCodeGenerator) + public static IEnumerable GetDataAnnotations( + this ISkipNavigation skipNavigation, + IAnnotationCodeGenerator annotationCodeGenerator) { if (skipNavigation.ForeignKey!.PrincipalKey.IsPrimaryKey()) { @@ -304,7 +323,7 @@ public static IEnumerable GetDataAnnotations(this ISkipNa } /// - /// Gets the fluent API calls to configure a model. + /// Gets the fluent API calls to configure a model. /// /// The model. /// The provider's annotation code generator. @@ -331,13 +350,14 @@ public static IEnumerable GetDataAnnotations(this ISkipNa } /// - /// Gets the fluent API calls to configure an entity type. + /// Gets the fluent API calls to configure an entity type. /// /// The entity type. /// The provider's annotation code generator. /// The fluent API calls. public static FluentApiCodeFragment? GetFluentApiCalls( - this IEntityType entityType, IAnnotationCodeGenerator annotationCodeGenerator) + this IEntityType entityType, + IAnnotationCodeGenerator annotationCodeGenerator) { FluentApiCodeFragment? root = null; @@ -365,10 +385,7 @@ public static IEnumerable GetDataAnnotations(this ISkipNa if (entityType.FindPrimaryKey() is null) { - var hasNoKey = new FluentApiCodeFragment(nameof(EntityTypeBuilder.HasNoKey)) - { - HasDataAnnotation = true - }; + var hasNoKey = new FluentApiCodeFragment(nameof(EntityTypeBuilder.HasNoKey)) { HasDataAnnotation = true }; root = root?.Chain(hasNoKey) ?? hasNoKey; } @@ -424,10 +441,10 @@ public static IEnumerable GetDataAnnotations(this ISkipNa { toTableArguments.Add(new NestedClosureCodeFragment("tb", toTableNestedCalls)); } + var toTable = new FluentApiCodeFragment(nameof(RelationalEntityTypeBuilderExtensions.ToTable)) { - Arguments = toTableArguments, - HasDataAnnotation = toTableHandledByDataAnnotations + Arguments = toTableArguments, HasDataAnnotation = toTableHandledByDataAnnotations }; root = root?.Chain(toTable) ?? toTable; @@ -440,10 +457,7 @@ public static IEnumerable GetDataAnnotations(this ISkipNa if (explicitViewSchema || viewName != null) { - var toView = new FluentApiCodeFragment(nameof(RelationalEntityTypeBuilderExtensions.ToView)) - { - Arguments = { viewName } - }; + var toView = new FluentApiCodeFragment(nameof(RelationalEntityTypeBuilderExtensions.ToView)) { Arguments = { viewName } }; if (explicitViewSchema) { @@ -459,7 +473,8 @@ public static IEnumerable GetDataAnnotations(this ISkipNa root = root?.Chain(annotationsRoot) ?? annotationsRoot; } - annotationsRoot = GenerateAnnotations(entityType, annotationsHandledByDataAnnotations, annotationCodeGenerator, hasDataAnnotation: true); + annotationsRoot = GenerateAnnotations( + entityType, annotationsHandledByDataAnnotations, annotationCodeGenerator, hasDataAnnotation: true); if (annotationsRoot is not null) { root = root?.Chain(annotationsRoot) ?? annotationsRoot; @@ -469,7 +484,7 @@ public static IEnumerable GetDataAnnotations(this ISkipNa } /// - /// Gets the fluent API calls to configure a key. + /// Gets the fluent API calls to configure a key. /// /// The key. /// The provider's annotation code generator. @@ -492,7 +507,7 @@ public static IEnumerable GetDataAnnotations(this ISkipNa } /// - /// Gets the fluent API calls to configure an index. + /// Gets the fluent API calls to configure an index. /// /// The index. /// The provider's annotation code generator. @@ -534,7 +549,7 @@ public static IEnumerable GetDataAnnotations(this ISkipNa } /// - /// Gets the fluent API calls to configure a property. + /// Gets the fluent API calls to configure a property. /// /// The property. /// The provider's annotation code generator. @@ -566,10 +581,7 @@ public static IEnumerable GetDataAnnotations(this ISkipNa && property.ClrType.IsNullableType() && !property.IsPrimaryKey()) { - var isRequired = new FluentApiCodeFragment(nameof(PropertyBuilder.IsRequired)) - { - HasDataAnnotation = true - }; + var isRequired = new FluentApiCodeFragment(nameof(PropertyBuilder.IsRequired)) { HasDataAnnotation = true }; root = root?.Chain(isRequired) ?? isRequired; } @@ -579,8 +591,7 @@ public static IEnumerable GetDataAnnotations(this ISkipNa { var hasMaxLength = new FluentApiCodeFragment(nameof(PropertyBuilder.HasMaxLength)) { - Arguments = { maxLength.Value }, - HasDataAnnotation = true + Arguments = { maxLength.Value }, HasDataAnnotation = true }; root = root?.Chain(hasMaxLength) ?? hasMaxLength; @@ -592,12 +603,7 @@ public static IEnumerable GetDataAnnotations(this ISkipNa { var hasPrecision = new FluentApiCodeFragment(nameof(PropertyBuilder.HasPrecision)) { - Arguments = - { - precision.Value, - scale.Value - }, - HasDataAnnotation = true + Arguments = { precision.Value, scale.Value }, HasDataAnnotation = true }; root = root?.Chain(hasPrecision) ?? hasPrecision; @@ -606,8 +612,7 @@ public static IEnumerable GetDataAnnotations(this ISkipNa { var hasPrecision = new FluentApiCodeFragment(nameof(PropertyBuilder.HasPrecision)) { - Arguments = { precision.Value }, - HasDataAnnotation = true + Arguments = { precision.Value }, HasDataAnnotation = true }; root = root?.Chain(hasPrecision) ?? hasPrecision; @@ -615,10 +620,7 @@ public static IEnumerable GetDataAnnotations(this ISkipNa if (property.IsUnicode() != null) { - var isUnicode = new FluentApiCodeFragment(nameof(PropertyBuilder.IsUnicode)) - { - HasDataAnnotation = true - }; + var isUnicode = new FluentApiCodeFragment(nameof(PropertyBuilder.IsUnicode)) { HasDataAnnotation = true }; if (property.IsUnicode() == false) { @@ -662,7 +664,8 @@ public static IEnumerable GetDataAnnotations(this ISkipNa root = root?.Chain(annotationsRoot) ?? annotationsRoot; } - annotationsRoot = GenerateAnnotations(property, annotationsHandledByDataAnnotations, annotationCodeGenerator, hasDataAnnotation: true); + annotationsRoot = GenerateAnnotations( + property, annotationsHandledByDataAnnotations, annotationCodeGenerator, hasDataAnnotation: true); if (annotationsRoot is not null) { root = root?.Chain(annotationsRoot) ?? annotationsRoot; @@ -672,13 +675,16 @@ public static IEnumerable GetDataAnnotations(this ISkipNa } /// - /// Gets the fluent API calls to configure a foreign key. + /// Gets the fluent API calls to configure a foreign key. /// /// The foreign key. /// The provider's annotation code generator. /// A value indicating wheter to use string fluent API overloads instead of ones that take a property accessor lambda. /// The fluent API calls. - public static FluentApiCodeFragment? GetFluentApiCalls(this IForeignKey foreignKey, IAnnotationCodeGenerator annotationCodeGenerator, bool useStrings = false) + public static FluentApiCodeFragment? GetFluentApiCalls( + this IForeignKey foreignKey, + IAnnotationCodeGenerator annotationCodeGenerator, + bool useStrings = false) { FluentApiCodeFragment? root = null; @@ -709,10 +715,7 @@ public static IEnumerable GetDataAnnotations(this ISkipNa root = root?.Chain(hasPrincipalKey) ?? hasPrincipalKey; } - var hasForeignKey = new FluentApiCodeFragment(nameof(ReferenceReferenceBuilder.HasForeignKey)) - { - HasDataAnnotation = true - }; + var hasForeignKey = new FluentApiCodeFragment(nameof(ReferenceReferenceBuilder.HasForeignKey)) { HasDataAnnotation = true }; if (foreignKey.IsUnique) { @@ -754,7 +757,7 @@ public static IEnumerable GetDataAnnotations(this ISkipNa } /// - /// Gets the fluent API calls to configure a sequence. + /// Gets the fluent API calls to configure a sequence. /// /// The sequence. /// The provider's annotation code generator. @@ -765,40 +768,28 @@ public static IEnumerable GetDataAnnotations(this ISkipNa if (sequence.StartValue != Sequence.DefaultStartValue) { - var startsAt = new FluentApiCodeFragment(nameof(SequenceBuilder.StartsAt)) - { - Arguments = { sequence.StartValue } - }; + var startsAt = new FluentApiCodeFragment(nameof(SequenceBuilder.StartsAt)) { Arguments = { sequence.StartValue } }; root = root?.Chain(startsAt) ?? startsAt; } if (sequence.IncrementBy != Sequence.DefaultIncrementBy) { - var incrementsBy = new FluentApiCodeFragment(nameof(SequenceBuilder.IncrementsBy)) - { - Arguments = { sequence.IncrementBy } - }; + var incrementsBy = new FluentApiCodeFragment(nameof(SequenceBuilder.IncrementsBy)) { Arguments = { sequence.IncrementBy } }; root = root?.Chain(incrementsBy) ?? incrementsBy; } if (sequence.MinValue != Sequence.DefaultMinValue) { - var hasMin = new FluentApiCodeFragment(nameof(SequenceBuilder.HasMin)) - { - Arguments = { sequence.MinValue } - }; + var hasMin = new FluentApiCodeFragment(nameof(SequenceBuilder.HasMin)) { Arguments = { sequence.MinValue } }; root = root?.Chain(hasMin) ?? hasMin; } if (sequence.MaxValue != Sequence.DefaultMaxValue) { - var hasMax = new FluentApiCodeFragment(nameof(SequenceBuilder.HasMax)) - { - Arguments = { sequence.MaxValue } - }; + var hasMax = new FluentApiCodeFragment(nameof(SequenceBuilder.HasMax)) { Arguments = { sequence.MaxValue } }; root = root?.Chain(hasMax) ?? hasMax; } @@ -813,7 +804,11 @@ public static IEnumerable GetDataAnnotations(this ISkipNa return root; } - private static FluentApiCodeFragment? GenerateAnnotations(IAnnotatable annotatable, Dictionary annotations, IAnnotationCodeGenerator annotationCodeGenerator, bool hasDataAnnotation = false) + private static FluentApiCodeFragment? GenerateAnnotations( + IAnnotatable annotatable, + Dictionary annotations, + IAnnotationCodeGenerator annotationCodeGenerator, + bool hasDataAnnotation = false) { FluentApiCodeFragment? root = null; @@ -829,12 +824,7 @@ public static IEnumerable GetDataAnnotations(this ISkipNa { var hasAnnotation = new FluentApiCodeFragment(nameof(ModelBuilder.HasAnnotation)) { - Arguments = - { - annotation.Name, - annotation.Value - }, - HasDataAnnotation = hasDataAnnotation + Arguments = { annotation.Name, annotation.Value }, HasDataAnnotation = hasDataAnnotation }; root = root?.Chain(hasAnnotation) ?? hasAnnotation; diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index f9497d6cbb4..8cd7d40aa3a 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -1,7 +1,7 @@ // ------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version: 17.0.0.0 +// Runtime Version: 16.0.0.0 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -21,14 +21,19 @@ namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal /// /// Class to produce the template output /// - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + + #line 1 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] public partial class CSharpDbContextGenerator : CSharpDbContextGeneratorBase { +#line hidden /// /// Create the template output /// public virtual string TransformText() { + + #line 16 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" var services = (IServiceProvider)Host; var providerCode = services.GetRequiredService(); @@ -51,66 +56,154 @@ public virtual string TransformText() if (!string.IsNullOrEmpty(NamespaceHint)) { + + #line default + #line hidden this.Write("namespace "); + + #line 38 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(NamespaceHint)); + + #line default + #line hidden this.Write(";\r\n\r\n"); + + #line 40 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" } + + #line default + #line hidden this.Write("public partial class "); + + #line 43 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(Options.ContextName)); + + #line default + #line hidden this.Write(" : DbContext\r\n{\r\n"); + + #line 45 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" if (!Options.SuppressOnConfiguring) { + + #line default + #line hidden this.Write(" public "); + + #line 49 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(Options.ContextName)); + + #line default + #line hidden this.Write("()\r\n {\r\n }\r\n\r\n"); + + #line 53 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" } + + #line default + #line hidden this.Write(" public "); + + #line 56 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(Options.ContextName)); + + #line default + #line hidden this.Write("(DbContextOptions<"); + + #line 56 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(Options.ContextName)); + + #line default + #line hidden this.Write("> options)\r\n : base(options)\r\n {\r\n }\r\n\r\n"); + + #line 61 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" foreach (var entityType in Model.GetEntityTypes().Where(e => !e.IsSimpleManyToManyJoinEntityType())) { + + #line default + #line hidden this.Write(" public virtual DbSet<"); + + #line 65 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(entityType.Name)); + + #line default + #line hidden this.Write("> "); + + #line 65 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(entityType.GetDbSetName())); + + #line default + #line hidden this.Write(" { get; set; }"); + + #line 65 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(Options.UseNullableReferenceTypes ? " = null!;" : "")); + + #line default + #line hidden this.Write("\r\n\r\n"); + + #line 67 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" } if (!Options.SuppressOnConfiguring) { - this.Write(" protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)" + - "\r\n"); + + #line default + #line hidden + this.Write(" protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)\r\n"); + + #line 74 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" if (!Options.SuppressConnectionStringWarning) { - this.Write(@"#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263. -"); + + #line default + #line hidden + this.Write("#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.\r\n"); + + #line 79 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" } + + #line default + #line hidden this.Write(" => optionsBuilder"); + + #line 82 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Fragment(providerCode.GenerateUseProvider(Options.ConnectionString), indent: 3))); + + #line default + #line hidden this.Write(";\r\n\r\n"); + + #line 84 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" } + + #line default + #line hidden this.Write(" protected override void OnModelCreating(ModelBuilder modelBuilder)\r\n {\r\n"); + + #line 90 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" var anyConfiguration = false; @@ -119,9 +212,19 @@ public virtual string TransformText() { usings.AddRange(modelFluentApiCalls.GetRequiredUsings()); + + #line default + #line hidden this.Write(" modelBuilder"); + + #line 98 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Fragment(modelFluentApiCalls, indent: 3))); + + #line default + #line hidden this.Write(";\r\n"); + + #line 99 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" anyConfiguration = true; } @@ -140,16 +243,25 @@ public virtual string TransformText() var anyEntityTypeConfiguration = false; + + #line default + #line hidden this.Write(" modelBuilder.Entity<"); + + #line 117 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(entityType.Name)); + + #line default + #line hidden this.Write(">(entity =>\r\n {\r\n"); + + #line 119 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" var key = entityType.FindPrimaryKey(); if (key != null) { var keyFluentApiCalls = key.GetFluentApiCalls(annotationCodeGenerator); if (keyFluentApiCalls != null - || key.Properties.Count > 1 || (!key.IsHandledByConventions() && !Options.UseDataAnnotations)) { if (keyFluentApiCalls != null) @@ -157,11 +269,26 @@ public virtual string TransformText() usings.AddRange(keyFluentApiCalls.GetRequiredUsings()); } + + #line default + #line hidden this.Write(" entity.HasKey("); + + #line 132 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Lambda(key.Properties, "e"))); + + #line default + #line hidden this.Write(")"); + + #line 132 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Fragment(keyFluentApiCalls, indent: 4))); + + #line default + #line hidden this.Write(";\r\n"); + + #line 133 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" anyEntityTypeConfiguration = true; } @@ -178,9 +305,19 @@ public virtual string TransformText() WriteLine(""); } + + #line default + #line hidden this.Write(" entity"); + + #line 149 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Fragment(entityTypeFluentApiCalls, indent: 4))); + + #line default + #line hidden this.Write(";\r\n"); + + #line 150 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" anyEntityTypeConfiguration = true; } @@ -199,13 +336,33 @@ public virtual string TransformText() usings.AddRange(indexFluentApiCalls.GetRequiredUsings()); } + + #line default + #line hidden this.Write(" entity.HasIndex("); + + #line 168 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Lambda(index.Properties, "e"))); + + #line default + #line hidden this.Write(", "); + + #line 168 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Literal(index.GetDatabaseName()))); + + #line default + #line hidden this.Write(")"); + + #line 168 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Fragment(indexFluentApiCalls, indent: 4))); + + #line default + #line hidden this.Write(";\r\n"); + + #line 169 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" anyEntityTypeConfiguration = true; } @@ -228,11 +385,26 @@ public virtual string TransformText() WriteLine(""); } + + #line default + #line hidden this.Write(" entity.Property(e => e."); + + #line 191 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(property.Name)); + + #line default + #line hidden this.Write(")"); + + #line 191 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Fragment(propertyFluentApiCalls, indent: 4))); + + #line default + #line hidden this.Write(";\r\n"); + + #line 192 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" anyEntityTypeConfiguration = true; firstProperty = false; @@ -254,15 +426,40 @@ public virtual string TransformText() WriteLine(""); } + + #line default + #line hidden this.Write(" entity.HasOne(d => d."); + + #line 213 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(foreignKey.DependentToPrincipal.Name)); + + #line default + #line hidden this.Write(")."); + + #line 213 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(foreignKey.IsUnique ? "WithOne" : "WithMany")); + + #line default + #line hidden this.Write("(p => p."); + + #line 213 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(foreignKey.PrincipalToDependent.Name)); + + #line default + #line hidden this.Write(")"); + + #line 213 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Fragment(foreignKeyFluentApiCalls, indent: 4))); + + #line default + #line hidden this.Write(";\r\n"); + + #line 214 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" anyEntityTypeConfiguration = true; } @@ -290,22 +487,61 @@ public virtual string TransformText() usings.AddRange(rightFluentApiCalls.GetRequiredUsings()); } + + #line default + #line hidden this.Write(" entity.HasMany(d => d."); + + #line 241 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(skipNavigation.Name)); + + #line default + #line hidden this.Write(").WithMany(p => p."); + + #line 241 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(skipNavigation.Inverse.Name)); - this.Write(")\r\n .UsingEntity>(\r\n " + - " "); + + #line default + #line hidden + this.Write(")\r\n .UsingEntity>(\r\n "); + + #line 243 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Literal(joinEntityType.Name))); + + #line default + #line hidden this.Write(",\r\n r => r.HasOne<"); + + #line 244 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(right.PrincipalEntityType.Name)); + + #line default + #line hidden this.Write(">().WithMany()"); + + #line 244 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Fragment(rightFluentApiCalls, indent: 6))); + + #line default + #line hidden this.Write(",\r\n l => l.HasOne<"); + + #line 245 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(left.PrincipalEntityType.Name)); + + #line default + #line hidden this.Write(">().WithMany()"); + + #line 245 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Fragment(leftFluentApiCalls, indent: 6))); + + #line default + #line hidden this.Write(",\r\n j =>\r\n {\r\n"); + + #line 248 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" var joinKey = joinEntityType.FindPrimaryKey(); var joinKeyFluentApiCalls = joinKey.GetFluentApiCalls(annotationCodeGenerator); @@ -315,20 +551,45 @@ public virtual string TransformText() usings.AddRange(joinKeyFluentApiCalls.GetRequiredUsings()); } + + #line default + #line hidden this.Write(" j.HasKey("); + + #line 257 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Arguments(joinKey.Properties.Select(e => e.Name)))); + + #line default + #line hidden this.Write(")"); + + #line 257 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Fragment(joinKeyFluentApiCalls, indent: 7))); + + #line default + #line hidden this.Write(";\r\n"); + + #line 258 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" var joinEntityTypeFluentApiCalls = joinEntityType.GetFluentApiCalls(annotationCodeGenerator); if (joinEntityTypeFluentApiCalls != null) { usings.AddRange(joinEntityTypeFluentApiCalls.GetRequiredUsings()); + + #line default + #line hidden this.Write(" j"); + + #line 264 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Fragment(joinEntityTypeFluentApiCalls, indent: 7))); + + #line default + #line hidden this.Write(";\r\n"); + + #line 265 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" } @@ -340,22 +601,52 @@ public virtual string TransformText() usings.AddRange(indexFluentApiCalls.GetRequiredUsings()); } + + #line default + #line hidden this.Write(" j.HasIndex("); + + #line 276 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Literal(index.Properties.Select(e => e.Name).ToArray()))); + + #line default + #line hidden this.Write(", "); + + #line 276 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Literal(index.GetDatabaseName()))); + + #line default + #line hidden this.Write(")"); + + #line 276 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Fragment(indexFluentApiCalls, indent: 7))); + + #line default + #line hidden this.Write(";\r\n"); + + #line 277 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" } + + #line default + #line hidden this.Write(" });\r\n"); + + #line 281 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" anyEntityTypeConfiguration = true; } + + #line default + #line hidden this.Write(" });\r\n"); + + #line 286 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" // If any signicant code was generated, append it to the main environment if (anyEntityTypeConfiguration) @@ -374,14 +665,39 @@ public virtual string TransformText() var needsSchema = !string.IsNullOrEmpty(sequence.Schema) && sequence.Schema != sequence.Model.GetDefaultSchema(); var sequenceFluentApiCalls = sequence.GetFluentApiCalls(annotationCodeGenerator); + + #line default + #line hidden this.Write(" modelBuilder.HasSequence"); + + #line 304 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(needsType ? $"<{code.Reference(sequence.Type)}>" : "")); + + #line default + #line hidden this.Write("("); + + #line 304 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Literal(sequence.Name))); + + #line default + #line hidden + + #line 304 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(needsSchema ? $", {code.Literal(sequence.Schema)}" : "")); + + #line default + #line hidden this.Write(")"); + + #line 304 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(code.Fragment(sequenceFluentApiCalls, indent: 3))); + + #line default + #line hidden this.Write(";\r\n"); + + #line 305 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" } @@ -390,8 +706,12 @@ public virtual string TransformText() WriteLine(""); } - this.Write(" OnModelCreatingPartial(modelBuilder);\r\n }\r\n\r\n partial void OnModelC" + - "reatingPartial(ModelBuilder modelBuilder);\r\n}\r\n"); + + #line default + #line hidden + this.Write(" OnModelCreatingPartial(modelBuilder);\r\n }\r\n\r\n partial void OnModelCreatingPartial(ModelBuilder modelBuilder);\r\n}\r\n"); + + #line 318 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" mainEnvironment = GenerationEnvironment; GenerationEnvironment = new StringBuilder(); @@ -399,9 +719,19 @@ public virtual string TransformText() foreach (var ns in usings.Distinct().OrderBy(x => x, new NamespaceComparer())) { + + #line default + #line hidden this.Write("using "); + + #line 325 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(ns)); + + #line default + #line hidden this.Write(";\r\n"); + + #line 326 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" } @@ -409,6 +739,9 @@ public virtual string TransformText() GenerationEnvironment.Append(mainEnvironment); + + #line default + #line hidden return this.GenerationEnvironment.ToString(); } private global::Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost hostValue; @@ -426,6 +759,8 @@ public virtual string TransformText() this.hostValue = value; } } + + #line 1 "C:\github\efcore\src\EFCore.Design\Scaffolding\Internal\CSharpDbContextGenerator.tt" private global::Microsoft.EntityFrameworkCore.Metadata.IModel _ModelField; @@ -527,9 +862,8 @@ public virtual void Initialize() } else { - this.Error("The type \'Microsoft.EntityFrameworkCore.Scaffolding.ModelCodeGenerationOptions\' o" + - "f the parameter \'Options\' did not match the type of the data passed to the templ" + - "ate."); + this.Error("The type \'Microsoft.EntityFrameworkCore.Scaffolding.ModelCodeGenerationOptions\' of " + + "the parameter \'Options\' did not match the type of the data passed to the template."); } } } @@ -580,12 +914,18 @@ public virtual void Initialize() } + + #line default + #line hidden } + + #line default + #line hidden #region Base class /// /// Base class for this transformation /// - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] public class CSharpDbContextGeneratorBase { #region Fields diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.tt b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.tt index ca6309261e9..02971eb93ba 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.tt +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.tt @@ -122,7 +122,6 @@ public partial class <#= Options.ContextName #> : DbContext { var keyFluentApiCalls = key.GetFluentApiCalls(annotationCodeGenerator); if (keyFluentApiCalls != null - || key.Properties.Count > 1 || (!key.IsHandledByConventions() && !Options.UseDataAnnotations)) { if (keyFluentApiCalls != null) diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs index 6814231614d..f7466dbf537 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs @@ -531,7 +531,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) Assert.Equal("PrimaryKey", model.FindEntityType("TestNamespace.Entity").FindPrimaryKey().Properties[0].Name)); [ConditionalFact] - public void KeyAttribute_is_generated_on_multiple_properties_but_configuring_using_fluent_api_for_composite_key() + public void KeyAttribute_is_generated_on_multiple_properties_but_and_uses_PrimaryKeyAttribute_for_composite_key() => Test( modelBuilder => modelBuilder .Entity( @@ -554,6 +554,7 @@ public void KeyAttribute_is_generated_on_multiple_properties_but_configuring_usi namespace TestNamespace; +[PrimaryKey(""Key"", ""Serial"")] public partial class Post { [Key] @@ -593,11 +594,6 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity(entity => - { - entity.HasKey(e => new { e.Key, e.Serial }); - }); - OnModelCreatingPartial(modelBuilder); } @@ -1531,11 +1527,6 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity(entity => - { - entity.HasKey(e => new { e.Id1, e.Id2 }); - }); - modelBuilder.Entity(entity => { entity.Property(e => e.Id).UseIdentityColumn();