diff --git a/scripts/install-aspnet-codegenerator.cmd b/scripts/install-aspnet-codegenerator.cmd index cc0744d58..24cda8927 100644 --- a/scripts/install-aspnet-codegenerator.cmd +++ b/scripts/install-aspnet-codegenerator.cmd @@ -1,4 +1,4 @@ -set VERSION=8.0.0-dev +set VERSION=7.0.4 set DEFAULT_NUPKG_PATH=%userprofile%\.nuget\packages set SRC_DIR=%cd% set NUPKG=artifacts/packages/Debug/Shipping/ diff --git a/scripts/install-aspnet-codegenerator.sh b/scripts/install-aspnet-codegenerator.sh index c758f627f..649b57956 100755 --- a/scripts/install-aspnet-codegenerator.sh +++ b/scripts/install-aspnet-codegenerator.sh @@ -1,6 +1,6 @@ #!/bin/bash -VERSION=8.0.0-dev +VERSION=7.0.4 DEFAULT_NUPKG_PATH=~/.nuget/packages SRC_DIR=$(pwd) echo $SRC_DIR diff --git a/src/Scaffolding/VS.Web.CG.Mvc/Identity/IdentityGeneratorTemplateModelBuilder.cs b/src/Scaffolding/VS.Web.CG.Mvc/Identity/IdentityGeneratorTemplateModelBuilder.cs index 35c4e1feb..46dcc10c8 100644 --- a/src/Scaffolding/VS.Web.CG.Mvc/Identity/IdentityGeneratorTemplateModelBuilder.cs +++ b/src/Scaffolding/VS.Web.CG.Mvc/Identity/IdentityGeneratorTemplateModelBuilder.cs @@ -744,14 +744,9 @@ private void ValidateRequiredDependencies() var dependencies = new HashSet() { "Microsoft.AspNetCore.Identity.UI", - "Microsoft.EntityFrameworkCore.Design" + EfConstants.EfToolsPackageName }; - const string EfDesignPackageName = "Microsoft.EntityFrameworkCore.Design"; - var isEFDesignPackagePresent = _projectContext - .PackageDependencies - .Any(package => package.Name.Equals(EfDesignPackageName, StringComparison.OrdinalIgnoreCase)); - var missingPackages = dependencies.Where(d => !_projectContext.PackageDependencies.Any(p => p.Name.Equals(d, StringComparison.OrdinalIgnoreCase))); if (CalledFromCommandline && missingPackages.Any()) { diff --git a/src/Scaffolding/VS.Web.CG.Mvc/Minimal Api/MinimalApiGenerator.cs b/src/Scaffolding/VS.Web.CG.Mvc/Minimal Api/MinimalApiGenerator.cs index 827496a59..6315b44ea 100644 --- a/src/Scaffolding/VS.Web.CG.Mvc/Minimal Api/MinimalApiGenerator.cs +++ b/src/Scaffolding/VS.Web.CG.Mvc/Minimal Api/MinimalApiGenerator.cs @@ -32,7 +32,7 @@ public class MinimalApiGenerator : ICodeGenerator private IModelTypesLocator ModelTypesLocator { get; set; } private IFileSystem FileSystem { get; set; } private IProjectContext ProjectContext { get; set; } - private IEntityFrameworkService EntityFrameworkService { get; set;} + private IEntityFrameworkService EntityFrameworkService { get; set; } private ICodeGeneratorActionsService CodeGeneratorActionsService { get; set; } private Workspace Workspace { get; set; } private ConsoleLogger ConsoleLogger { get; set; } @@ -74,7 +74,7 @@ public async Task GenerateCode(MinimalApiGeneratorCommandLineModel model) EntityFrameworkService, ModelTypesLocator, Logger, - areaName : string.Empty); + areaName: string.Empty); if (!string.IsNullOrEmpty(modelTypeAndContextModel.DbContextFullName) && CalledFromCommandline) { @@ -85,7 +85,7 @@ public async Task GenerateCode(MinimalApiGeneratorCommandLineModel model) { ValidateOpenApiDependencies(ProjectContext.PackageDependencies); } - + var templateModel = new MinimalApiModel(modelTypeAndContextModel.ModelType, modelTypeAndContextModel.DbContextFullName, model.EndpintsClassName) { EndpointsName = model.EndpintsClassName, @@ -116,7 +116,7 @@ public async Task GenerateCode(MinimalApiGeneratorCommandLineModel model) } } //execute CodeGeneratorActionsService.AddFileFromTemplateAsync to add endpoints file. - else + else { //Add endpoints file with endpoints class since it does not exist. ValidateModel(model); @@ -178,7 +178,7 @@ internal async Task AddEndpointsMethod(string membersBlockText, string endpoints { usings.Add("Microsoft.AspNetCore.Http.HttpResults"); } - var endpointsCodeFile = new CodeFile { Usings = usings.ToArray()}; + var endpointsCodeFile = new CodeFile { Usings = usings.ToArray() }; var docBuilder = new DocumentBuilder(docEditor, endpointsCodeFile, ConsoleLogger); var newRoot = docBuilder.AddUsings(new CodeChangeOptions()); var classNode = newRoot.DescendantNodes().FirstOrDefault(node => node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.Identifier.ValueText.Contains(className)); @@ -196,7 +196,7 @@ internal async Task AddEndpointsMethod(string membersBlockText, string endpoints classDeclaration = SyntaxFactory.ClassDeclaration($"{templateModel.ModelType.Name}Endpoints") .WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword))) .NormalizeWhitespace() - .WithLeadingTrivia(SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.CarriageReturnLineFeed); + .WithLeadingTrivia(SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.CarriageReturnLineFeed); } var modifiedClass = classDeclaration.AddMembers( SyntaxFactory.GlobalStatement(SyntaxFactory.ParseStatement(membersBlockText)).WithLeadingTrivia(SyntaxFactory.Tab)); @@ -212,7 +212,7 @@ internal async Task AddEndpointsMethod(string membersBlockText, string endpoints { newRoot = newRoot.ReplaceNode(classNode, modifiedClass); } - + docEditor.ReplaceNode(docRoot, newRoot); var classFileSourceTxt = await docEditor.GetChangedDocument()?.GetTextAsync(); var classFileTxt = classFileSourceTxt?.ToString(); @@ -323,7 +323,7 @@ internal async Task ModifyProgramCs(MinimalApiModel templateModel) newRoot = newRoot?.ReplaceNode(mainMethod.Body, updatedMethod); } } - + if (templateModel.OpenAPI) { var builderVariable = ProjectModifierHelper.GetBuilderVariableIdentifierTransformation(newRoot.Members); @@ -336,7 +336,7 @@ internal async Task ModifyProgramCs(MinimalApiModel templateModel) { filteredChanges = DocumentBuilder.AddLeadingTriviaSpaces(filteredChanges, spaces: 12); var mainMethod = DocumentBuilder.GetMethodFromSyntaxRoot(newRoot, Main); - { + { var updatedMethod = DocumentBuilder.ApplyChangesToMethod(mainMethod.Body, filteredChanges); newRoot = newRoot?.ReplaceNode(mainMethod.Body, updatedMethod); } @@ -390,7 +390,7 @@ private void ValidateOpenApiDependencies(IEnumerable pack string.Format(MessageStrings.InstallPackagesForScaffoldingIdentity, string.Join(",", missingPackages))); } } - + private string GetMinimalApiCodeModifierConfig() { string jsonText = string.Empty; diff --git a/src/Scaffolding/VS.Web.CG.Mvc/Templates/MinimalApi/MinimalApiEf.cshtml b/src/Scaffolding/VS.Web.CG.Mvc/Templates/MinimalApi/MinimalApiEf.cshtml index 635d34f5f..2b2b3fc2a 100644 --- a/src/Scaffolding/VS.Web.CG.Mvc/Templates/MinimalApi/MinimalApiEf.cshtml +++ b/src/Scaffolding/VS.Web.CG.Mvc/Templates/MinimalApi/MinimalApiEf.cshtml @@ -13,6 +13,8 @@ string updateModel = $"Update{@modelName}"; string dbContextName = Model.ContextTypeName; var entitySetName = Model.ModelMetadata.EntitySetName; + var entitySetNoTracking = $"{entitySetName}.AsNoTracking()"; + var entityProperties = Model.ModelMetadata.Properties; var primaryKeyName = Model.ModelMetadata.PrimaryKeys[0].PropertyName; var primaryKeyNameLowerCase = primaryKeyName.ToLowerInvariant(); var primaryKeyShortTypeName = Model.ModelMetadata.PrimaryKeys[0].ShortTypeName; @@ -23,9 +25,10 @@ var remove = $"{@entitySetName}.Remove({@Model.ModelVariable})"; string resultsExtension = Model.UseTypedResults ? "TypedResults" : "Results"; string typedTaskWithNotFound = Model.UseTypedResults ? $"Task, NotFound>>" : ""; - string typedTaskWithNoContent = Model.UseTypedResults ? $"Task>" : ""; + string typedTaskOkNotFound = Model.UseTypedResults ? $"Task>" : ""; string resultsNotFound = $"{resultsExtension}.NotFound()"; string resultsOkModel = $"{resultsExtension}.Ok(model)"; + string resultsOkEmpty = $"{resultsExtension}.Ok()"; string resultsNoContent = $"{resultsExtension}.NoContent()"; string resultsOkModelVariable = $"{resultsExtension}.Ok({@Model.ModelVariable})"; string createdApiVar = string.Format("$\"{0}/{{{1}.{2}}}\",{3}", @routePrefix, @Model.ModelVariable, @primaryKeyName, @Model.ModelVariable); @@ -76,7 +79,8 @@ public static class @endPointsClassName group.MapGet("/{id}", async @typedTaskWithNotFound (@primaryKeyShortTypeName @primaryKeyNameLowerCase, @dbContextName db) => { - return await db.@findModel + return await db.@entitySetNoTracking + .FirstOrDefaultAsync(model => model.@primaryKeyName == @primaryKeyNameLowerCase) is @modelName model ? @resultsOkModel : @resultsNotFound; @@ -95,19 +99,23 @@ public static class @endPointsClassName @:@builderExtensions; } - group.MapPut("/{id}", async @typedTaskWithNoContent (@primaryKeyShortTypeName @primaryKeyNameLowerCase, @modelName @Model.ModelVariable, @dbContextName db) => + group.MapPut("/{id}", async @typedTaskOkNotFound (@primaryKeyShortTypeName @primaryKeyNameLowerCase, @modelName @Model.ModelVariable, @dbContextName db) => { - var foundModel = await db.@findModel; - - if (foundModel is null) - { - return @resultsNotFound; - } - - db.Update(@Model.ModelVariable); - await db.SaveChangesAsync(); + var affected = await db.@entitySetName + .Where(model => model.@primaryKeyName == @primaryKeyNameLowerCase) + .ExecuteUpdateAsync(setters => setters +@{ + //should be atleast one property (primary key) + foreach(var modelProperty in entityProperties) + { + string modelPropertyName = modelProperty.PropertyName; + string setPropertyString = $".SetProperty(m => m.{modelPropertyName}, {Model.ModelVariable}.{modelPropertyName})"; + @:@setPropertyString + } +} + ); - return @resultsNoContent; + return affected == 1 ? @resultsOkEmpty : @resultsNotFound; }) @{ builderExtensions = $".WithName(\"{@updateModel}\")"; @@ -143,16 +151,13 @@ public static class @endPointsClassName @:@builderExtensions; } - group.MapDelete("/{id}", async @typedTaskWithNotFound (@primaryKeyShortTypeName @primaryKeyNameLowerCase, @dbContextName db) => + group.MapDelete("/{id}", async @typedTaskOkNotFound (@primaryKeyShortTypeName @primaryKeyNameLowerCase, @dbContextName db) => { - if (await db.@findModel is @modelName @Model.ModelVariable) - { - db.@remove; - await db.SaveChangesAsync(); - return @resultsOkModelVariable; - } + var affected = await db.@entitySetName + .Where(model => model.@primaryKeyName == @primaryKeyNameLowerCase) + .ExecuteDeleteAsync(); - return @resultsNotFound; + return affected == 1 ? @resultsOkEmpty : @resultsNotFound; }) @{ builderExtensions = $".WithName(\"{@deleteModel}\")"; diff --git a/src/Scaffolding/VS.Web.CG.Mvc/Templates/MinimalApi/MinimalApiEfNoClass.cshtml b/src/Scaffolding/VS.Web.CG.Mvc/Templates/MinimalApi/MinimalApiEfNoClass.cshtml index ce7b352db..25207ada6 100644 --- a/src/Scaffolding/VS.Web.CG.Mvc/Templates/MinimalApi/MinimalApiEfNoClass.cshtml +++ b/src/Scaffolding/VS.Web.CG.Mvc/Templates/MinimalApi/MinimalApiEfNoClass.cshtml @@ -3,6 +3,7 @@ string modelName = Model.ModelType.Name; string routePrefix = "/api/" + modelName; string endPointsClassName = Model.EndpointsName; + string methodName = $"Map{@modelName}Endpoints"; string pluralModel = $"{@modelName}s"; string getAllModels = $"GetAll{@pluralModel}"; string getModelById = $"Get{@modelName}ById"; @@ -11,6 +12,8 @@ string updateModel = $"Update{@modelName}"; string dbContextName = Model.ContextTypeName; var entitySetName = Model.ModelMetadata.EntitySetName; + var entitySetNoTracking = $"{entitySetName}.AsNoTracking()"; + var entityProperties = Model.ModelMetadata.Properties; var primaryKeyName = Model.ModelMetadata.PrimaryKeys[0].PropertyName; var primaryKeyNameLowerCase = primaryKeyName.ToLowerInvariant(); var primaryKeyShortTypeName = Model.ModelMetadata.PrimaryKeys[0].ShortTypeName; @@ -22,9 +25,10 @@ var remove = $"{@entitySetName}.Remove({@Model.ModelVariable})"; string resultsExtension = Model.UseTypedResults ? "TypedResults" : "Results"; string typedTaskWithNotFound = Model.UseTypedResults ? $"Task, NotFound>>" : ""; - string typedTaskWithNoContent = Model.UseTypedResults ? $"Task>" : ""; + string typedTaskOkNotFound = Model.UseTypedResults ? $"Task>" : ""; string resultsNotFound = $"{resultsExtension}.NotFound()"; string resultsOkModel = $"{resultsExtension}.Ok(model)"; + string resultsOkEmpty = $"{resultsExtension}.Ok()"; string resultsNoContent = $"{resultsExtension}.NoContent()"; string resultsOkModelVariable = $"{resultsExtension}.Ok({@Model.ModelVariable})"; string createdApiVar = string.Format("$\"{0}/{{{1}.{2}}}\",{3}", @routePrefix, @Model.ModelVariable, @primaryKeyName, @Model.ModelVariable); @@ -64,7 +68,8 @@ group.MapGet("/{id}", async @typedTaskWithNotFound (@primaryKeyShortTypeName @primaryKeyNameLowerCase, @dbContextName db) => { - return await db.@findModel + return await db.@entitySetNoTracking + .FirstOrDefaultAsync(model => model.@primaryKeyName == @primaryKeyNameLowerCase) is @modelName model ? @resultsOkModel : @resultsNotFound; @@ -83,19 +88,23 @@ @:@builderExtensions; } - group.MapPut("/{id}", async @typedTaskWithNoContent (@primaryKeyShortTypeName @primaryKeyNameLowerCase, @modelName @Model.ModelVariable, @dbContextName db) => + group.MapPut("/{id}", async @typedTaskOkNotFound (@primaryKeyShortTypeName @primaryKeyNameLowerCase, @modelName @Model.ModelVariable, @dbContextName db) => { - var foundModel = await db.@findModel; - - if (foundModel is null) - { - return @resultsNotFound; - } - - db.Update(@Model.ModelVariable); - await db.SaveChangesAsync(); + var affected = await db.@entitySetName + .Where(model => model.@primaryKeyName == @primaryKeyNameLowerCase) + .ExecuteUpdateAsync(setters => setters +@{ + //should be atleast one property (primary key) + foreach(var modelProperty in entityProperties) + { + string modelPropertyName = modelProperty.PropertyName; + string setPropertyString = $".SetProperty(m => m.{modelPropertyName}, {Model.ModelVariable}.{modelPropertyName})"; + @:@setPropertyString + } +} + ); - return @resultsNoContent; + return affected == 1 ? @resultsOkEmpty : @resultsNotFound; }) @{ builderExtensions = $".WithName(\"{@updateModel}\")"; @@ -131,16 +140,13 @@ @:@builderExtensions; } - group.MapDelete("/{id}", async @typedTaskWithNotFound (@primaryKeyShortTypeName @primaryKeyNameLowerCase, @dbContextName db) => + group.MapDelete("/{id}", async @typedTaskOkNotFound (@primaryKeyShortTypeName @primaryKeyNameLowerCase, @dbContextName db) => { - if (await db.@findModel is @modelName @Model.ModelVariable) - { - db.@remove; - await db.SaveChangesAsync(); - return @resultsOkModelVariable; - } + var affected = await db.@entitySetName + .Where(model => model.@primaryKeyName == @primaryKeyNameLowerCase) + .ExecuteDeleteAsync(); - return @resultsNotFound; + return affected == 1 ? @resultsOkEmpty : @resultsNotFound; }) @{ builderExtensions = $".WithName(\"{@deleteModel}\")"; diff --git a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/EFValidationUtil.cs b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/EFValidationUtil.cs index bdd8368bb..44d672e63 100644 --- a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/EFValidationUtil.cs +++ b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/EFValidationUtil.cs @@ -13,12 +13,12 @@ internal static class EFValidationUtil internal static void ValidateEFDependencies(IEnumerable dependencies, DbProvider dataContextType) { var isEFDesignPackagePresent = dependencies - .Any(package => package.Name.Equals(EfConstants.EfDesignPackageName, StringComparison.OrdinalIgnoreCase)); + .Any(package => package.Name.Equals(EfConstants.EfToolsPackageName, StringComparison.OrdinalIgnoreCase)); if (!isEFDesignPackagePresent) { throw new InvalidOperationException( - string.Format(MessageStrings.InstallEfPackages, $"{EfConstants.EfDesignPackageName}")); + string.Format(MessageStrings.InstallEfPackages, $"{EfConstants.EfToolsPackageName}")); } if (EfConstants.EfPackagesDict.TryGetValue(dataContextType, out var dbProviderPackageName)) diff --git a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/SharedConstants.cs b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/SharedConstants.cs index 06bb0af79..5a0c6baf4 100644 --- a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/SharedConstants.cs +++ b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/SharedConstants.cs @@ -14,7 +14,7 @@ public static class EfConstants public static string SQLite = DbProvider.SQLite.ToString(); public static string CosmosDb = DbProvider.CosmosDb.ToString(); public static string Postgres = DbProvider.Postgres.ToString(); - public const string EfDesignPackageName = "Microsoft.EntityFrameworkCore.Design"; + public const string EfToolsPackageName = "Microsoft.EntityFrameworkCore.Tools"; public const string SqlServerPackageName = "Microsoft.EntityFrameworkCore.SqlServer"; public const string SqlitePackageName = "Microsoft.EntityFrameworkCore.Sqlite"; public const string CosmosPakcageName = "Microsoft.EntityFrameworkCore.Cosmos";