diff --git a/docs/ef/entity.md b/docs/ef/entity.md index 377b2d3a..71ab01d1 100644 --- a/docs/ef/entity.md +++ b/docs/ef/entity.md @@ -58,6 +58,8 @@ data: entityNaming: Singular relationshipNaming: Plural prefixWithSchemaName: false + enumMappings: + dbo.Table.Property: My.Namespace.Enum ``` ### name @@ -94,6 +96,10 @@ Configuration on how to generate relationship property names. Default: `Plural` Control if class names should be generated with schema name prefixed eg. dbo.MyTable = DboMyTable. Default: `false` +### enumMappings + +Dictionary for enum mappings instead of eg. `int` to replace a property use `schema.table.property: namespace.enum` + ### document Include XML documentation for the generated class. Default: `false` diff --git a/src/EntityFrameworkCore.Generator.Core/Metadata/Generation/Property.cs b/src/EntityFrameworkCore.Generator.Core/Metadata/Generation/Property.cs index d8df0118..db3f47f6 100644 --- a/src/EntityFrameworkCore.Generator.Core/Metadata/Generation/Property.cs +++ b/src/EntityFrameworkCore.Generator.Core/Metadata/Generation/Property.cs @@ -21,6 +21,7 @@ public class Property : ModelBase public DbType DataType { get; set; } public Type SystemType { get; set; } + public string EnumTypeName { get; set; } public bool? IsNullable { get; set; } diff --git a/src/EntityFrameworkCore.Generator.Core/ModelGenerator.cs b/src/EntityFrameworkCore.Generator.Core/ModelGenerator.cs index ad58371d..76fb8b6b 100644 --- a/src/EntityFrameworkCore.Generator.Core/ModelGenerator.cs +++ b/src/EntityFrameworkCore.Generator.Core/ModelGenerator.cs @@ -198,7 +198,10 @@ private void CreateProperties(Entity entity, IEnumerable columns property.StoreType = mapping.StoreType; property.NativeType = mapping.StoreTypeNameBase; property.DataType = mapping.DbType ?? DbType.AnsiString; + property.SystemType = mapping.ClrType; + var fullName = $"{column.Table.Schema}.{column.Table.Name}.{column.Name}"; + property.EnumTypeName = _options.Data.Entity.EnumMappings.GetValueOrDefault(fullName); property.Size = mapping.Size; property.IsProcessed = true; @@ -227,10 +230,16 @@ private void CreateRelationships(EntityContext entityContext, Entity entity, Dat private void CreateRelationship(EntityContext entityContext, Entity foreignEntity, DatabaseForeignKey tableKeySchema) { + _options.Variables.Set(VariableConstants.TableSchema, ToLegalName(tableKeySchema.PrincipalTable.Schema)); + _options.Variables.Set(VariableConstants.TableName, ToLegalName(tableKeySchema.PrincipalTable.Name)); + Entity primaryEntity = GetEntity(entityContext, tableKeySchema.PrincipalTable, false, false); + _options.Variables.Set(VariableConstants.TableSchema, ToLegalName(tableKeySchema.Table.Schema)); + _options.Variables.Set(VariableConstants.TableName, ToLegalName(tableKeySchema.Table.Name)); + string primaryName = primaryEntity.EntityClass; - string foreignName = foreignEntity.EntityClass; + string foreignName = tableKeySchema.Name.ToLower(); string relationshipName = tableKeySchema.Name; relationshipName = _namer.UniqueRelationshipName(relationshipName); @@ -266,9 +275,9 @@ private void CreateRelationship(EntityContext entityContext, Entity foreignEntit foreignRelationship.Entity = foreignEntity; foreignRelationship.Properties = new PropertyCollection(foreignMembers); - string prefix = GetMemberPrefix(foreignRelationship, primaryName, foreignName); + //string prefix = GetMemberPrefix(foreignRelationship, primaryName, foreignName); - string foreignPropertyName = ToPropertyName(foreignEntity.EntityClass, prefix + primaryName); + string foreignPropertyName = ToPropertyName(foreignEntity.EntityClass, /*prefix +*/ foreignName); foreignPropertyName = _namer.UniqueName(foreignEntity.EntityClass, foreignPropertyName); foreignRelationship.PropertyName = foreignPropertyName; @@ -297,7 +306,7 @@ private void CreateRelationship(EntityContext entityContext, Entity foreignEntit else primaryRelationship.Cardinality = Cardinality.Many; - string primaryPropertyName = prefix + foreignName; + string primaryPropertyName = /*prefix +*/ foreignName; if (!isOneToOne) primaryPropertyName = RelationshipName(primaryPropertyName); diff --git a/src/EntityFrameworkCore.Generator.Core/Options/EntityClassOptions.cs b/src/EntityFrameworkCore.Generator.Core/Options/EntityClassOptions.cs index e0f0e510..bd07ad01 100644 --- a/src/EntityFrameworkCore.Generator.Core/Options/EntityClassOptions.cs +++ b/src/EntityFrameworkCore.Generator.Core/Options/EntityClassOptions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; namespace EntityFrameworkCore.Generator.Options @@ -21,6 +22,7 @@ public EntityClassOptions(VariableDictionary variables, string prefix) RelationshipNaming = RelationshipNaming.Plural; EntityNaming = EntityNaming.Singular; PrefixWithSchemaName = false; + EnumMappings = new Dictionary(); } /// @@ -70,5 +72,8 @@ public string BaseClass /// [DefaultValue(false)] public bool PrefixWithSchemaName { get; set; } + + + public Dictionary EnumMappings { get; set; } } } \ No newline at end of file diff --git a/src/EntityFrameworkCore.Generator.Core/Templates/EntityClassTemplate.cs b/src/EntityFrameworkCore.Generator.Core/Templates/EntityClassTemplate.cs index df3e8ebf..ef793821 100644 --- a/src/EntityFrameworkCore.Generator.Core/Templates/EntityClassTemplate.cs +++ b/src/EntityFrameworkCore.Generator.Core/Templates/EntityClassTemplate.cs @@ -116,6 +116,9 @@ private void GenerateProperties() foreach (var property in _entity.Properties) { var propertyType = property.SystemType.ToNullableType(property.IsNullable == true); + if (property.EnumTypeName != null) + propertyType = property.EnumTypeName.ToNullableType(property.IsNullable == true) + (propertyType.Contains("?") ? "?" : ""); + var propertyName = property.PropertyName.ToSafeName(); if (Options.Data.Entity.Document) diff --git a/src/EntityFrameworkCore.Generator.Core/Templates/ModelClassTemplate.cs b/src/EntityFrameworkCore.Generator.Core/Templates/ModelClassTemplate.cs index eb772d26..58724e69 100644 --- a/src/EntityFrameworkCore.Generator.Core/Templates/ModelClassTemplate.cs +++ b/src/EntityFrameworkCore.Generator.Core/Templates/ModelClassTemplate.cs @@ -73,6 +73,9 @@ private void GenerateProperties() foreach (var property in _model.Properties) { var propertyType = property.SystemType.ToNullableType(property.IsNullable == true); + if (property.EnumTypeName != null) + propertyType = property.EnumTypeName.ToNullableType(property.IsNullable == true) + (propertyType.Contains("?") ? "?" : ""); + var propertyName = property.PropertyName.ToSafeName(); if (ShouldDocument()) diff --git a/src/EntityFrameworkCore.Generator/generation.yml b/src/EntityFrameworkCore.Generator/generation.yml new file mode 100644 index 00000000..a86b65a5 --- /dev/null +++ b/src/EntityFrameworkCore.Generator/generation.yml @@ -0,0 +1,115 @@ +--- +#---------------------------------# +# https://efg.loresoft.com/en/latest/ +# project section - Used for shared variables through out the configuration file +#---------------------------------# +project: + # the root namespace for the project + namespace: 'api' + # the root directory for the project + directory: ./ + +#---------------------------------# +# data section - Used for configuring database connections +#---------------------------------# +database: + # the connection string to the database + connectionString: 'Server=hulk;Initial Catalog=SISTEMAS;Integrated Security=True;MultipleActiveResultSets=True' + # the database provider name. Default: SqlServer + provider: SqlServer + + # config name to read the connection string from the user secrets file + connectionName: 'ConnectionStrings:Generator' + # the user secret identifier, can be shared with .net core project + userSecretsId: '984ef0cf-2b22-4fd1-876d-e01489da4c1f' + + # tables to include or empty to include all + tables: + - dbo.LOG_SOP10200 + # schemas to include or empty to include all + schemas: + # table naming hint for how existing tables are named. Default: Singular + tableNaming: Singular + +#---------------------------------# +# data section - controls the generated files for Entity Framework +#---------------------------------# +data: + # data context file configuration + context: + name: 'ContextoDb' # the data context class name + baseClass: DbContext # the data context base class name + namespace: '{Project.Namespace}.Data' # the data context namespace + directory: '{Project.Directory}/Data' # the data context output directory + + # how to generate names for the DbSet properties on the data context. Default: Plural + propertyNaming: Suffix + #include XML documentation + document: false + + # entity class file configuration + entity: + namespace: '{Project.Namespace}.Data.Entities' # the entity class namespace + directory: '{Project.Directory}/Data/Entities' # the entity class output directory + + # how to generate entity class names from the table name. Default: Singular + entityNaming: Preserve + relationshipNaming: Suffix + name: '{Table.Schema}{Table.Name}Entity' + #baseClass: BaseEntity + + # how to generate relationship collections names for the entity. Default: Plural + #relationshipNaming: Suffix + #include XML documentation + document: false + + # Generate class names with prefixed schema name eg. dbo.MyTable = DboMyTable + # prefixWithSchemaName: true + enumMappings: + dbo.usuario.activo: api.Data.CustomEnums.YesNoEnum + mapping: + namespace: '{Project.Namespace}.Data.Mapping' # the mapping class namespace + directory: '{Project.Directory}/Data/Mapping' # the mapping class output directory + #include XML documentation + document: false +#---------------------------------# +# model section - controls the optional view model generation +#---------------------------------# +model: + # shared options between read, create and update models + shared: + namespace: '{Project.Namespace}.Data.Models' # the model class namespace + directory: '{Project.Directory}/Data/Models' # the mapping class output directory + # regular expression of entities and properties to exclude in all models + exclude: + # list of regular expressions of entity names + entities: + + # list of regular expressions of property names, source is Entity.Property + properties: + # update view model class configuration + update: + generate: true # generate update model class files + name: '{Table.Schema}_{Table.Name}Model' # the update model class name + #baseClass: EntityUpdateModel # the update model base class + namespace: '{Project.Namespace}.Data.Models' + directory: '{Project.Directory}/Data/Models' + exclude: + entities: [] + properties: [] + + # AutoMapper class configuration + mapper: + generate: true + name: '{Table.Schema}_{Table.Name}Profile' + baseClass: Profile + namespace: '{Project.Namespace}.Data.AutoMapper' + directory: '{Project.Directory}/Data/AutoMapper' + + # FluentValidation class configuration + validator: + generate: true + name: '{Model.Name}Validator' + baseClass: 'AbstractValidator<{Model.Name}>' + namespace: '{Project.Namespace}.Data.ModelValidation' + directory: '{Project.Directory}/Data/ModelValidation' diff --git a/test/EntityFrameworkCore.Generator.Core.Tests/ModelGeneratorTests.cs b/test/EntityFrameworkCore.Generator.Core.Tests/ModelGeneratorTests.cs index b5d04ddd..1f972b9f 100644 --- a/test/EntityFrameworkCore.Generator.Core.Tests/ModelGeneratorTests.cs +++ b/test/EntityFrameworkCore.Generator.Core.Tests/ModelGeneratorTests.cs @@ -161,8 +161,7 @@ public void GenerateWithSymbolInDatabaseName() var databaseTable = new DatabaseTable { Database = databaseModel, - Name = "Test+Error", - Schema = "dbo" + Name = "Test+Error" }; databaseModel.Tables.Add(databaseTable); @@ -199,8 +198,7 @@ public void GenerateWithAllNumberColumnName() var testTable = new DatabaseTable { Database = databaseModel, - Name = "TestTable", - Schema = "dbo" + Name = "TestTable" }; databaseModel.Tables.Add(testTable); @@ -460,107 +458,94 @@ public void GenerateWithPrefixedSchemaName() } [Fact] - public void GenerateIgnoreTable() + public void GenerateWithEnumMappings() { - var generatorOptions = new GeneratorOptions(); - generatorOptions.Database.Exclude.Add(new MatchOptions{ Expression = @"dbo\.ExpressionTable$" }); - generatorOptions.Database.Exclude.Add(new MatchOptions { Exact = @"dbo.DirectTable" }); - generatorOptions.Model.Read.Generate = true; - generatorOptions.Model.Create.Generate = true; - generatorOptions.Model.Update.Generate = true; - generatorOptions.Model.Validator.Generate = true; - generatorOptions.Model.Mapper.Generate = true; + var generatorOptions = new GeneratorOptions(); + generatorOptions.Data.Entity.PrefixWithSchemaName = true; + generatorOptions.Data.Entity.EnumMappings = new Dictionary { + {"tst.TestTable.TestEnumType", "My.NameSpace.MyTestEnum" } + }; var databaseModel = new DatabaseModel { DatabaseName = "TestDatabase", DefaultSchema = "dbo" }; - var testTable = new DatabaseTable + var testTableDbo = new DatabaseTable { Database = databaseModel, Name = "TestTable", Schema = "dbo" }; - databaseModel.Tables.Add(testTable); + var testTableTst = new DatabaseTable + { + Database = databaseModel, + Name = "TestTable", + Schema = "tst" + }; + databaseModel.Tables.Add(testTableDbo); + databaseModel.Tables.Add(testTableTst); - var identifierColumn = new DatabaseColumn + var identifierColumnDbo = new DatabaseColumn { - Table = testTable, + Table = testTableDbo, Name = "Id", IsNullable = false, StoreType = "int" }; - testTable.Columns.Add(identifierColumn); - - var nameColumn = new DatabaseColumn - { - Table = testTable, - Name = "Name", - IsNullable = true, - StoreType = "varchar(50)" - }; - testTable.Columns.Add(nameColumn); - - var expressionTable = new DatabaseTable - { - Database = databaseModel, - Name = "ExpressionTable", - Schema = "dbo" - }; - expressionTable.Columns.Add(new DatabaseColumn + var identifierColumnTst = new DatabaseColumn { - Table = testTable, + Table = testTableTst, Name = "Id", IsNullable = false, StoreType = "int" - }); - databaseModel.Tables.Add(expressionTable); + }; + testTableDbo.Columns.Add(identifierColumnDbo); + testTableTst.Columns.Add(identifierColumnTst); - var directTable = new DatabaseTable + var nameColumnDbo = new DatabaseColumn { - Database = databaseModel, - Name = "DirectTable", - Schema = "dbo" + Table = testTableDbo, + Name = "Name", + IsNullable = true, + StoreType = "varchar(50)" }; - directTable.Columns.Add(new DatabaseColumn + var enumColumnTst = new DatabaseColumn { - Table = testTable, - Name = "Id", - IsNullable = false, + Table = testTableTst, + Name = "TestEnumType", + IsNullable = true, StoreType = "int" - }); - databaseModel.Tables.Add(directTable); + }; + testTableDbo.Columns.Add(nameColumnDbo); + testTableTst.Columns.Add(enumColumnTst); var generator = new ModelGenerator(NullLoggerFactory.Instance); var typeMappingSource = CreateTypeMappingSource(); var result = generator.Generate(generatorOptions, databaseModel, typeMappingSource); + result.ContextClass.Should().Be("TestDatabaseContext"); result.ContextNamespace.Should().Be("TestDatabase.Data"); - result.Entities.Count.Should().Be(1); + result.Entities.Count.Should().Be(2); var firstEntity = result.Entities[0]; firstEntity.TableName.Should().Be("TestTable"); firstEntity.TableSchema.Should().Be("dbo"); - firstEntity.EntityClass.Should().Be("TestTable"); + firstEntity.EntityClass.Should().Be("DboTestTable"); firstEntity.EntityNamespace.Should().Be("TestDatabase.Data.Entities"); - firstEntity.MappingClass.Should().Be("TestTableMap"); + firstEntity.MappingClass.Should().Be("DboTestTableMap"); firstEntity.MappingNamespace.Should().Be("TestDatabase.Data.Mapping"); - firstEntity.MapperClass.Should().Be("TestTableProfile"); - firstEntity.MapperNamespace.Should().Be("TestDatabase.Domain.Mapping"); - firstEntity.Properties.Count.Should().Be(2); - firstEntity.Models.Count.Should().Be(3); - - var firstModel = firstEntity.Models[0]; - firstModel.ModelClass.Should().StartWith("TestTable"); - firstModel.ModelClass.Should().EndWith("Model"); - firstModel.ModelNamespace.Should().Be("TestDatabase.Domain.Models"); - firstModel.ValidatorClass.Should().StartWith("TestTable"); - firstModel.ValidatorClass.Should().EndWith("Validator"); - firstModel.ValidatorNamespace.Should().Be("TestDatabase.Domain.Validation"); + var secondEntity = result.Entities[1]; + secondEntity.TableName.Should().Be("TestTable"); + secondEntity.TableSchema.Should().Be("tst"); + secondEntity.EntityClass.Should().Be("TstTestTable"); + secondEntity.EntityNamespace.Should().Be("TestDatabase.Data.Entities"); + secondEntity.MappingClass.Should().Be("TstTestTableMap"); + secondEntity.MappingNamespace.Should().Be("TestDatabase.Data.Mapping"); + secondEntity.Properties.First(x => x.PropertyName == "TestEnumType").EnumTypeName.Should().Be("My.NameSpace.MyTestEnum"); }