From ae2dcf18bab0ea0d5c94093fa6f2f1232f83bf7b Mon Sep 17 00:00:00 2001 From: Guy Pender Date: Wed, 15 Dec 2021 12:47:57 +0000 Subject: [PATCH 1/3] Add .ToTable and .HasColumnName methods when using Transforms --- .../HbsCSharpDbContextGenerator.cs | 14 +- .../HbsCSharpEntityTypeGenerator.cs | 3 +- .../ExpectedContexts.cs | 190 ++++++++++++++++++ .../ExpectedEntities.cs | 122 +++++++++++ .../HbsCSharpScaffoldingGeneratorTests.cs | 124 ++++++++++-- .../Helpers/HandlebarsTransformers.cs | 43 ++++ 6 files changed, 476 insertions(+), 20 deletions(-) create mode 100644 test/Scaffolding.Handlebars.Tests/Helpers/HandlebarsTransformers.cs diff --git a/src/EntityFrameworkCore.Scaffolding.Handlebars/HbsCSharpDbContextGenerator.cs b/src/EntityFrameworkCore.Scaffolding.Handlebars/HbsCSharpDbContextGenerator.cs index f132fee..c46f549 100644 --- a/src/EntityFrameworkCore.Scaffolding.Handlebars/HbsCSharpDbContextGenerator.cs +++ b/src/EntityFrameworkCore.Scaffolding.Handlebars/HbsCSharpDbContextGenerator.cs @@ -536,6 +536,11 @@ private void GenerateTableName(IEntityType entityType, IndentedStringBuilder sb) var explicitSchema = schema != null && schema != defaultSchema; var explicitTable = explicitSchema || tableName != null && tableName != entityType.GetDbSetName(); + if (!explicitTable && tableName != null) + { + var overrideName = EntityTypeTransformationService.TransformTypeEntityName(tableName); + if (!tableName.Equals(overrideName)) explicitTable = true; + } if (explicitTable) { var parameterString = CSharpHelper.Literal(tableName); @@ -596,11 +601,16 @@ private void GenerateIndex(IEntityType entityType, IIndex index, IndentedStringB private void GenerateProperty(IEntityType entityType, IProperty property, bool useDataAnnotations, IndentedStringBuilder sb) { + var propertyName = EntityTypeTransformationService.TransformPropertyName(entityType, property.Name, property.DeclaringType.Name); var lines = new List { - $".{nameof(EntityTypeBuilder.Property)}(e => e.{EntityTypeTransformationService.TransformPropertyName(entityType, property.Name, property.DeclaringType.Name)})" + $".{nameof(EntityTypeBuilder.Property)}(e => e.{propertyName})" }; - + // Add .HasColumnName Fluent method for remapped columns where UseDataAnnotations is false + if (!propertyName.Equals(property.Name) && !UseDataAnnotations) + { + lines.Add($".HasColumnName(\"{property.Name}\")"); + } var annotations = AnnotationCodeGenerator .FilterIgnoredAnnotations(property.GetAnnotations()) .ToDictionary(a => a.Name, a => a); diff --git a/src/EntityFrameworkCore.Scaffolding.Handlebars/HbsCSharpEntityTypeGenerator.cs b/src/EntityFrameworkCore.Scaffolding.Handlebars/HbsCSharpEntityTypeGenerator.cs index bf149b3..61bc5e0 100644 --- a/src/EntityFrameworkCore.Scaffolding.Handlebars/HbsCSharpEntityTypeGenerator.cs +++ b/src/EntityFrameworkCore.Scaffolding.Handlebars/HbsCSharpEntityTypeGenerator.cs @@ -533,9 +533,10 @@ private void GenerateKeyAttribute(IProperty property) private void GenerateColumnAttribute(IProperty property) { var columnName = property.GetColumnBaseName(); + var propertyName = EntityTypeTransformationService.TransformPropertyName(property.DeclaringEntityType, property.Name, property.DeclaringType.Name); var columnType = property.GetConfiguredColumnType(); - var delimitedColumnName = columnName != null && columnName != property.Name ? CSharpHelper.Literal(columnName) : null; + var delimitedColumnName = columnName != null && columnName != propertyName ? CSharpHelper.Literal(columnName) : null; var delimitedColumnType = columnType != null ? CSharpHelper.Literal(columnType) : null; if ((delimitedColumnName ?? delimitedColumnType) != null) diff --git a/test/Scaffolding.Handlebars.Tests/ExpectedContexts.cs b/test/Scaffolding.Handlebars.Tests/ExpectedContexts.cs index 3b3f619..1bdbed7 100644 --- a/test/Scaffolding.Handlebars.Tests/ExpectedContexts.cs +++ b/test/Scaffolding.Handlebars.Tests/ExpectedContexts.cs @@ -74,7 +74,80 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } "; } + private static class ExpectedContextsWithTransformMappings + { + public static string ContextClass = +@"using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace FakeNamespace +{ + public partial class FakeDbContext : DbContext + { + public virtual DbSet Category { get; set; } + public virtual DbSet Product { get; set; } + + public FakeDbContext(DbContextOptions options) : base(options) + { + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if (!optionsBuilder.IsConfigured) + { +#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. + optionsBuilder.UseSqlServer(""" + Constants.Connections.SqlServerConnection.Replace(@"\",@"\\") + @"""); + } + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.ToTable(""Category""); + + entity.HasComment(""A category of products""); + + entity.Property(e => e.CategoryNameRenamed) + .HasColumnName(""CategoryName"") + .IsRequired() + .HasMaxLength(15) + .HasComment(""The name of a category""); + }); + + modelBuilder.Entity(entity => + { + entity.ToTable(""Product""); + + entity.HasIndex(e => e.CategoryId, ""IX_Product_CategoryId""); + entity.Property(e => e.ProductName) + .IsRequired() + .HasMaxLength(40); + + entity.Property(e => e.RowVersion) + .IsRowVersion() + .IsConcurrencyToken(); + + entity.Property(e => e.UnitPriceRenamed) + .HasColumnName(""UnitPrice"") + .HasColumnType(""money""); + + entity.HasOne(d => d.Category) + .WithMany(p => p.Product) + .HasForeignKey(d => d.CategoryId); + }); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); + } +} +"; + } private static class ExpectedContextsSupressOnConfiguring { public static string ContextClass = @@ -137,7 +210,71 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } "; } + private static class ExpectedContextsSupressOnConfiguringWithTransformMappings + { + public static string ContextClass = +@"using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace FakeNamespace +{ + public partial class FakeDbContext : DbContext + { + public virtual DbSet Category { get; set; } + public virtual DbSet Product { get; set; } + + public FakeDbContext(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.ToTable(""Category""); + + entity.HasComment(""A category of products""); + + entity.Property(e => e.CategoryNameRenamed) + .HasColumnName(""CategoryName"") + .IsRequired() + .HasMaxLength(15) + .HasComment(""The name of a category""); + }); + + modelBuilder.Entity(entity => + { + entity.ToTable(""Product""); + + entity.HasIndex(e => e.CategoryId, ""IX_Product_CategoryId""); + + entity.Property(e => e.ProductName) + .IsRequired() + .HasMaxLength(40); + + entity.Property(e => e.RowVersion) + .IsRowVersion() + .IsConcurrencyToken(); + + entity.Property(e => e.UnitPriceRenamed) + .HasColumnName(""UnitPrice"") + .HasColumnType(""money""); + + entity.HasOne(d => d.Category) + .WithMany(p => p.Product) + .HasForeignKey(d => d.CategoryId); + }); + + OnModelCreatingPartial(modelBuilder); + } + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); + } +} +"; + } public static class ExpectedContextsWithAnnotations { public static string ContextClass = @@ -190,5 +327,58 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } "; } + public static class ExpectedContextsWithAnnotationsAndTransformMappings + { + public static string ContextClass = +@"using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace FakeNamespace +{ + public partial class FakeDbContext : DbContext + { + public virtual DbSet Category { get; set; } + public virtual DbSet Product { get; set; } + + public FakeDbContext(DbContextOptions options) : base(options) + { + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if (!optionsBuilder.IsConfigured) + { +#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. + optionsBuilder.UseSqlServer(""" + Constants.Connections.SqlServerConnection.Replace(@"\",@"\\") + @"""); + } + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasComment(""A category of products""); + + entity.Property(e => e.CategoryNameRenamed).HasComment(""The name of a category""); + }); + + modelBuilder.Entity(entity => + { + entity.Property(e => e.RowVersion) + .IsRowVersion() + .IsConcurrencyToken(); + }); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); + } +} +"; + } + } } diff --git a/test/Scaffolding.Handlebars.Tests/ExpectedEntities.cs b/test/Scaffolding.Handlebars.Tests/ExpectedEntities.cs index bb0f187..1e392e2 100644 --- a/test/Scaffolding.Handlebars.Tests/ExpectedEntities.cs +++ b/test/Scaffolding.Handlebars.Tests/ExpectedEntities.cs @@ -53,6 +53,57 @@ public partial class Product "; } + private static class ExpectedEntitiesWithTransformMappings + { + public const string CategoryClass = +@"using System; +using System.Collections.Generic; + +namespace FakeNamespace +{ + /// + /// A category of products + /// + public partial class CategoryRenamed + { + public CategoryRenamed() + { + Products = new HashSet(); + } + + public int CategoryId { get; set; } + + /// + /// The name of a category + /// + public string CategoryNameRenamed { get; set; } + + public virtual ICollection Products { get; set; } + } +} +"; + + public const string ProductClass = +@"using System; +using System.Collections.Generic; + +namespace FakeNamespace +{ + public partial class ProductRenamed + { + public int ProductId { get; set; } + public string ProductName { get; set; } + public decimal? UnitPriceRenamed { get; set; } + public bool Discontinued { get; set; } + public byte[] RowVersion { get; set; } + public int? CategoryId { get; set; } + + public virtual CategoryRenamed Category { get; set; } + } +} +"; + } + private static class ExpectedEntitiesWithAnnotations { public const string CategoryClass = @@ -123,6 +174,77 @@ public partial class Product "; } + private static class ExpectedEntitiesWithAnnotationsAndTransformMappings + { + public const string CategoryClass = + @"using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace FakeNamespace +{ + /// + /// A category of products + /// + [Table(""Category"")] + public partial class CategoryRenamed + { + public CategoryRenamed() + { + Products = new HashSet(); + } + + [Key] + public int CategoryId { get; set; } + + /// + /// The name of a category + /// + [Required] + [Column(""CategoryName"")] + [StringLength(15)] + public string CategoryNameRenamed { get; set; } + + [InverseProperty(nameof(ProductRenamed.Category))] + public virtual ICollection Products { get; set; } + } +} +"; + + public const string ProductClass = + @"using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace FakeNamespace +{ + [Table(""Product"")] + [Index(nameof(CategoryId), Name = ""IX_Product_CategoryId"")] + public partial class ProductRenamed + { + [Key] + public int ProductId { get; set; } + [Required] + [StringLength(40)] + public string ProductName { get; set; } + [Column(""UnitPrice"", TypeName = ""money"")] + public decimal? UnitPriceRenamed { get; set; } + public bool Discontinued { get; set; } + public byte[] RowVersion { get; set; } + public int? CategoryId { get; set; } + + [ForeignKey(nameof(CategoryId))] + [InverseProperty(""Products"")] + public virtual CategoryRenamed Category { get; set; } + } +} +"; + } + private static class ExpectedEntitiesWithNullableNavigation { public const string CategoryClass = diff --git a/test/Scaffolding.Handlebars.Tests/HbsCSharpScaffoldingGeneratorTests.cs b/test/Scaffolding.Handlebars.Tests/HbsCSharpScaffoldingGeneratorTests.cs index 9803fc8..8278bf2 100644 --- a/test/Scaffolding.Handlebars.Tests/HbsCSharpScaffoldingGeneratorTests.cs +++ b/test/Scaffolding.Handlebars.Tests/HbsCSharpScaffoldingGeneratorTests.cs @@ -139,7 +139,7 @@ public void WriteCode_Should_Generate_Context_File(bool useDataAnnotations, stri Language = "C#", SuppressOnConfiguring = suppressOnConfiguring }); - + // Assert var files = GetGeneratedFiles(model, options); object expectedContext; @@ -155,6 +155,51 @@ public void WriteCode_Should_Generate_Context_File(bool useDataAnnotations, stri Assert.Equal(expectedContext, context); } + [Theory] + [InlineData(false, "en-US", true)] + [InlineData(false, "en-US", false)] + [InlineData(true, "en-US", false)] + [InlineData(false, "tr-TR", false)] + [InlineData(true, "tr-TR", false)] + public void WriteCode_WithTransformMapping_Should_Generate_Context_File(bool useDataAnnotations, string culture, bool suppressOnConfiguring) + { + // Arrange + bool useTransformers = true; + Thread.CurrentThread.CurrentCulture = new CultureInfo(culture); + var options = ReverseEngineerOptions.DbContextOnly; + var scaffolder = CreateScaffolder(options, useTransformers); + + // Act + var model = scaffolder.ScaffoldModel( + connectionString: Constants.Connections.SqlServerConnection, + databaseOptions: new DatabaseModelFactoryOptions(), + modelOptions: new ModelReverseEngineerOptions(){NoPluralize = useTransformers}, // Pluralized properties can also generate .ToTable methods. We only want mapping transforms to generate the .ToTable methods + codeOptions: new ModelCodeGenerationOptions + { + ContextNamespace = Constants.Parameters.RootNamespace, + ModelNamespace = Constants.Parameters.RootNamespace, + ContextName = Constants.Parameters.ContextName, + ContextDir = Constants.Parameters.ProjectPath, + UseDataAnnotations = useDataAnnotations, + Language = "C#", + SuppressOnConfiguring = suppressOnConfiguring + }); + + // Assert + var files = GetGeneratedFiles(model, options); + object expectedContext; + expectedContext = useDataAnnotations + ? ExpectedContextsWithAnnotationsAndTransformMappings.ContextClass + : ExpectedContextsWithTransformMappings.ContextClass; + if (suppressOnConfiguring) + { + expectedContext = ExpectedContextsSupressOnConfiguringWithTransformMappings.ContextClass; + } + var context = files[Constants.Files.CSharpFiles.DbContextFile]; + + Assert.Equal(expectedContext, context); + } + [Theory] [InlineData(false, "en-US")] [InlineData(true, "en-US")] @@ -199,6 +244,51 @@ public void WriteCode_Should_Generate_Entity_Files(bool useDataAnnotations, stri Assert.Equal(expectedProduct, product); } + [Theory] + [InlineData(false, "en-US")] + [InlineData(true, "en-US")] + [InlineData(false, "tr-TR")] + [InlineData(true, "tr-TR")] + public void WriteCode_WithTransformMapping_Should_Generate_Entity_Files(bool useDataAnnotations, string culture) + { + // Arrange + bool useTransformers = true; + Thread.CurrentThread.CurrentCulture = new CultureInfo(culture); + var options = ReverseEngineerOptions.EntitiesOnly; + var scaffolder = CreateScaffolder(options, useTransformers); + + // Act + var model = scaffolder.ScaffoldModel( + connectionString: Constants.Connections.SqlServerConnection, + databaseOptions: new DatabaseModelFactoryOptions(), + modelOptions: new ModelReverseEngineerOptions(), + codeOptions: new ModelCodeGenerationOptions + { + ContextNamespace = Constants.Parameters.RootNamespace, + ModelNamespace = Constants.Parameters.RootNamespace, + ContextName = Constants.Parameters.ContextName, + ContextDir = Constants.Parameters.ProjectPath, + UseDataAnnotations = useDataAnnotations, + Language = "C#", + }); + + // Assert + var files = GetGeneratedFiles(model, options); + var category = files[Constants.Files.CSharpFiles.CategoryFile]; + var product = files[Constants.Files.CSharpFiles.ProductFile]; + + object expectedCategory; + object expectedProduct; + expectedCategory = useDataAnnotations + ? ExpectedEntitiesWithAnnotationsAndTransformMappings.CategoryClass + : ExpectedEntitiesWithTransformMappings.CategoryClass; + expectedProduct = useDataAnnotations + ? ExpectedEntitiesWithAnnotationsAndTransformMappings.ProductClass + : ExpectedEntitiesWithTransformMappings.ProductClass; + Assert.Equal(expectedCategory, category); + Assert.Equal(expectedProduct, product); + } + [Theory] [InlineData("en-US")] [InlineData("tr-TR")] @@ -425,12 +515,16 @@ public void Save_Should_Write_Context_and_Entity_Files() Assert.Equal(expectedProductPath, result.AdditionalFiles[1]); } + private IReverseEngineerScaffolder CreateScaffolder(ReverseEngineerOptions revEngOptions, bool useEntityNameMappings) + { + return CreateScaffolder(revEngOptions, _ => { }, useEntityNameMappings, null); + } private IReverseEngineerScaffolder CreateScaffolder(ReverseEngineerOptions revEngOptions, string filenamePrefix = null) { - return CreateScaffolder(revEngOptions, _ => { }, filenamePrefix); + return CreateScaffolder(revEngOptions, _ => { }, false, filenamePrefix); } - private IReverseEngineerScaffolder CreateScaffolder(ReverseEngineerOptions revEngOptions, Action configureOptions, string filenamePrefix = null) + private IReverseEngineerScaffolder CreateScaffolder(ReverseEngineerOptions revEngOptions, Action configureOptions, bool useEntityTransformMappings = false, string filenamePrefix = null) { var fileService = new InMemoryTemplateFileService(); fileService.InputFiles(ContextClassTemplate, ContextImportsTemplate, @@ -486,19 +580,16 @@ private IReverseEngineerScaffolder CreateScaffolder(ReverseEngineerOptions revEn new HbsBlockHelperService(new Dictionary>())) .AddSingleton(); - - if (string.IsNullOrWhiteSpace(filenamePrefix)) - { - services - .AddSingleton() - .AddSingleton(); - } - else - { - services - .AddSingleton(y => new HbsContextTransformationService(contextName => $"{filenamePrefix}{contextName}")) - .AddSingleton(y => new HbsEntityTypeTransformationService(entityFileNameTransformer: entityName => $"{filenamePrefix}{entityName}")); - } + // Add Transformation Services + services + .AddSingleton(y => new HbsContextTransformationService(contextName => !string.IsNullOrWhiteSpace(filenamePrefix) ? $"{filenamePrefix}{contextName}" : contextName)) + .AddSingleton(y => new HbsEntityTypeTransformationService( + entityFileNameTransformer: entityName => !string.IsNullOrWhiteSpace(filenamePrefix) ? $"{filenamePrefix}{entityName}" : entityName, + entityTypeNameTransformer: entityName => useEntityTransformMappings ? HandlebarsTransformers.MapEntityName(entityName) : entityName, + constructorTransformer: epi => useEntityTransformMappings ? HandlebarsTransformers.MapPropertyInfo(epi) : epi, + propertyTransformer: epi => useEntityTransformMappings ? HandlebarsTransformers.MapPropertyInfo(epi) : epi, + navPropertyTransformer: epi => useEntityTransformMappings ? HandlebarsTransformers.MapNavPropertyInfo(epi) : epi + )); services.Configure(configureOptions); @@ -510,7 +601,6 @@ private IReverseEngineerScaffolder CreateScaffolder(ReverseEngineerOptions revEn .GetRequiredService(); return scaffolder; } - private Dictionary GetGeneratedFiles(ScaffoldedModel model, ReverseEngineerOptions options) { var generatedFiles = new Dictionary(); diff --git a/test/Scaffolding.Handlebars.Tests/Helpers/HandlebarsTransformers.cs b/test/Scaffolding.Handlebars.Tests/Helpers/HandlebarsTransformers.cs new file mode 100644 index 0000000..fb62076 --- /dev/null +++ b/test/Scaffolding.Handlebars.Tests/Helpers/HandlebarsTransformers.cs @@ -0,0 +1,43 @@ +using EntityFrameworkCore.Scaffolding.Handlebars; +using System.Collections.Generic; + +namespace Scaffolding.Handlebars.Tests.Helpers +{ + public static class HandlebarsTransformers + { + static Dictionary entityTypeNameMappings = new Dictionary(){ + {"Product","ProductRenamed"}, + {"Category","CategoryRenamed"} + }; + static Dictionary entityPropertyNameMappings = new Dictionary(){ + {"UnitPrice","UnitPriceRenamed"}, + {"CategoryName","CategoryNameRenamed"} + }; + public static string MapEntityName(string entityName) + { + var nameOverride = string.Empty; + if (entityTypeNameMappings.TryGetValue(entityName, out nameOverride)) return nameOverride; + return entityName; + } + public static EntityPropertyInfo MapNavPropertyInfo(EntityPropertyInfo e) + { + return new EntityPropertyInfo(MapPropertyTypeName(e.PropertyType), MapPropertyName(e.PropertyName)); + } + public static EntityPropertyInfo MapPropertyInfo(EntityPropertyInfo e) + { + return new EntityPropertyInfo(MapPropertyTypeName(e.PropertyType), MapPropertyName(e.PropertyName)); + } + private static string MapPropertyTypeName(string propertyTypeName) + { + var propertyTypeNameOverride = string.Empty; + if (entityTypeNameMappings.TryGetValue(propertyTypeName, out propertyTypeNameOverride)) return propertyTypeNameOverride; + return propertyTypeName; + } + private static string MapPropertyName(string propertyName) + { + var propertyNameOverride = string.Empty; + if (entityPropertyNameMappings.TryGetValue(propertyName, out propertyNameOverride)) return propertyNameOverride; + return propertyName; + } + } +} \ No newline at end of file From 0be1fc94e637e53cfc2c4bd919b0974c652d38a7 Mon Sep 17 00:00:00 2001 From: Anthony Sneed Date: Sat, 18 Dec 2021 09:41:16 -0600 Subject: [PATCH 2/3] Clean up HandlebarsTransformers helper class in test project. --- .../Helpers/HandlebarsTransformers.cs | 44 +++++++------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/test/Scaffolding.Handlebars.Tests/Helpers/HandlebarsTransformers.cs b/test/Scaffolding.Handlebars.Tests/Helpers/HandlebarsTransformers.cs index fb62076..9ff3020 100644 --- a/test/Scaffolding.Handlebars.Tests/Helpers/HandlebarsTransformers.cs +++ b/test/Scaffolding.Handlebars.Tests/Helpers/HandlebarsTransformers.cs @@ -5,39 +5,27 @@ namespace Scaffolding.Handlebars.Tests.Helpers { public static class HandlebarsTransformers { - static Dictionary entityTypeNameMappings = new Dictionary(){ + static readonly Dictionary _entityTypeNameMappings = new(){ {"Product","ProductRenamed"}, {"Category","CategoryRenamed"} }; - static Dictionary entityPropertyNameMappings = new Dictionary(){ + static readonly Dictionary _entityPropertyNameMappings = new(){ {"UnitPrice","UnitPriceRenamed"}, {"CategoryName","CategoryNameRenamed"} }; - public static string MapEntityName(string entityName) - { - var nameOverride = string.Empty; - if (entityTypeNameMappings.TryGetValue(entityName, out nameOverride)) return nameOverride; - return entityName; - } - public static EntityPropertyInfo MapNavPropertyInfo(EntityPropertyInfo e) - { - return new EntityPropertyInfo(MapPropertyTypeName(e.PropertyType), MapPropertyName(e.PropertyName)); - } - public static EntityPropertyInfo MapPropertyInfo(EntityPropertyInfo e) - { - return new EntityPropertyInfo(MapPropertyTypeName(e.PropertyType), MapPropertyName(e.PropertyName)); - } - private static string MapPropertyTypeName(string propertyTypeName) - { - var propertyTypeNameOverride = string.Empty; - if (entityTypeNameMappings.TryGetValue(propertyTypeName, out propertyTypeNameOverride)) return propertyTypeNameOverride; - return propertyTypeName; - } - private static string MapPropertyName(string propertyName) - { - var propertyNameOverride = string.Empty; - if (entityPropertyNameMappings.TryGetValue(propertyName, out propertyNameOverride)) return propertyNameOverride; - return propertyName; - } + public static string MapEntityName(string entityName) => + _entityTypeNameMappings.TryGetValue(entityName, out var nameOverride) ? nameOverride : entityName; + + public static EntityPropertyInfo MapNavPropertyInfo(EntityPropertyInfo e) => + new(MapPropertyTypeName(e.PropertyType), MapPropertyName(e.PropertyName)); + + public static EntityPropertyInfo MapPropertyInfo(EntityPropertyInfo e) => + new(MapPropertyTypeName(e.PropertyType), MapPropertyName(e.PropertyName)); + + private static string MapPropertyTypeName(string propertyTypeName) => + _entityTypeNameMappings.TryGetValue(propertyTypeName, out var propertyTypeNameOverride) ? propertyTypeNameOverride : propertyTypeName; + + private static string MapPropertyName(string propertyName) => + _entityPropertyNameMappings.TryGetValue(propertyName, out var propertyNameOverride) ? propertyNameOverride : propertyName; } } \ No newline at end of file From a946d4eb27978dd0c1f0ec7d9493d83f3e0e6486 Mon Sep 17 00:00:00 2001 From: Anthony Sneed Date: Sat, 18 Dec 2021 11:46:18 -0600 Subject: [PATCH 3/3] Generate HasKey if key property transformed. --- .../HbsCSharpDbContextGenerator.cs | 27 +++++++++++------- .../ExpectedContexts.cs | 28 ++++++++++++++++--- .../ExpectedEntities.cs | 17 ++++++----- .../Helpers/HandlebarsTransformers.cs | 16 +++++++---- 4 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/EntityFrameworkCore.Scaffolding.Handlebars/HbsCSharpDbContextGenerator.cs b/src/EntityFrameworkCore.Scaffolding.Handlebars/HbsCSharpDbContextGenerator.cs index c46f549..5c5cfc5 100644 --- a/src/EntityFrameworkCore.Scaffolding.Handlebars/HbsCSharpDbContextGenerator.cs +++ b/src/EntityFrameworkCore.Scaffolding.Handlebars/HbsCSharpDbContextGenerator.cs @@ -493,17 +493,25 @@ private void GenerateKey(IKey key, IEntityType entityType, IndentedStringBuilder if (key.Properties.Count == 1 && annotations.Count == 0) { + bool propertyNameOverriden = false; + foreach (var property in key.Properties) + { + var transformedKeyName = EntityTypeTransformationService.TransformPropertyName(entityType, property.Name, property.DeclaringType.Name); + propertyNameOverriden = !property.Name.Equals(transformedKeyName); + if (propertyNameOverriden) break; + } + if (key is IConventionKey concreteKey && concreteKey.Properties.SequenceEqual( KeyDiscoveryConvention.DiscoverKeyProperties( concreteKey.DeclaringEntityType, - concreteKey.DeclaringEntityType.GetProperties()))) + concreteKey.DeclaringEntityType.GetProperties())) + && UseDataAnnotations || !propertyNameOverriden) { return; } - if (!explicitName - && UseDataAnnotations) + if (!explicitName && UseDataAnnotations) { return; } @@ -534,14 +542,13 @@ private void GenerateTableName(IEntityType entityType, IndentedStringBuilder sb) var schema = entityType.GetSchema(); var defaultSchema = entityType.Model.GetDefaultSchema(); + var transformedTableName = EntityTypeTransformationService.TransformTypeEntityName(tableName); + var tableNameOverriden = tableName != null && !tableName.Equals(transformedTableName); + var explicitSchema = schema != null && schema != defaultSchema; var explicitTable = explicitSchema || tableName != null && tableName != entityType.GetDbSetName(); - if (!explicitTable && tableName != null) - { - var overrideName = EntityTypeTransformationService.TransformTypeEntityName(tableName); - if (!tableName.Equals(overrideName)) explicitTable = true; - } - if (explicitTable) + if (!explicitTable && tableName != null && tableNameOverriden) explicitTable = true; + if (explicitTable && tableName != null) { var parameterString = CSharpHelper.Literal(tableName); if (explicitSchema) @@ -560,7 +567,7 @@ private void GenerateTableName(IEntityType entityType, IndentedStringBuilder sb) var explicitViewSchema = viewSchema != null && viewSchema != defaultSchema; var explicitViewTable = explicitViewSchema || viewName != null; - if (explicitViewTable) + if (explicitViewTable && viewName != null) { var parameterString = CSharpHelper.Literal(viewName); if (explicitViewSchema) diff --git a/test/Scaffolding.Handlebars.Tests/ExpectedContexts.cs b/test/Scaffolding.Handlebars.Tests/ExpectedContexts.cs index 1bdbed7..fe56a55 100644 --- a/test/Scaffolding.Handlebars.Tests/ExpectedContexts.cs +++ b/test/Scaffolding.Handlebars.Tests/ExpectedContexts.cs @@ -106,10 +106,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => { + entity.HasKey(e => e.CategoryIdRenamed); + entity.ToTable(""Category""); entity.HasComment(""A category of products""); + entity.Property(e => e.CategoryIdRenamed).HasColumnName(""CategoryId""); + entity.Property(e => e.CategoryNameRenamed) .HasColumnName(""CategoryName"") .IsRequired() @@ -119,9 +123,15 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity(entity => { + entity.HasKey(e => e.ProductIdRenamed); + entity.ToTable(""Product""); - entity.HasIndex(e => e.CategoryId, ""IX_Product_CategoryId""); + entity.HasIndex(e => e.CategoryIdRenamed, ""IX_Product_CategoryId""); + + entity.Property(e => e.ProductIdRenamed).HasColumnName(""ProductId""); + + entity.Property(e => e.CategoryIdRenamed).HasColumnName(""CategoryId""); entity.Property(e => e.ProductName) .IsRequired() @@ -137,7 +147,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.HasOne(d => d.Category) .WithMany(p => p.Product) - .HasForeignKey(d => d.CategoryId); + .HasForeignKey(d => d.CategoryIdRenamed); }); OnModelCreatingPartial(modelBuilder); @@ -233,10 +243,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => { + entity.HasKey(e => e.CategoryIdRenamed); + entity.ToTable(""Category""); entity.HasComment(""A category of products""); + entity.Property(e => e.CategoryIdRenamed).HasColumnName(""CategoryId""); + entity.Property(e => e.CategoryNameRenamed) .HasColumnName(""CategoryName"") .IsRequired() @@ -246,9 +260,15 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity(entity => { + entity.HasKey(e => e.ProductIdRenamed); + entity.ToTable(""Product""); - entity.HasIndex(e => e.CategoryId, ""IX_Product_CategoryId""); + entity.HasIndex(e => e.CategoryIdRenamed, ""IX_Product_CategoryId""); + + entity.Property(e => e.ProductIdRenamed).HasColumnName(""ProductId""); + + entity.Property(e => e.CategoryIdRenamed).HasColumnName(""CategoryId""); entity.Property(e => e.ProductName) .IsRequired() @@ -264,7 +284,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.HasOne(d => d.Category) .WithMany(p => p.Product) - .HasForeignKey(d => d.CategoryId); + .HasForeignKey(d => d.CategoryIdRenamed); }); OnModelCreatingPartial(modelBuilder); diff --git a/test/Scaffolding.Handlebars.Tests/ExpectedEntities.cs b/test/Scaffolding.Handlebars.Tests/ExpectedEntities.cs index 1e392e2..af0b615 100644 --- a/test/Scaffolding.Handlebars.Tests/ExpectedEntities.cs +++ b/test/Scaffolding.Handlebars.Tests/ExpectedEntities.cs @@ -71,7 +71,7 @@ public CategoryRenamed() Products = new HashSet(); } - public int CategoryId { get; set; } + public int CategoryIdRenamed { get; set; } /// /// The name of a category @@ -91,12 +91,12 @@ namespace FakeNamespace { public partial class ProductRenamed { - public int ProductId { get; set; } + public int ProductIdRenamed { get; set; } public string ProductName { get; set; } public decimal? UnitPriceRenamed { get; set; } public bool Discontinued { get; set; } public byte[] RowVersion { get; set; } - public int? CategoryId { get; set; } + public int? CategoryIdRenamed { get; set; } public virtual CategoryRenamed Category { get; set; } } @@ -197,7 +197,8 @@ public CategoryRenamed() } [Key] - public int CategoryId { get; set; } + [Column(""CategoryId"")] + public int CategoryIdRenamed { get; set; } /// /// The name of a category @@ -227,7 +228,8 @@ namespace FakeNamespace public partial class ProductRenamed { [Key] - public int ProductId { get; set; } + [Column(""ProductId"")] + public int ProductIdRenamed { get; set; } [Required] [StringLength(40)] public string ProductName { get; set; } @@ -235,9 +237,10 @@ public partial class ProductRenamed public decimal? UnitPriceRenamed { get; set; } public bool Discontinued { get; set; } public byte[] RowVersion { get; set; } - public int? CategoryId { get; set; } + [Column(""CategoryId"")] + public int? CategoryIdRenamed { get; set; } - [ForeignKey(nameof(CategoryId))] + [ForeignKey(nameof(CategoryIdRenamed))] [InverseProperty(""Products"")] public virtual CategoryRenamed Category { get; set; } } diff --git a/test/Scaffolding.Handlebars.Tests/Helpers/HandlebarsTransformers.cs b/test/Scaffolding.Handlebars.Tests/Helpers/HandlebarsTransformers.cs index 9ff3020..9acb43b 100644 --- a/test/Scaffolding.Handlebars.Tests/Helpers/HandlebarsTransformers.cs +++ b/test/Scaffolding.Handlebars.Tests/Helpers/HandlebarsTransformers.cs @@ -5,13 +5,17 @@ namespace Scaffolding.Handlebars.Tests.Helpers { public static class HandlebarsTransformers { - static readonly Dictionary _entityTypeNameMappings = new(){ - {"Product","ProductRenamed"}, - {"Category","CategoryRenamed"} + static readonly Dictionary _entityTypeNameMappings = new() + { + { "Product","ProductRenamed" }, + { "Category","CategoryRenamed" } }; - static readonly Dictionary _entityPropertyNameMappings = new(){ - {"UnitPrice","UnitPriceRenamed"}, - {"CategoryName","CategoryNameRenamed"} + static readonly Dictionary _entityPropertyNameMappings = new() + { + { "ProductId", "ProductIdRenamed" }, + { "UnitPrice","UnitPriceRenamed"}, + { "CategoryId", "CategoryIdRenamed" }, + { "CategoryName","CategoryNameRenamed" } }; public static string MapEntityName(string entityName) => _entityTypeNameMappings.TryGetValue(entityName, out var nameOverride) ? nameOverride : entityName;