diff --git a/src/Core/RevEng.Core.80/Routines/Functions/FunctionScaffolder.cs b/src/Core/RevEng.Core.80/Routines/Functions/FunctionScaffolder.cs index f9239c2f0..cec024d06 100644 --- a/src/Core/RevEng.Core.80/Routines/Functions/FunctionScaffolder.cs +++ b/src/Core/RevEng.Core.80/Routines/Functions/FunctionScaffolder.cs @@ -1,29 +1,242 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Scaffolding; +using NetTopologySuite.Geometries; +using RevEng.Common; using RevEng.Core.Abstractions; using RevEng.Core.Abstractions.Metadata; using RevEng.Core.Routines.Extensions; namespace RevEng.Core.Routines.Functions { - public abstract class FunctionScaffolder : RoutineScaffolder + public abstract class FunctionScaffolder : IRoutineScaffolder { +#pragma warning disable SA1401 // Fields should be private + internal readonly ICSharpHelper Code; + internal IndentedStringBuilder Sb; +#pragma warning restore SA1401 // Fields should be private + private readonly IClrTypeMapper typeMapper; + protected FunctionScaffolder([System.Diagnostics.CodeAnalysis.NotNull] ICSharpHelper code, IClrTypeMapper typeMapper) - : base(code, typeMapper) { + ArgumentNullException.ThrowIfNull(code); + + Code = code; + this.typeMapper = typeMapper; } - public new string FileNameSuffix { get; set; } + public string FileNameSuffix { get; set; } - public new SavedModelFiles Save(ScaffoldedModel scaffoldedModel, string outputDir, string nameSpaceValue, bool useAsyncCalls) + public SavedModelFiles Save(ScaffoldedModel scaffoldedModel, string outputDir, string nameSpaceValue, bool useAsyncCalls) { - return base.Save(scaffoldedModel, outputDir, nameSpaceValue, useAsyncCalls); + ArgumentNullException.ThrowIfNull(scaffoldedModel); + + Directory.CreateDirectory(outputDir); + + var contextPath = Path.GetFullPath(Path.Combine(outputDir, scaffoldedModel.ContextFile.Path)); + var path = Path.GetDirectoryName(contextPath); + if (path != null) + { + Directory.CreateDirectory(path); + File.WriteAllText(contextPath, scaffoldedModel.ContextFile.Code, Encoding.UTF8); + } + + var additionalFiles = new List(); + + foreach (var entityTypeFile in scaffoldedModel.AdditionalFiles) + { + var additionalFilePath = Path.Combine(outputDir, entityTypeFile.Path); + var addpath = Path.GetDirectoryName(additionalFilePath); + if (addpath != null) + { + Directory.CreateDirectory(addpath); + File.WriteAllText(additionalFilePath, entityTypeFile.Code, Encoding.UTF8); + additionalFiles.Add(additionalFilePath); + } + } + + return new SavedModelFiles(contextPath, additionalFiles); } - public new ScaffoldedModel ScaffoldModel(RoutineModel model, ModuleScaffolderOptions scaffolderOptions, List schemas, ref List errors) + public ScaffoldedModel ScaffoldModel(RoutineModel model, ModuleScaffolderOptions scaffolderOptions, List schemas, ref List errors) + { + ArgumentNullException.ThrowIfNull(model); + + ArgumentNullException.ThrowIfNull(errors); + + ArgumentNullException.ThrowIfNull(scaffolderOptions); + + var result = new ScaffoldedModel(); + var path = string.Empty; + + errors.AddRange(model.Errors); + + schemas = schemas ?? new List(); + + foreach (var routine in model.Routines.Where(r => string.IsNullOrEmpty(r.MappedType) && (!(r is Function f) || !f.IsScalar))) + { + var i = 1; + + foreach (var resultSet in routine.Results) + { + if (routine.NoResultSet) + { + continue; + } + + var suffix = string.Empty; + if (routine.Results.Count > 1) + { + suffix = $"{i++}"; + } + + var typeName = ScaffoldHelper.GenerateIdentifierName(routine, model, Code, scaffolderOptions.UsePascalIdentifiers) + "Result" + suffix; + + var classContent = WriteResultClass(resultSet, scaffolderOptions, typeName, routine.Schema); + + if (!string.IsNullOrEmpty(routine.Schema)) + { + schemas.Add($"{routine.Schema}Schema"); + } + + path = scaffolderOptions.UseSchemaFolders + ? Path.Combine(routine.Schema, $"{typeName}.cs") + : $"{typeName}.cs"; +#if CORE90 + result.AdditionalFiles.Add(new ScaffoldedFile(path, classContent)); +#else + result.AdditionalFiles.Add(new ScaffoldedFile + { + Code = classContent, + Path = path, + }); +#endif + } + } + + var dbContext = WriteDbContext(scaffolderOptions, model, schemas.Distinct().ToList()); + + path = Path.GetFullPath(Path.Combine(scaffolderOptions.ContextDir, scaffolderOptions.ContextName + $"{FileNameSuffix}.cs")); +#if CORE90 + result.ContextFile = new ScaffoldedFile(path, dbContext); +#else + result.ContextFile = new ScaffoldedFile + { + Code = dbContext, + Path = path, + }; +#endif + return result; + } + + protected abstract string WriteDbContext(ModuleScaffolderOptions scaffolderOptions, RoutineModel model, List schemas); + + private string WriteResultClass(List resultElements, ModuleScaffolderOptions options, string name, string schemaName) { - return base.ScaffoldModel(model, scaffolderOptions, schemas, ref errors); + var @namespace = options.ModelNamespace; + + Sb = new IndentedStringBuilder(); + + Sb.AppendLine(PathHelper.Header); + + if (resultElements.Exists(p => typeMapper.GetClrType(p) == typeof(Geometry))) + { + Sb.AppendLine("using NetTopologySuite.Geometries;"); + } + + Sb.AppendLine("using System;"); + Sb.AppendLine("using System.Collections.Generic;"); + if (options.UseDecimalDataAnnotation) + { + Sb.AppendLine("using System.ComponentModel.DataAnnotations;"); + } + + Sb.AppendLine("using System.ComponentModel.DataAnnotations.Schema;"); + Sb.AppendLine(); + + if (options.NullableReferences) + { + Sb.AppendLine("#nullable enable"); + Sb.AppendLine(); + } + + Sb.AppendLine($"namespace {@namespace}{(options.UseSchemaNamespaces ? $".{schemaName}Schema" : string.Empty)}"); + Sb.AppendLine("{"); + + using (Sb.Indent()) + { + GenerateClass(resultElements, name, options.NullableReferences, options.UseDecimalDataAnnotation, options.UsePascalIdentifiers); + } + + Sb.AppendLine("}"); + + return Sb.ToString(); + } + + private void GenerateClass(List resultElements, string name, bool nullableReferences, bool useDecimalDataAnnotation, bool usePascalCase) + { + Sb.AppendLine($"public partial class {name}"); + Sb.AppendLine("{"); + + using (Sb.Indent()) + { + GenerateProperties(resultElements, nullableReferences, useDecimalDataAnnotation, usePascalCase); + } + + Sb.AppendLine("}"); + } + + private void GenerateProperties(List resultElements, bool nullableReferences, bool useDecimalDataAnnotation, bool usePascalCase) + { + foreach (var property in resultElements.OrderBy(e => e.Ordinal)) + { + var propertyNames = ScaffoldHelper.GeneratePropertyName(property.Name, Code, usePascalCase); + + if (property.StoreType == "decimal" && useDecimalDataAnnotation) + { + Sb.AppendLine($"[Column(\"{property.Name}\", TypeName = \"{property.StoreType}({property.Precision},{property.Scale})\")]"); + } + else + { + if (!string.IsNullOrEmpty(propertyNames.Item2)) + { + Sb.AppendLine(propertyNames.Item2); + } + } + + if (useDecimalDataAnnotation + && ((property.StoreType.StartsWith("varchar", StringComparison.OrdinalIgnoreCase) + || property.StoreType.StartsWith("nvarchar", StringComparison.OrdinalIgnoreCase)) + && property.MaxLength > 0)) + { + var maxLength = property.StoreType.StartsWith("varchar", StringComparison.OrdinalIgnoreCase) ? property.MaxLength : property.MaxLength / 2; + + Sb.AppendLine($"[StringLength({maxLength})]"); + } + + var propertyType = typeMapper.GetClrType(property); + var nullableAnnotation = string.Empty; + var defaultAnnotation = string.Empty; + + if (nullableReferences && !propertyType.IsValueType) + { + if (property.Nullable) + { + nullableAnnotation = "?"; + } + else + { + defaultAnnotation = $" = default!;"; + } + } + + Sb.AppendLine($"public {Code.Reference(propertyType)}{nullableAnnotation} {propertyNames.Item1} {{ get; set; }}{defaultAnnotation}"); + } } } } diff --git a/src/Core/RevEng.Core.80/Routines/Functions/SqlServerFunctionScaffolder.cs b/src/Core/RevEng.Core.80/Routines/Functions/SqlServerFunctionScaffolder.cs index a2ecac3b1..18d1205c9 100644 --- a/src/Core/RevEng.Core.80/Routines/Functions/SqlServerFunctionScaffolder.cs +++ b/src/Core/RevEng.Core.80/Routines/Functions/SqlServerFunctionScaffolder.cs @@ -21,10 +21,6 @@ public SqlServerFunctionScaffolder([NotNull] ICSharpHelper code, IClrTypeMapper FileNameSuffix = ".Functions"; } - protected override void GenerateProcedure(Routine procedure, RoutineModel model, bool signatureOnly, bool useAsyncCalls, bool usePascalCase) - { - } - protected override string WriteDbContext(ModuleScaffolderOptions scaffolderOptions, RoutineModel model, List schemas) { ArgumentNullException.ThrowIfNull(scaffolderOptions); diff --git a/src/Core/RevEng.Core.80/Routines/Procedures/ProcedureScaffolder.cs b/src/Core/RevEng.Core.80/Routines/Procedures/ProcedureScaffolder.cs index 718004656..47714ae20 100644 --- a/src/Core/RevEng.Core.80/Routines/Procedures/ProcedureScaffolder.cs +++ b/src/Core/RevEng.Core.80/Routines/Procedures/ProcedureScaffolder.cs @@ -1,8 +1,12 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Scaffolding; +using NetTopologySuite.Geometries; using RevEng.Common; using RevEng.Core.Abstractions; using RevEng.Core.Abstractions.Metadata; @@ -10,24 +14,233 @@ namespace RevEng.Core.Routines.Procedures { - public abstract class ProcedureScaffolder : RoutineScaffolder + public abstract class ProcedureScaffolder : IRoutineScaffolder { +#pragma warning disable SA1401 // Fields should be private + internal readonly ICSharpHelper Code; + internal IndentedStringBuilder Sb; +#pragma warning restore SA1401 // Fields should be private + private readonly IClrTypeMapper typeMapper; + protected ProcedureScaffolder([System.Diagnostics.CodeAnalysis.NotNull] ICSharpHelper code, IClrTypeMapper typeMapper) - : base(code, typeMapper) { + ArgumentNullException.ThrowIfNull(code); + + Code = code; + this.typeMapper = typeMapper; } - public new SavedModelFiles Save(ScaffoldedModel scaffoldedModel, string outputDir, string nameSpaceValue, bool useAsyncCalls) + public string FileNameSuffix { get; set; } + + public string ProviderUsing { get; set; } + + public SavedModelFiles Save(ScaffoldedModel scaffoldedModel, string outputDir, string nameSpaceValue, bool useAsyncCalls) { - return base.Save(scaffoldedModel, outputDir, nameSpaceValue, useAsyncCalls); + ArgumentNullException.ThrowIfNull(scaffoldedModel); + + Directory.CreateDirectory(outputDir); + + var contextPath = Path.GetFullPath(Path.Combine(outputDir, scaffoldedModel.ContextFile.Path)); + var path = Path.GetDirectoryName(contextPath); + if (path != null) + { + Directory.CreateDirectory(path); + File.WriteAllText(contextPath, scaffoldedModel.ContextFile.Code, Encoding.UTF8); + } + + var additionalFiles = new List(); + + foreach (var entityTypeFile in scaffoldedModel.AdditionalFiles) + { + var additionalFilePath = Path.Combine(outputDir, entityTypeFile.Path); + var addpath = Path.GetDirectoryName(additionalFilePath); + if (addpath != null) + { + Directory.CreateDirectory(addpath); + File.WriteAllText(additionalFilePath, entityTypeFile.Code, Encoding.UTF8); + additionalFiles.Add(additionalFilePath); + } + } + + return new SavedModelFiles(contextPath, additionalFiles); } - public new ScaffoldedModel ScaffoldModel(RoutineModel model, ModuleScaffolderOptions scaffolderOptions, List schemas, ref List errors) + public ScaffoldedModel ScaffoldModel(RoutineModel model, ModuleScaffolderOptions scaffolderOptions, List schemas, ref List errors) { - return base.ScaffoldModel(model, scaffolderOptions, schemas, ref errors); + ArgumentNullException.ThrowIfNull(model); + + ArgumentNullException.ThrowIfNull(errors); + + ArgumentNullException.ThrowIfNull(scaffolderOptions); + + var result = new ScaffoldedModel(); + var path = string.Empty; + + errors.AddRange(model.Errors); + + schemas = schemas ?? new List(); + + foreach (var routine in model.Routines.Where(r => string.IsNullOrEmpty(r.MappedType) && (!(r is Function f) || !f.IsScalar))) + { + var i = 1; + + foreach (var resultSet in routine.Results) + { + if (routine.NoResultSet) + { + continue; + } + + var suffix = string.Empty; + if (routine.Results.Count > 1) + { + suffix = $"{i++}"; + } + + var typeName = ScaffoldHelper.GenerateIdentifierName(routine, model, Code, scaffolderOptions.UsePascalIdentifiers) + "Result" + suffix; + + var classContent = WriteResultClass(resultSet, scaffolderOptions, typeName, routine.Schema); + + if (!string.IsNullOrEmpty(routine.Schema)) + { + schemas.Add($"{routine.Schema}Schema"); + } + + path = scaffolderOptions.UseSchemaFolders + ? Path.Combine(routine.Schema, $"{typeName}.cs") + : $"{typeName}.cs"; +#if CORE90 + result.AdditionalFiles.Add(new ScaffoldedFile(path, classContent)); +#else + result.AdditionalFiles.Add(new ScaffoldedFile + { + Code = classContent, + Path = path, + }); +#endif + } + } + + var dbContextInterface = WriteDbContextInterface(scaffolderOptions, model, schemas.Distinct().ToList()); + + if (!string.IsNullOrEmpty(dbContextInterface)) + { + path = Path.GetFullPath(Path.Combine(scaffolderOptions.ContextDir, $"I{scaffolderOptions.ContextName}{FileNameSuffix}.cs")); +#if CORE90 + result.AdditionalFiles.Add(new ScaffoldedFile(path, dbContextInterface)); +#else + result.AdditionalFiles.Add(new ScaffoldedFile + { + Code = dbContextInterface, + Path = path, + }); +#endif + } + + var dbContext = WriteDbContext(scaffolderOptions, model, schemas.Distinct().ToList()); + + path = Path.GetFullPath(Path.Combine(scaffolderOptions.ContextDir, scaffolderOptions.ContextName + $"{FileNameSuffix}.cs")); + +#if CORE90 + result.ContextFile = new ScaffoldedFile(path, dbContext); +#else + result.ContextFile = new ScaffoldedFile + { + Code = dbContext, + Path = path, + }; +#endif + return result; + } + + protected abstract void GenerateProcedure(Routine procedure, RoutineModel model, bool signatureOnly, bool useAsyncCalls, bool usePascalCase); + + private List CreateUsings(ModuleScaffolderOptions scaffolderOptions, RoutineModel model, List schemas) + { + var usings = new List() + { + "using Microsoft.EntityFrameworkCore", + "using System", + "using System.Collections.Generic", + "using System.Data", + "using System.Threading", + "using System.Threading.Tasks", + $"using {scaffolderOptions.ModelNamespace}", + }; + + usings.Add(ProviderUsing); + + if (scaffolderOptions.UseSchemaNamespaces) + { + schemas.Distinct().OrderBy(s => s).ToList().ForEach(schema => usings.Add($"using {scaffolderOptions.ModelNamespace}.{schema}")); + } + + if (model.Routines.Exists(r => r.SupportsMultipleResultSet)) + { + usings.AddRange(new List() + { + "using Dapper", + "using Microsoft.EntityFrameworkCore.Storage", + "using System.Linq", + }); + } + + if (model.Routines.SelectMany(r => r.Parameters).Any(p => typeMapper.GetClrType(p) == typeof(Geometry))) + { + usings.AddRange(new List() + { + "using NetTopologySuite.Geometries", + }); + } + + usings.Sort(); + return usings; } - protected override string WriteDbContext(ModuleScaffolderOptions scaffolderOptions, RoutineModel model, List schemas) + private string WriteDbContextInterface(ModuleScaffolderOptions scaffolderOptions, RoutineModel model, List schemas) + { + ArgumentNullException.ThrowIfNull(scaffolderOptions); + + ArgumentNullException.ThrowIfNull(model); + + Sb = new IndentedStringBuilder(); + + Sb.AppendLine(PathHelper.Header); + Sb.AppendLine("#nullable disable"); // procedure parameters are always nullable + + var usings = CreateUsings(scaffolderOptions, model, schemas); + + foreach (var statement in usings) + { + Sb.AppendLine($"{statement};"); + } + + Sb.AppendLine(); + Sb.AppendLine($"namespace {scaffolderOptions.ContextNamespace}"); + Sb.AppendLine("{"); + + using (Sb.Indent()) + { + Sb.AppendLine($"public partial interface I{scaffolderOptions.ContextName}{FileNameSuffix}"); + Sb.AppendLine("{"); + using (Sb.Indent()) + { + foreach (var procedure in model.Routines) + { + GenerateProcedure(procedure, model, true, scaffolderOptions.UseAsyncCalls, scaffolderOptions.UsePascalIdentifiers); + Sb.AppendLine(";"); + } + } + + Sb.AppendLine("}"); + } + + Sb.AppendLine("}"); + + return Sb.ToString(); + } + + private string WriteDbContext(ModuleScaffolderOptions scaffolderOptions, RoutineModel model, List schemas) { ArgumentNullException.ThrowIfNull(scaffolderOptions); @@ -192,5 +405,109 @@ private void GenerateDapperSupport(bool useAsyncCalls) Sb.AppendLine("}"); } } + + private string WriteResultClass(List resultElements, ModuleScaffolderOptions options, string name, string schemaName) + { + var @namespace = options.ModelNamespace; + + Sb = new IndentedStringBuilder(); + + Sb.AppendLine(PathHelper.Header); + + if (resultElements.Exists(p => typeMapper.GetClrType(p) == typeof(Geometry))) + { + Sb.AppendLine("using NetTopologySuite.Geometries;"); + } + + Sb.AppendLine("using System;"); + Sb.AppendLine("using System.Collections.Generic;"); + + if (options.UseDecimalDataAnnotation) + { + Sb.AppendLine("using System.ComponentModel.DataAnnotations;"); + } + + Sb.AppendLine("using System.ComponentModel.DataAnnotations.Schema;"); + Sb.AppendLine(); + + if (options.NullableReferences) + { + Sb.AppendLine("#nullable enable"); + Sb.AppendLine(); + } + + Sb.AppendLine($"namespace {@namespace}{(options.UseSchemaNamespaces ? $".{schemaName}Schema" : string.Empty)}"); + Sb.AppendLine("{"); + + using (Sb.Indent()) + { + GenerateClass(resultElements, name, options.NullableReferences, options.UseDecimalDataAnnotation, options.UsePascalIdentifiers); + } + + Sb.AppendLine("}"); + + return Sb.ToString(); + } + + private void GenerateClass(List resultElements, string name, bool nullableReferences, bool useDecimalDataAnnotation, bool usePascalCase) + { + Sb.AppendLine($"public partial class {name}"); + Sb.AppendLine("{"); + + using (Sb.Indent()) + { + GenerateProperties(resultElements, nullableReferences, useDecimalDataAnnotation, usePascalCase); + } + + Sb.AppendLine("}"); + } + + private void GenerateProperties(List resultElements, bool nullableReferences, bool useDecimalDataAnnotation, bool usePascalCase) + { + foreach (var property in resultElements.OrderBy(e => e.Ordinal)) + { + var propertyNames = ScaffoldHelper.GeneratePropertyName(property.Name, Code, usePascalCase); + + if (property.StoreType == "decimal" && useDecimalDataAnnotation) + { + Sb.AppendLine($"[Column(\"{property.Name}\", TypeName = \"{property.StoreType}({property.Precision},{property.Scale})\")]"); + } + else + { + if (!string.IsNullOrEmpty(propertyNames.Item2)) + { + Sb.AppendLine(propertyNames.Item2); + } + } + + if (useDecimalDataAnnotation + && ((property.StoreType.StartsWith("varchar", StringComparison.OrdinalIgnoreCase) + || property.StoreType.StartsWith("nvarchar", StringComparison.OrdinalIgnoreCase)) + && property.MaxLength > 0)) + { + var maxLength = property.StoreType.StartsWith("varchar", StringComparison.OrdinalIgnoreCase) ? property.MaxLength : property.MaxLength / 2; + + Sb.AppendLine($"[StringLength({maxLength})]"); + } + + var propertyType = typeMapper.GetClrType(property); + var nullableAnnotation = string.Empty; + var defaultAnnotation = string.Empty; + + if (nullableReferences && !propertyType.IsValueType) + { + if (property.Nullable) + { + nullableAnnotation = "?"; + } + else + { + defaultAnnotation = $" = default!;"; + } + } + + Sb.AppendLine($"public {Code.Reference(propertyType)}{nullableAnnotation} {propertyNames.Item1} {{ get; set; }}{defaultAnnotation}"); + } + } } } diff --git a/src/Core/RevEng.Core.80/Routines/RoutineScaffolder.cs b/src/Core/RevEng.Core.80/Routines/RoutineScaffolder.cs deleted file mode 100644 index 61775be65..000000000 --- a/src/Core/RevEng.Core.80/Routines/RoutineScaffolder.cs +++ /dev/null @@ -1,350 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using Microsoft.EntityFrameworkCore.Design; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Scaffolding; -using NetTopologySuite.Geometries; -using RevEng.Common; -using RevEng.Core.Abstractions; -using RevEng.Core.Abstractions.Metadata; -using RevEng.Core.Routines.Extensions; - -namespace RevEng.Core.Routines -{ - public abstract class RoutineScaffolder : IRoutineScaffolder - { -#pragma warning disable SA1401 // Fields should be private - internal readonly ICSharpHelper Code; - internal IndentedStringBuilder Sb; -#pragma warning restore SA1401 // Fields should be private - private readonly IClrTypeMapper typeMapper; - - protected RoutineScaffolder([System.Diagnostics.CodeAnalysis.NotNull] ICSharpHelper code, IClrTypeMapper typeMapper) - { - ArgumentNullException.ThrowIfNull(code); - ArgumentNullException.ThrowIfNull(typeMapper); - - Code = code; - this.typeMapper = typeMapper; - } - - public string ProviderUsing { get; set; } - - public string FileNameSuffix { get; set; } - - public SavedModelFiles Save(ScaffoldedModel scaffoldedModel, string outputDir, string nameSpaceValue, bool useAsyncCalls) - { - ArgumentNullException.ThrowIfNull(scaffoldedModel); - - Directory.CreateDirectory(outputDir); - - var contextPath = Path.GetFullPath(Path.Combine(outputDir, scaffoldedModel.ContextFile.Path)); - var path = Path.GetDirectoryName(contextPath); - if (path != null) - { - Directory.CreateDirectory(path); - File.WriteAllText(contextPath, scaffoldedModel.ContextFile.Code, Encoding.UTF8); - } - - var additionalFiles = new List(); - - foreach (var entityTypeFile in scaffoldedModel.AdditionalFiles) - { - var additionalFilePath = Path.Combine(outputDir, entityTypeFile.Path); - var addpath = Path.GetDirectoryName(additionalFilePath); - if (addpath != null) - { - Directory.CreateDirectory(addpath); - File.WriteAllText(additionalFilePath, entityTypeFile.Code, Encoding.UTF8); - additionalFiles.Add(additionalFilePath); - } - } - - return new SavedModelFiles(contextPath, additionalFiles); - } - - public ScaffoldedModel ScaffoldModel(RoutineModel model, ModuleScaffolderOptions scaffolderOptions, List schemas, ref List errors) - { - ArgumentNullException.ThrowIfNull(model); - - ArgumentNullException.ThrowIfNull(errors); - - ArgumentNullException.ThrowIfNull(scaffolderOptions); - - var result = new ScaffoldedModel(); - var path = string.Empty; - - errors.AddRange(model.Errors); - - schemas = schemas ?? new List(); - - foreach (var routine in model.Routines.Where(r => string.IsNullOrEmpty(r.MappedType) && (!(r is Function f) || !f.IsScalar))) - { - var i = 1; - - foreach (var resultSet in routine.Results) - { - if (routine.NoResultSet) - { - continue; - } - - var suffix = string.Empty; - if (routine.Results.Count > 1) - { - suffix = $"{i++}"; - } - - var typeName = ScaffoldHelper.GenerateIdentifierName(routine, model, Code, scaffolderOptions.UsePascalIdentifiers) + "Result" + suffix; - - var classContent = WriteResultClass(resultSet, scaffolderOptions, typeName, routine.Schema); - - if (!string.IsNullOrEmpty(routine.Schema)) - { - schemas.Add($"{routine.Schema}Schema"); - } - - path = scaffolderOptions.UseSchemaFolders - ? Path.Combine(routine.Schema, $"{typeName}.cs") - : $"{typeName}.cs"; -#if CORE90 - result.AdditionalFiles.Add(new ScaffoldedFile(path, classContent)); -#else - result.AdditionalFiles.Add(new ScaffoldedFile - { - Code = classContent, - Path = path, - }); -#endif - } - } - - var dbContextInterface = WriteDbContextInterface(scaffolderOptions, model, schemas.Distinct().ToList()); - - if (!string.IsNullOrEmpty(dbContextInterface)) - { - path = Path.GetFullPath(Path.Combine(scaffolderOptions.ContextDir, $"I{scaffolderOptions.ContextName}{FileNameSuffix}.cs")); -#if CORE90 - result.AdditionalFiles.Add(new ScaffoldedFile(path, dbContextInterface)); -#else - result.AdditionalFiles.Add(new ScaffoldedFile - { - Code = dbContextInterface, - Path = path, - }); -#endif - } - - var dbContext = WriteDbContext(scaffolderOptions, model, schemas.Distinct().ToList()); - - path = Path.GetFullPath(Path.Combine(scaffolderOptions.ContextDir, scaffolderOptions.ContextName + $"{FileNameSuffix}.cs")); -#if CORE90 - result.ContextFile = new ScaffoldedFile(path, dbContext); -#else - result.ContextFile = new ScaffoldedFile - { - Code = dbContext, - Path = path, - }; -#endif - return result; - } - - public List CreateUsings(ModuleScaffolderOptions scaffolderOptions, RoutineModel model, List schemas) - { - ArgumentNullException.ThrowIfNull(scaffolderOptions); - ArgumentNullException.ThrowIfNull(model); - - var usings = new List() - { - "using Microsoft.EntityFrameworkCore", - "using System", - "using System.Collections.Generic", - "using System.Data", - "using System.Threading", - "using System.Threading.Tasks", - $"using {scaffolderOptions.ModelNamespace}", - }; - - usings.Add(ProviderUsing); - - if (scaffolderOptions.UseSchemaNamespaces) - { - schemas.Distinct().OrderBy(s => s).ToList().ForEach(schema => usings.Add($"using {scaffolderOptions.ModelNamespace}.{schema}")); - } - - if (model.Routines.Exists(r => r.SupportsMultipleResultSet)) - { - usings.AddRange(new List() - { - "using Dapper", - "using Microsoft.EntityFrameworkCore.Storage", - "using System.Linq", - }); - } - - if (model.Routines.SelectMany(r => r.Parameters).Any(p => typeMapper.GetClrType(p) == typeof(Geometry))) - { - usings.AddRange(new List() - { - "using NetTopologySuite.Geometries", - }); - } - - usings.Sort(); - return usings; - } - - protected abstract void GenerateProcedure(Routine procedure, RoutineModel model, bool signatureOnly, bool useAsyncCalls, bool usePascalCase); - - protected abstract string WriteDbContext(ModuleScaffolderOptions scaffolderOptions, RoutineModel model, List schemas); - - private string WriteDbContextInterface(ModuleScaffolderOptions scaffolderOptions, RoutineModel model, List schemas) - { - ArgumentNullException.ThrowIfNull(scaffolderOptions); - - ArgumentNullException.ThrowIfNull(model); - - Sb = new IndentedStringBuilder(); - - Sb.AppendLine(PathHelper.Header); - Sb.AppendLine("#nullable disable"); // procedure parameters are always nullable - - var usings = CreateUsings(scaffolderOptions, model, schemas); - - foreach (var statement in usings) - { - Sb.AppendLine($"{statement};"); - } - - Sb.AppendLine(); - Sb.AppendLine($"namespace {scaffolderOptions.ContextNamespace}"); - Sb.AppendLine("{"); - - using (Sb.Indent()) - { - Sb.AppendLine($"public partial interface I{scaffolderOptions.ContextName}{FileNameSuffix}"); - Sb.AppendLine("{"); - using (Sb.Indent()) - { - foreach (var procedure in model.Routines) - { - GenerateProcedure(procedure, model, true, scaffolderOptions.UseAsyncCalls, scaffolderOptions.UsePascalIdentifiers); - Sb.AppendLine(";"); - } - } - - Sb.AppendLine("}"); - } - - Sb.AppendLine("}"); - - return Sb.ToString(); - } - - private string WriteResultClass(List resultElements, ModuleScaffolderOptions options, string name, string schemaName) - { - var @namespace = options.ModelNamespace; - - Sb = new IndentedStringBuilder(); - - Sb.AppendLine(PathHelper.Header); - - if (resultElements.Exists(p => typeMapper.GetClrType(p) == typeof(Geometry))) - { - Sb.AppendLine("using NetTopologySuite.Geometries;"); - } - - Sb.AppendLine("using System;"); - Sb.AppendLine("using System.Collections.Generic;"); - if (options.UseDecimalDataAnnotation) - { - Sb.AppendLine("using System.ComponentModel.DataAnnotations;"); - } - - Sb.AppendLine("using System.ComponentModel.DataAnnotations.Schema;"); - Sb.AppendLine(); - - if (options.NullableReferences) - { - Sb.AppendLine("#nullable enable"); - Sb.AppendLine(); - } - - Sb.AppendLine($"namespace {@namespace}{(options.UseSchemaNamespaces ? $".{schemaName}Schema" : string.Empty)}"); - Sb.AppendLine("{"); - - using (Sb.Indent()) - { - GenerateClass(resultElements, name, options.NullableReferences, options.UseDecimalDataAnnotation, options.UsePascalIdentifiers); - } - - Sb.AppendLine("}"); - - return Sb.ToString(); - } - - private void GenerateClass(List resultElements, string name, bool nullableReferences, bool useDecimalDataAnnotation, bool usePascalCase) - { - Sb.AppendLine($"public partial class {name}"); - Sb.AppendLine("{"); - - using (Sb.Indent()) - { - GenerateProperties(resultElements, nullableReferences, useDecimalDataAnnotation, usePascalCase); - } - - Sb.AppendLine("}"); - } - - private void GenerateProperties(List resultElements, bool nullableReferences, bool useDecimalDataAnnotation, bool usePascalCase) - { - foreach (var property in resultElements.OrderBy(e => e.Ordinal)) - { - var propertyNames = ScaffoldHelper.GeneratePropertyName(property.Name, Code, usePascalCase); - - if (property.StoreType == "decimal" && useDecimalDataAnnotation) - { - Sb.AppendLine($"[Column(\"{property.Name}\", TypeName = \"{property.StoreType}({property.Precision},{property.Scale})\")]"); - } - else - { - if (!string.IsNullOrEmpty(propertyNames.Item2)) - { - Sb.AppendLine(propertyNames.Item2); - } - } - - if (useDecimalDataAnnotation - && (property.StoreType == "varchar" || property.StoreType == "nvarchar") - && property.MaxLength > 0) - { - var maxLength = property.StoreType == "varchar" ? property.MaxLength : property.MaxLength / 2; - - Sb.AppendLine($"[StringLength({maxLength})]"); - } - - var propertyType = typeMapper.GetClrType(property); - var nullableAnnotation = string.Empty; - var defaultAnnotation = string.Empty; - - if (nullableReferences && !propertyType.IsValueType) - { - if (property.Nullable) - { - nullableAnnotation = "?"; - } - else - { - defaultAnnotation = $" = default!;"; - } - } - - Sb.AppendLine($"public {Code.Reference(propertyType)}{nullableAnnotation} {propertyNames.Item1} {{ get; set; }}{defaultAnnotation}"); - } - } - } -} diff --git a/src/GUI/lib/efreveng80.exe.zip b/src/GUI/lib/efreveng80.exe.zip index caac7bf2a..3904055db 100644 Binary files a/src/GUI/lib/efreveng80.exe.zip and b/src/GUI/lib/efreveng80.exe.zip differ diff --git a/test/ScaffoldingTester/DatabaseBuild/DatabaseBuild.csproj b/test/ScaffoldingTester/DatabaseBuild/DatabaseBuild.csproj index 27abdff11..2d38b37b3 100644 --- a/test/ScaffoldingTester/DatabaseBuild/DatabaseBuild.csproj +++ b/test/ScaffoldingTester/DatabaseBuild/DatabaseBuild.csproj @@ -1,17 +1,12 @@  - netstandard2.1 - True + netstandard2.0 + True - - - - - diff --git a/test/ScaffoldingTester/ScaffoldingTester5/Models/CustOrderHistResult.cs b/test/ScaffoldingTester/ScaffoldingTester5/Models/CustOrderHistResult.cs index 1a1a7ed69..e744e99f7 100644 --- a/test/ScaffoldingTester/ScaffoldingTester5/Models/CustOrderHistResult.cs +++ b/test/ScaffoldingTester/ScaffoldingTester5/Models/CustOrderHistResult.cs @@ -1,12 +1,14 @@ // This file has been auto generated by EF Core Power Tools. using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace ScaffoldingTester.Models { public partial class CustOrderHistResult { + [StringLength(20)] public string ProductName { get; set; } public int? Total { get; set; } } diff --git a/test/ScaffoldingTester/ScaffoldingTester5/Models/CustOrdersDetailResult.cs b/test/ScaffoldingTester/ScaffoldingTester5/Models/CustOrdersDetailResult.cs index 65fa3e128..675cb8934 100644 --- a/test/ScaffoldingTester/ScaffoldingTester5/Models/CustOrdersDetailResult.cs +++ b/test/ScaffoldingTester/ScaffoldingTester5/Models/CustOrdersDetailResult.cs @@ -1,12 +1,14 @@ // This file has been auto generated by EF Core Power Tools. using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace ScaffoldingTester.Models { public partial class CustOrdersDetailResult { + [StringLength(20)] public string ProductName { get; set; } public decimal UnitPrice { get; set; } public short Quantity { get; set; } diff --git a/test/ScaffoldingTester/ScaffoldingTester5/Models/CustOrdersOrdersResult.cs b/test/ScaffoldingTester/ScaffoldingTester5/Models/CustOrdersOrdersResult.cs index 479352c98..6930aa9d6 100644 --- a/test/ScaffoldingTester/ScaffoldingTester5/Models/CustOrdersOrdersResult.cs +++ b/test/ScaffoldingTester/ScaffoldingTester5/Models/CustOrdersOrdersResult.cs @@ -1,6 +1,7 @@ // This file has been auto generated by EF Core Power Tools. using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace ScaffoldingTester.Models diff --git a/test/ScaffoldingTester/ScaffoldingTester5/Models/EmployeeSalesbyCountryResult.cs b/test/ScaffoldingTester/ScaffoldingTester5/Models/EmployeeSalesbyCountryResult.cs index e60da1f75..7b1a02d19 100644 --- a/test/ScaffoldingTester/ScaffoldingTester5/Models/EmployeeSalesbyCountryResult.cs +++ b/test/ScaffoldingTester/ScaffoldingTester5/Models/EmployeeSalesbyCountryResult.cs @@ -1,14 +1,18 @@ // This file has been auto generated by EF Core Power Tools. using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace ScaffoldingTester.Models { public partial class EmployeeSalesbyCountryResult { + [StringLength(7)] public string Country { get; set; } + [StringLength(10)] public string LastName { get; set; } + [StringLength(5)] public string FirstName { get; set; } public DateTime? ShippedDate { get; set; } public int OrderID { get; set; } diff --git a/test/ScaffoldingTester/ScaffoldingTester5/Models/MultiSetResult.cs b/test/ScaffoldingTester/ScaffoldingTester5/Models/MultiSetResult.cs index 7b8504e08..99a7a1f80 100644 --- a/test/ScaffoldingTester/ScaffoldingTester5/Models/MultiSetResult.cs +++ b/test/ScaffoldingTester/ScaffoldingTester5/Models/MultiSetResult.cs @@ -1,6 +1,7 @@ // This file has been auto generated by EF Core Power Tools. using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace ScaffoldingTester.Models diff --git a/test/ScaffoldingTester/ScaffoldingTester5/Models/NorthwindContext.Functions.cs b/test/ScaffoldingTester/ScaffoldingTester5/Models/NorthwindContext.Functions.cs index 26a80c870..e56f52724 100644 --- a/test/ScaffoldingTester/ScaffoldingTester5/Models/NorthwindContext.Functions.cs +++ b/test/ScaffoldingTester/ScaffoldingTester5/Models/NorthwindContext.Functions.cs @@ -11,20 +11,20 @@ public partial class NorthwindContext { [DbFunction("ISOweek", "dbo")] - public static int? Isoweek(DateTime? DATE) + public static int? ISOweek(DateTime? DATE) { throw new NotSupportedException("This method can only be called from Entity Framework Core queries"); } [DbFunction("tvp", "dbo")] - public IQueryable Tvp(int? storeid) + public IQueryable tvp(int? storeid) { - return FromExpression(() => Tvp(storeid)); + return FromExpression(() => tvp(storeid)); } protected void OnModelCreatingGeneratedFunctions(ModelBuilder modelBuilder) { - modelBuilder.Entity().HasNoKey(); + modelBuilder.Entity().HasNoKey(); } } } diff --git a/test/ScaffoldingTester/ScaffoldingTester5/Models/OutputScenariosResult.cs b/test/ScaffoldingTester/ScaffoldingTester5/Models/OutputScenariosResult.cs index a77bc9f57..27eb21142 100644 --- a/test/ScaffoldingTester/ScaffoldingTester5/Models/OutputScenariosResult.cs +++ b/test/ScaffoldingTester/ScaffoldingTester5/Models/OutputScenariosResult.cs @@ -1,6 +1,7 @@ // This file has been auto generated by EF Core Power Tools. using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace ScaffoldingTester.Models diff --git a/test/ScaffoldingTester/ScaffoldingTester5/Models/SalesByCategoryResult.cs b/test/ScaffoldingTester/ScaffoldingTester5/Models/SalesByCategoryResult.cs index 5f02a59f2..c36582dc0 100644 --- a/test/ScaffoldingTester/ScaffoldingTester5/Models/SalesByCategoryResult.cs +++ b/test/ScaffoldingTester/ScaffoldingTester5/Models/SalesByCategoryResult.cs @@ -1,12 +1,14 @@ // This file has been auto generated by EF Core Power Tools. using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace ScaffoldingTester.Models { public partial class SalesByCategoryResult { + [StringLength(20)] public string ProductName { get; set; } [Column("TotalPurchase", TypeName = "decimal(38,2)")] public decimal? TotalPurchase { get; set; } diff --git a/test/ScaffoldingTester/ScaffoldingTester5/Models/SalesbyYearResult.cs b/test/ScaffoldingTester/ScaffoldingTester5/Models/SalesbyYearResult.cs index 8facc0b3a..da61e5ca2 100644 --- a/test/ScaffoldingTester/ScaffoldingTester5/Models/SalesbyYearResult.cs +++ b/test/ScaffoldingTester/ScaffoldingTester5/Models/SalesbyYearResult.cs @@ -1,6 +1,7 @@ // This file has been auto generated by EF Core Power Tools. using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace ScaffoldingTester.Models @@ -10,6 +11,7 @@ public partial class SalesbyYearResult public DateTime? ShippedDate { get; set; } public int OrderID { get; set; } public decimal? Subtotal { get; set; } + [StringLength(15)] public string Year { get; set; } } } diff --git a/test/ScaffoldingTester/ScaffoldingTester5/Models/TenMostExpensiveProductsResult.cs b/test/ScaffoldingTester/ScaffoldingTester5/Models/TenMostExpensiveProductsResult.cs index d41a4afcf..3d44c7bae 100644 --- a/test/ScaffoldingTester/ScaffoldingTester5/Models/TenMostExpensiveProductsResult.cs +++ b/test/ScaffoldingTester/ScaffoldingTester5/Models/TenMostExpensiveProductsResult.cs @@ -1,12 +1,14 @@ // This file has been auto generated by EF Core Power Tools. using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace ScaffoldingTester.Models { public partial class TenMostExpensiveProductsResult { + [StringLength(20)] public string TenMostExpensiveProducts { get; set; } public decimal? UnitPrice { get; set; } } diff --git a/test/ScaffoldingTester/ScaffoldingTester5/Models/tvpResult.cs b/test/ScaffoldingTester/ScaffoldingTester5/Models/tvpResult.cs index b443f9879..acac3f9b4 100644 --- a/test/ScaffoldingTester/ScaffoldingTester5/Models/tvpResult.cs +++ b/test/ScaffoldingTester/ScaffoldingTester5/Models/tvpResult.cs @@ -1,13 +1,15 @@ // This file has been auto generated by EF Core Power Tools. using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace ScaffoldingTester.Models { - public partial class TvpResult + public partial class tvpResult { public int Id { get; set; } + [StringLength(4)] public string Name { get; set; } public decimal Total { get; set; } }