Skip to content

Commit b8eebb2

Browse files
committed
Add a compiled slim model generator
Part of #1906
1 parent 2b86d9f commit b8eebb2

File tree

79 files changed

+3859
-309
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+3859
-309
lines changed

src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ public static IServiceCollection AddEntityFrameworkDesignTimeServices(
5858
.TryAddSingleton<IMigrationsCodeGeneratorSelector, MigrationsCodeGeneratorSelector>()
5959
.TryAddSingleton<IModelCodeGenerator, CSharpModelGenerator>()
6060
.TryAddSingleton<IModelCodeGeneratorSelector, ModelCodeGeneratorSelector>()
61+
.TryAddSingleton<ICompiledModelCodeGenerator, CSharpSlimModelCodeGenerator>()
62+
.TryAddSingleton<ICompiledModelCodeGeneratorSelector, CompiledModelCodeGeneratorSelector>()
6163
.TryAddSingleton<INamedConnectionStringResolver>(
6264
new DesignTimeConnectionStringResolver(applicationServiceProviderAccessor))
6365
.TryAddSingleton<IPluralizer, HumanizerPluralizer>()

src/EFCore.Design/Design/Internal/CSharpHelper.cs

Lines changed: 32 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,6 @@ public CSharpHelper(IRelationalTypeMappingSource relationalTypeMappingSource)
3737
_relationalTypeMappingSource = relationalTypeMappingSource;
3838
}
3939

40-
private static readonly IReadOnlyDictionary<Type, string> _builtInTypes = new Dictionary<Type, string>
41-
{
42-
{ typeof(bool), "bool" },
43-
{ typeof(byte), "byte" },
44-
{ typeof(sbyte), "sbyte" },
45-
{ typeof(char), "char" },
46-
{ typeof(short), "short" },
47-
{ typeof(int), "int" },
48-
{ typeof(long), "long" },
49-
{ typeof(ushort), "ushort" },
50-
{ typeof(uint), "uint" },
51-
{ typeof(ulong), "ulong" },
52-
{ typeof(decimal), "decimal" },
53-
{ typeof(float), "float" },
54-
{ typeof(double), "double" },
55-
{ typeof(string), "string" },
56-
{ typeof(object), "object" }
57-
};
58-
5940
private static readonly IReadOnlyCollection<string> _keywords = new[]
6041
{
6142
"__arglist",
@@ -166,7 +147,8 @@ public CSharpHelper(IRelationalTypeMappingSource relationalTypeMappingSource)
166147
{ typeof(uint), (c, v) => c.Literal((uint)v) },
167148
{ typeof(ulong), (c, v) => c.Literal((ulong)v) },
168149
{ typeof(ushort), (c, v) => c.Literal((ushort)v) },
169-
{ typeof(BigInteger), (c, v) => c.Literal((BigInteger)v) }
150+
{ typeof(BigInteger), (c, v) => c.Literal((BigInteger)v) },
151+
{ typeof(Type), (c, v) => c.Literal((Type)v) }
170152
};
171153

172154
/// <summary>
@@ -215,50 +197,7 @@ private string Reference(Type type, bool useFullName)
215197
{
216198
Check.NotNull(type, nameof(type));
217199

218-
if (_builtInTypes.TryGetValue(type, out var builtInType))
219-
{
220-
return builtInType;
221-
}
222-
223-
if (type.IsConstructedGenericType
224-
&& type.GetGenericTypeDefinition() == typeof(Nullable<>))
225-
{
226-
return Reference(type.UnwrapNullableType()) + "?";
227-
}
228-
229-
var builder = new StringBuilder();
230-
231-
if (type.IsArray)
232-
{
233-
builder
234-
.Append(Reference(type.GetElementType()!))
235-
.Append('[');
236-
237-
var rank = type.GetArrayRank();
238-
for (var i = 1; i < rank; i++)
239-
{
240-
builder.Append(',');
241-
}
242-
243-
builder.Append(']');
244-
245-
return builder.ToString();
246-
}
247-
248-
if (type.IsNested)
249-
{
250-
Check.DebugAssert(type.DeclaringType != null, "DeclaringType is null");
251-
builder
252-
.Append(Reference(type.DeclaringType))
253-
.Append('.');
254-
}
255-
256-
builder.Append(
257-
useFullName
258-
? type.DisplayName()
259-
: type.ShortDisplayName());
260-
261-
return builder.ToString();
200+
return type.DisplayName(fullName: useFullName, compilable: true);
262201
}
263202

264203
/// <summary>
@@ -267,7 +206,7 @@ private string Reference(Type type, bool useFullName)
267206
/// any release. You should only use it directly in your code with extreme caution and knowing that
268207
/// doing so can result in application failures when updating to a new Entity Framework Core release.
269208
/// </summary>
270-
public virtual string Identifier(string name, ICollection<string>? scope = null)
209+
public virtual string Identifier(string name, ICollection<string>? scope = null, bool? capitalize = null)
271210
{
272211
Check.NotEmpty(name, nameof(name));
273212

@@ -298,6 +237,11 @@ public virtual string Identifier(string name, ICollection<string>? scope = null)
298237
builder.Insert(0, '_');
299238
}
300239

240+
if (capitalize != null)
241+
{
242+
ChangeFirstLetterCase(builder, capitalize.Value);
243+
}
244+
301245
var identifier = builder.ToString();
302246
if (scope != null)
303247
{
@@ -315,6 +259,20 @@ public virtual string Identifier(string name, ICollection<string>? scope = null)
315259
return _keywords.Contains(identifier) ? "@" + identifier : identifier;
316260
}
317261

262+
private static StringBuilder ChangeFirstLetterCase(StringBuilder builder, bool capitalize)
263+
{
264+
if (builder.Length == 0)
265+
{
266+
return builder;
267+
}
268+
269+
var first = builder[index: 0];
270+
builder.Remove(startIndex: 0, length: 1)
271+
.Insert(index: 0, value: capitalize ? char.ToUpper(first) : char.ToLower(first));
272+
273+
return builder;
274+
}
275+
318276
/// <summary>
319277
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
320278
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -566,6 +524,15 @@ public virtual string Literal(ushort value)
566524
public virtual string Literal(BigInteger value)
567525
=> $"BigInteger.Parse(\"{value.ToString(NumberFormatInfo.InvariantInfo)}\", NumberFormatInfo.InvariantInfo)";
568526

527+
/// <summary>
528+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
529+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
530+
/// any release. You should only use it directly in your code with extreme caution and knowing that
531+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
532+
/// </summary>
533+
public virtual string Literal(Type value)
534+
=> $"typeof({Reference(value)})";
535+
569536
/// <summary>
570537
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
571538
/// the same compatibility standards as public APIs. It may be changed or removed without notice in

src/EFCore.Design/Design/Internal/LanguageBasedSelector.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,28 @@ protected LanguageBasedSelector(IEnumerable<T> services)
4242
/// doing so can result in application failures when updating to a new Entity Framework Core release.
4343
/// </summary>
4444
public virtual T Select(string? language)
45+
=> Select(language, Services);
46+
47+
/// <summary>
48+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
49+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
50+
/// any release. You should only use it directly in your code with extreme caution and knowing that
51+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
52+
/// </summary>
53+
protected virtual T Select(string? language, IEnumerable<T> services)
4554
{
4655
if (string.IsNullOrEmpty(language))
4756
{
4857
language = "C#";
4958
}
5059

51-
var legacyService = Services.LastOrDefault(s => s.Language == null);
60+
var legacyService = services.LastOrDefault(s => s.Language == null);
5261
if (legacyService != null)
5362
{
5463
return legacyService;
5564
}
5665

57-
var matches = Services.Where(s => string.Equals(s.Language, language, StringComparison.OrdinalIgnoreCase)).ToList();
66+
var matches = services.Where(s => string.Equals(s.Language, language, StringComparison.OrdinalIgnoreCase)).ToList();
5867
if (matches.Count == 0)
5968
{
6069
throw new OperationException(DesignStrings.NoLanguageService(language, typeof(T).ShortDisplayName()));

src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,7 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui
100100
GenerateSequence(builderName, sequence, stringBuilder);
101101
}
102102

103-
GenerateEntityTypes(builderName, Sort(model.GetEntityTypes()), stringBuilder);
104-
}
105-
106-
private static IReadOnlyList<IEntityType> Sort(IEnumerable<IEntityType> entityTypes)
107-
{
108-
var entityTypeGraph = new Multigraph<IEntityType, int>();
109-
entityTypeGraph.AddVertices(entityTypes);
110-
foreach (var entityType in entityTypes.Where(et => et.BaseType != null))
111-
{
112-
entityTypeGraph.AddEdge(entityType.BaseType!, entityType, 0);
113-
}
114-
115-
return entityTypeGraph.TopologicalSort();
103+
GenerateEntityTypes(builderName, model.GetEntityTypesInHierarchicalOrder(), stringBuilder);
116104
}
117105

118106
/// <summary>

src/EFCore.Design/Migrations/Internal/MigrationsCodeGeneratorSelector.cs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,5 @@ public MigrationsCodeGeneratorSelector(IEnumerable<IMigrationsCodeGenerator> ser
2525
: base(services)
2626
{
2727
}
28-
29-
/// <summary>
30-
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
31-
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
32-
/// any release. You should only use it directly in your code with extreme caution and knowing that
33-
/// doing so can result in application failures when updating to a new Entity Framework Core release.
34-
/// </summary>
35-
public virtual IMigrationsCodeGenerator? Override { get; set; }
36-
37-
/// <summary>
38-
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
39-
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
40-
/// any release. You should only use it directly in your code with extreme caution and knowing that
41-
/// doing so can result in application failures when updating to a new Entity Framework Core release.
42-
/// </summary>
43-
public override IMigrationsCodeGenerator Select(string? language)
44-
=> Override ?? base.Select(language);
4528
}
4629
}

src/EFCore.Design/Properties/DesignStrings.Designer.cs

Lines changed: 56 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/EFCore.Design/Properties/DesignStrings.resx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,27 @@
132132
<data name="CannotFindTypeMappingForColumn" xml:space="preserve">
133133
<value>Could not find type mapping for column '{columnName}' with data type '{dateType}'. Skipping column.</value>
134134
</data>
135+
<data name="CompiledModelConstructorBinding" xml:space="preserve">
136+
<value>The entity type '{entityType}' has a custom constructor binding. This is usually caused by using proxies. Compiled model can't be generated, because custom constructor bindings are not supported.</value>
137+
</data>
138+
<data name="CompiledModelDefiningQuery" xml:space="preserve">
139+
<value>The entity type '{entityType}' has a defining query configured. Compiled model can't be generated, because defining queries are not supported.</value>
140+
</data>
141+
<data name="CompiledModelQueryFilter" xml:space="preserve">
142+
<value>The entity type '{entityType}' has a query filter configured. Compiled model can't be generated, because query filters are not supported.</value>
143+
</data>
144+
<data name="CompiledModelTypeMapping" xml:space="preserve">
145+
<value>The property '{entityType}.{property}' has a custom type mapping configured. Compiled model can't be generated, because custom type mappings are not supported.</value>
146+
</data>
147+
<data name="CompiledModelValueComparer" xml:space="preserve">
148+
<value>The property '{entityType}.{property}' has a value comparer configured. Compiled model can't be generated, because value comparers are not supported.</value>
149+
</data>
150+
<data name="CompiledModelValueConverter" xml:space="preserve">
151+
<value>The property '{entityType}.{property}' has a value converter configured. Compiled model can't be generated, because value converters are not supported.</value>
152+
</data>
153+
<data name="CompiledModelValueGenerator" xml:space="preserve">
154+
<value>The property '{entityType}.{property}' has a value generator configured. Compiled model can't be generated, because value generators are not supported.</value>
155+
</data>
135156
<data name="ConflictingContextAndMigrationName" xml:space="preserve">
136157
<value>The name you have chosen for the migration, '{name}', is the same as the context class name. Please choose a different name for your migration. Might we suggest 'InitialCreate' for your first migration?</value>
137158
</data>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.EntityFrameworkCore.Scaffolding
7+
{
8+
/// <summary>
9+
/// Represents the options to use while generating code for compiled model metadata.
10+
/// </summary>
11+
public class CompiledModelCodeGenerationOptions
12+
{
13+
/// <summary>
14+
/// Gets or sets the namespace for model metadata classes.
15+
/// </summary>
16+
/// <value> The namespace for model metadata classes. </value>
17+
public virtual string ModelNamespace { get; set; } = null!;
18+
19+
/// <summary>
20+
/// Gets or sets the type of the corresponding DbContext.
21+
/// </summary>
22+
/// <value> The type of the corresponding DbContext. </value>
23+
public virtual Type ContextType { get; set; } = null!;
24+
25+
/// <summary>
26+
/// Gets or sets the programming language to scaffold for.
27+
/// </summary>
28+
/// <value> The programming language to scaffold for. </value>
29+
public virtual string? Language { get; set; }
30+
}
31+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.EntityFrameworkCore.Design;
6+
using Microsoft.EntityFrameworkCore.Metadata;
7+
8+
namespace Microsoft.EntityFrameworkCore.Scaffolding
9+
{
10+
/// <summary>
11+
/// Used to generate code for compiled model metadata.
12+
/// </summary>
13+
public interface ICompiledModelCodeGenerator : ILanguageBasedService
14+
{
15+
/// <summary>
16+
/// Generates code for compiled model metadata.
17+
/// </summary>
18+
/// <param name="model"> The source model. </param>
19+
/// <param name="options"> The options to use during generation. </param>
20+
/// <returns> The generated model metadata files. </returns>
21+
IReadOnlyCollection<ScaffoldedFile> GenerateModel(
22+
IModel model,
23+
CompiledModelCodeGenerationOptions options);
24+
}
25+
}

0 commit comments

Comments
 (0)