Skip to content

Commit a6414d2

Browse files
Adapt to changes in EF DbContext discovery.
- Addresses #464 - Addresses #467
1 parent a4de14d commit a6414d2

File tree

12 files changed

+130
-25
lines changed

12 files changed

+130
-25
lines changed

src/Microsoft.VisualStudio.Web.CodeGeneration.EntityFrameworkCore/DbContextEditorServices.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,22 @@ public async Task<SyntaxTree> AddNewContext(NewDbContextTemplateModel dbContextT
5858
}
5959

6060
var templateName = "NewLocalDbContext.cshtml";
61+
return await AddNewContextItemsInternal(templateName, dbContextTemplateModel);
62+
}
63+
64+
public async Task<SyntaxTree> AddNewDbContextFactory(NewDbContextTemplateModel dbContextTemplateModel)
65+
{
66+
if (dbContextTemplateModel == null)
67+
{
68+
throw new ArgumentNullException(nameof(dbContextTemplateModel));
69+
}
70+
71+
var templateName = "NewDbContextFactory.cshtml";
72+
return await AddNewContextItemsInternal(templateName, dbContextTemplateModel);
73+
}
74+
75+
private async Task<SyntaxTree> AddNewContextItemsInternal(string templateName, NewDbContextTemplateModel dbContextTemplateModel)
76+
{
6177
var templatePath = _filesLocator.GetFilePath(templateName, TemplateFolders);
6278
Contract.Assert(File.Exists(templatePath));
6379

@@ -79,6 +95,7 @@ public async Task<SyntaxTree> AddNewContext(NewDbContextTemplateModel dbContextT
7995
return CSharpSyntaxTree.ParseText(sourceText);
8096
}
8197

98+
8299
public EditSyntaxTreeResult AddModelToContext(ModelType dbContext, ModelType modelType)
83100
{
84101
if (!IsModelPropertyExists(dbContext.TypeSymbol, modelType.FullName))
@@ -217,7 +234,7 @@ private IPropertySymbol TryGetIConfigurationRootProperty(ITypeSymbol startup)
217234
if (namedType != null &&
218235
namedType.ContainingAssembly.Name == "Microsoft.Extensions.Configuration.Abstractions" &&
219236
namedType.ContainingNamespace.ToDisplayString() == "Microsoft.Extensions.Configuration" &&
220-
namedType.Name == "IConfigurationRoot")
237+
namedType.Name == "IConfiguration")
221238
{
222239
return pSymbol;
223240
}

src/Microsoft.VisualStudio.Web.CodeGeneration.EntityFrameworkCore/EntityFrameworkServices.cs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ public async Task<ContextProcessingResult> GetModelMetadata(string dbContextFull
116116
{
117117
Type dbContextType;
118118
SyntaxTree dbContextSyntaxTree = null;
119+
SyntaxTree dbContextFactorySyntaxTree = null;
119120

120121
EditSyntaxTreeResult startUpEditResult = new EditSyntaxTreeResult()
121122
{
@@ -126,6 +127,7 @@ public async Task<ContextProcessingResult> GetModelMetadata(string dbContextFull
126127

127128
var dbContextSymbols = _modelTypesLocator.GetType(dbContextFullTypeName).ToList();
128129
var startupType = _modelTypesLocator.GetType("Startup").FirstOrDefault();
130+
var programType = _modelTypesLocator.GetType("Program").FirstOrDefault();
129131
Type modelReflectionType = null;
130132
ReflectedTypesProvider reflectedTypesProvider = null;
131133
string dbContextError = string.Empty;
@@ -138,8 +140,9 @@ public async Task<ContextProcessingResult> GetModelMetadata(string dbContextFull
138140

139141
// Create a new Context
140142
_logger.LogMessage(string.Format(MessageStrings.GeneratingDbContext, dbContextFullTypeName));
141-
var dbContextTemplateModel = new NewDbContextTemplateModel(dbContextFullTypeName, modelTypeSymbol);
143+
var dbContextTemplateModel = new NewDbContextTemplateModel(dbContextFullTypeName, modelTypeSymbol, programType);
142144
dbContextSyntaxTree = await _dbContextEditorServices.AddNewContext(dbContextTemplateModel);
145+
dbContextFactorySyntaxTree = await _dbContextEditorServices.AddNewDbContextFactory(dbContextTemplateModel);
143146
state = ContextProcessingStatus.ContextAdded;
144147

145148
// Edit startup class to register the context using DI
@@ -171,6 +174,7 @@ public async Task<ContextProcessingResult> GetModelMetadata(string dbContextFull
171174
{
172175
c = c.AddSyntaxTrees(assemblyAttributeGenerator.GenerateAttributeSyntaxTree());
173176
c = c.AddSyntaxTrees(dbContextSyntaxTree);
177+
c = c.AddSyntaxTrees(dbContextFactorySyntaxTree);
174178
if (startUpEditResult.Edited)
175179
{
176180
c = c.ReplaceSyntaxTree(startUpEditResult.OldTree, startUpEditResult.NewTree);
@@ -190,6 +194,7 @@ public async Task<ContextProcessingResult> GetModelMetadata(string dbContextFull
190194

191195
// Add file information
192196
dbContextSyntaxTree = dbContextSyntaxTree.WithFilePath(GetPathForNewContext(dbContextTemplateModel.DbContextTypeName, areaName));
197+
dbContextFactorySyntaxTree = dbContextFactorySyntaxTree.WithFilePath(GetPathForNewContextFactory(dbContextTemplateModel.DbContextTypeName, areaName));
193198
}
194199
else
195200
{
@@ -279,9 +284,11 @@ public async Task<ContextProcessingResult> GetModelMetadata(string dbContextFull
279284
var metadata = GetModelMetadata(dbContextType, modelReflectionType, reflectedStartupType);
280285

281286
// Write the DbContext/Startup if getting the model metadata is successful
282-
if (dbContextSyntaxTree != null)
287+
if (dbContextSyntaxTree != null && dbContextFactorySyntaxTree != null)
283288
{
284289
PersistSyntaxTree(dbContextSyntaxTree);
290+
PersistSyntaxTree(dbContextFactorySyntaxTree);
291+
285292
if (state == ContextProcessingStatus.ContextAdded || state == ContextProcessingStatus.ContextAddedButRequiresConfig)
286293
{
287294
_logger.LogMessage(string.Format(MessageStrings.AddedDbContext, dbContextSyntaxTree.FilePath.Substring(_applicationInfo.ApplicationBasePath.Length)));
@@ -335,6 +342,30 @@ private string GetPathForNewContext(string contextShortTypeName, string areaName
335342
return outputPath;
336343
}
337344

345+
private string GetPathForNewContextFactory(string contextShortTypeName, string areaName)
346+
{
347+
var areaPath = string.IsNullOrEmpty(areaName) ? string.Empty : Path.Combine("Areas", areaName);
348+
var appBasePath = _applicationInfo.ApplicationBasePath;
349+
var outputPath = Path.Combine(
350+
appBasePath,
351+
areaPath,
352+
NewDbContextFolderName,
353+
contextShortTypeName + "Factory.cs");
354+
355+
if (File.Exists(outputPath))
356+
{
357+
// Odd case, a file exists with the same name as the DbContextFactoryTypeName but perhaps
358+
// the type defined in that file is different, what should we do in this case?
359+
// How likely is the above scenario?
360+
// Perhaps we can enumerate files with prefix and generate a safe name? For now, just throw.
361+
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
362+
MessageStrings.DbContextCreationError_fileExists,
363+
outputPath));
364+
}
365+
366+
return outputPath;
367+
}
368+
338369
private void ValidateEFSqlServerDependency()
339370
{
340371
if (_projectContext.GetPackage(EFSqlServerPackageName) == null)

src/Microsoft.VisualStudio.Web.CodeGeneration.EntityFrameworkCore/IDbContextEditorServices.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public interface IDbContextEditorServices
1010
{
1111
Task<SyntaxTree> AddNewContext(NewDbContextTemplateModel dbContextTemplateModel);
1212

13+
Task<SyntaxTree> AddNewDbContextFactory(NewDbContextTemplateModel dbContextTemplateModel);
14+
1315
EditSyntaxTreeResult AddModelToContext(ModelType dbContext, ModelType modelType);
1416

1517
EditSyntaxTreeResult EditStartupForNewContext(ModelType startup, string dbContextTypeName, string dbContextNamespace, string dataBaseName);

src/Microsoft.VisualStudio.Web.CodeGeneration.EntityFrameworkCore/NewDbContextTemplateModel.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Microsoft.VisualStudio.Web.CodeGeneration.EntityFrameworkCore
88
{
99
public class NewDbContextTemplateModel
1010
{
11-
public NewDbContextTemplateModel(string dbContextName, ModelType modelType)
11+
public NewDbContextTemplateModel(string dbContextName, ModelType modelType, ModelType programType)
1212
{
1313
if (dbContextName == null)
1414
{
@@ -20,10 +20,17 @@ public NewDbContextTemplateModel(string dbContextName, ModelType modelType)
2020
throw new ArgumentNullException(nameof(modelType));
2121
}
2222

23+
if (programType == null)
24+
{
25+
throw new ArgumentNullException(nameof(programType));
26+
}
27+
2328
var modelNamespace = modelType.Namespace;
2429

2530
ModelTypeName = modelType.Name;
2631
ModelTypeFullName = modelType.FullName;
32+
ProgramTypeName = programType.Name;
33+
ProgramNamespace = programType.Namespace;
2734
RequiredNamespaces = new HashSet<string>();
2835

2936
var classNameModel = new ClassNameModel(dbContextName);
@@ -36,6 +43,7 @@ public NewDbContextTemplateModel(string dbContextName, ModelType modelType)
3643
{
3744
RequiredNamespaces.Add(modelNamespace);
3845
}
46+
3947
}
4048

4149
public string DbContextTypeName { get; private set; }
@@ -45,6 +53,9 @@ public NewDbContextTemplateModel(string dbContextName, ModelType modelType)
4553
public string ModelTypeName { get; private set; }
4654
public string ModelTypeFullName { get; private set; }
4755

56+
public string ProgramTypeName { get; private set; }
57+
public string ProgramNamespace { get; private set; }
58+
4859
public HashSet<string> RequiredNamespaces { get; private set; }
4960
}
5061
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
@inherits Microsoft.VisualStudio.Web.CodeGeneration.Templating.RazorTemplateBase
2+
@using System.Collections.Generic;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
using Microsoft.EntityFrameworkCore.Infrastructure;
8+
using Microsoft.Extensions.DependencyInjection;
9+
@{
10+
if (!Model.DbContextNamespace.Equals(Model.ProgramNamespace, StringComparison.Ordinal))
11+
{
12+
@:using @Model.ProgramNamespace;
13+
}
14+
}
15+
@{
16+
var factoryClassName = @Model.DbContextTypeName+"Factory";
17+
}
18+
19+
namespace @Model.DbContextNamespace
20+
{
21+
public class @factoryClassName : IDbContextFactory<@Model.DbContextTypeName>
22+
{
23+
public @Model.DbContextTypeName Create(string[] args) =>
24+
@(Model.ProgramTypeName).BuildWebHost(args).Services.GetRequiredService<@Model.DbContextTypeName>();
25+
}
26+
}

test/E2E_Test/Compiler/Resources/Views/CarDetails.txt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,27 @@
44
<h4>Car</h4>
55
<hr />
66
<dl class="dl-horizontal">
7+
<dt>
8+
@Html.DisplayNameFor(model => model.ID)
9+
</dt>
10+
<dd>
11+
@Html.DisplayFor(model => model.ID)
12+
</dd>
713
<dt>
814
@Html.DisplayNameFor(model => model.Name)
915
</dt>
1016
<dd>
1117
@Html.DisplayFor(model => model.Name)
1218
</dd>
1319
<dt>
14-
@Html.DisplayNameFor(model => model.Manufacturer)
20+
@Html.DisplayNameFor(model => model.ManufacturerID)
1521
</dt>
1622
<dd>
17-
@Html.DisplayFor(model => model.Manufacturer.ID)
23+
@Html.DisplayFor(model => model.ManufacturerID)
1824
</dd>
1925
</dl>
2026
</div>
2127
<div>
22-
<a asp-action="Edit" asp-route-id="@Model.ID">Edit</a> |
28+
@Html.ActionLink("Edit", "Edit", new { /* id = Model.PrimaryKey */ }) |
2329
<a asp-action="Index">Back to List</a>
2430
</div>

test/E2E_Test/MvcControllerTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public void TestControllerWithContext_WithForeignKey()
121121
{
122122
using (var fileProvider = new TemporaryFileProvider())
123123
{
124-
new MsBuildProjectSetupHelper().SetupProjects(fileProvider, Output, true);
124+
new MsBuildProjectSetupHelper().SetupProjects(fileProvider, Output, false);
125125
TestProjectPath = Path.Combine(fileProvider.Root, "Root");
126126
Directory.SetCurrentDirectory(TestProjectPath);
127127
var args = new string[]

test/Microsoft.VisualStudio.Web.CodeGeneration.EntityFrameworkCore.Test/compiler/resources/Startup_Empty_Method_RegisterContext_After.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ namespace WebAppNamespace
1111
servicesVar.AddDbContext<MyContext>(options =>
1212
options.UseSqlServer(ConfigurationProp.GetConnectionString("MyContext")));
1313
}
14-
public IConfigurationRoot ConfigurationProp { get; set; }
14+
public IConfiguration ConfigurationProp { get; set; }
1515
}
1616
}

test/Microsoft.VisualStudio.Web.CodeGeneration.EntityFrameworkCore.Test/compiler/resources/Startup_Empty_Method_RegisterContext_Before.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ namespace WebAppNamespace
66
public void ConfigureServices(IServiceCollection servicesVar)
77
{
88
}
9-
public IConfigurationRoot ConfigurationProp { get; set; }
9+
public IConfiguration ConfigurationProp { get; set; }
1010
}
1111
}

test/Microsoft.VisualStudio.Web.CodeGeneration.EntityFrameworkCore.Test/compiler/resources/Startup_RegisterContext_After.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace WebAppNamespace
66
{
77
public class Startup
88
{
9-
public Microsoft.Extensions.Configuration.IConfigurationRoot ConfigurationProp { get; set; }
9+
public Microsoft.Extensions.Configuration.IConfiguration ConfigurationProp { get; set; }
1010

1111
public void ConfigureServices(IServiceCollection servicesVar)
1212
{

test/Microsoft.VisualStudio.Web.CodeGeneration.EntityFrameworkCore.Test/compiler/resources/Startup_RegisterContext_Before.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace WebAppNamespace
44
{
55
public class Startup
66
{
7-
public Microsoft.Extensions.Configuration.IConfigurationRoot ConfigurationProp { get; set; }
7+
public Microsoft.Extensions.Configuration.IConfiguration ConfigurationProp { get; set; }
88

99
public void ConfigureServices(IServiceCollection servicesVar)
1010
{

test/Shared/MsBuildProjectStrings.cs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ public static string GetNugetConfigTxt(string artifactsDir)
1212
<packageSources>
1313
<clear />
1414
<add key=""local"" value=""" + artifactsDir + @""" />
15-
<add key=""AspNetCoreCiDev"" value=""https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json"" />
16-
<add key=""dotnet-core"" value=""https://dotnet.myget.org/F/dotnet-core/api/v3/index.json"" />
15+
<add key=""AspNetCore"" value=""https://dotnet.myget.org/F/aspnetcore-ci-release/api/v3/index.json"" />
16+
<add key=""cli-deps"" value=""https://dotnet.myget.org/F/cli-deps/api/v3/index.json"" />
1717
<add key=""NuGet"" value=""https://api.nuget.org/v3/index.json"" />
1818
</packageSources>
1919
</configuration>";
@@ -158,16 +158,12 @@ namespace WebApplication1
158158
{
159159
public class Startup
160160
{
161-
public Startup(IHostingEnvironment env)
161+
public Startup(IConfiguration configuration)
162162
{
163-
var builder = new ConfigurationBuilder()
164-
.SetBasePath(env.ContentRootPath)
165-
.AddJsonFile(""appsettings.json"", optional: true, reloadOnChange: true);
166-
//.AddJsonFile($""appsettings.{env.EnvironmentName}.json"", optional: true);
167-
Configuration = builder.Build();
163+
Configuration = configuration;
168164
}
169165
170-
public IConfigurationRoot Configuration { get; }
166+
public IConfiguration Configuration { get; }
171167
172168
// This method gets called by the runtime. Use this method to add services to the container.
173169
public void ConfigureServices(IServiceCollection services)
@@ -177,7 +173,7 @@ public void ConfigureServices(IServiceCollection services)
177173
}
178174
179175
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
180-
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
176+
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
181177
{
182178
app.UseStaticFiles();
183179
CS7_method(out var i);
@@ -246,16 +242,32 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
246242
";
247243
public const string ProgramFileName = "Program.cs";
248244
public const string ProgramFileText = @"using System;
249-
namespace Test
245+
using System.Collections.Generic;
246+
using System.IO;
247+
using System.Linq;
248+
using System.Threading.Tasks;
249+
using Microsoft.AspNetCore;
250+
using Microsoft.AspNetCore.Hosting;
251+
using Microsoft.Extensions.Configuration;
252+
using Microsoft.Extensions.Logging;
253+
254+
namespace WebApplication1
250255
{
251256
public class Program
252257
{
253258
public static void Main(string[] args)
254259
{
255-
Console.WriteLine(""Hello"");
260+
BuildWebHost(args).Run();
256261
}
262+
263+
public static IWebHost BuildWebHost(string[] args) =>
264+
WebHost
265+
.CreateDefaultBuilder(args)
266+
.UseStartup<Startup>()
267+
.Build();
257268
}
258-
}";
269+
}
270+
";
259271

260272

261273
public const string LibraryProjectName = "Library1.csproj";

0 commit comments

Comments
 (0)