Skip to content

Commit

Permalink
Add a compiled slim model generator
Browse files Browse the repository at this point in the history
Part of #1906
  • Loading branch information
AndriySvyryd committed Apr 7, 2021
1 parent 80461f4 commit ce8ec4c
Show file tree
Hide file tree
Showing 70 changed files with 3,820 additions and 286 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public static IServiceCollection AddEntityFrameworkDesignTimeServices(
.TryAddSingleton<IMigrationsCodeGeneratorSelector, MigrationsCodeGeneratorSelector>()
.TryAddSingleton<IModelCodeGenerator, CSharpModelGenerator>()
.TryAddSingleton<IModelCodeGeneratorSelector, ModelCodeGeneratorSelector>()
.TryAddSingleton<ICompiledModelCodeGenerator, CSharpSlimModelCodeGenerator>()
.TryAddSingleton<ICompiledModelCodeGeneratorSelector, CompiledModelCodeGeneratorSelector>()
.TryAddSingleton<INamedConnectionStringResolver>(
new DesignTimeConnectionStringResolver(applicationServiceProviderAccessor))
.TryAddSingleton<IPluralizer, HumanizerPluralizer>()
Expand Down
97 changes: 32 additions & 65 deletions src/EFCore.Design/Design/Internal/CSharpHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,6 @@ public CSharpHelper(IRelationalTypeMappingSource relationalTypeMappingSource)
_relationalTypeMappingSource = relationalTypeMappingSource;
}

private static readonly IReadOnlyDictionary<Type, string> _builtInTypes = new Dictionary<Type, string>
{
{ typeof(bool), "bool" },
{ typeof(byte), "byte" },
{ typeof(sbyte), "sbyte" },
{ typeof(char), "char" },
{ typeof(short), "short" },
{ typeof(int), "int" },
{ typeof(long), "long" },
{ typeof(ushort), "ushort" },
{ typeof(uint), "uint" },
{ typeof(ulong), "ulong" },
{ typeof(decimal), "decimal" },
{ typeof(float), "float" },
{ typeof(double), "double" },
{ typeof(string), "string" },
{ typeof(object), "object" }
};

private static readonly IReadOnlyCollection<string> _keywords = new[]
{
"__arglist",
Expand Down Expand Up @@ -166,7 +147,8 @@ public CSharpHelper(IRelationalTypeMappingSource relationalTypeMappingSource)
{ typeof(uint), (c, v) => c.Literal((uint)v) },
{ typeof(ulong), (c, v) => c.Literal((ulong)v) },
{ typeof(ushort), (c, v) => c.Literal((ushort)v) },
{ typeof(BigInteger), (c, v) => c.Literal((BigInteger)v) }
{ typeof(BigInteger), (c, v) => c.Literal((BigInteger)v) },
{ typeof(Type), (c, v) => c.Literal((Type)v) }
};

/// <summary>
Expand Down Expand Up @@ -215,50 +197,7 @@ private string Reference(Type type, bool useFullName)
{
Check.NotNull(type, nameof(type));

if (_builtInTypes.TryGetValue(type, out var builtInType))
{
return builtInType;
}

if (type.IsConstructedGenericType
&& type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return Reference(type.UnwrapNullableType()) + "?";
}

var builder = new StringBuilder();

if (type.IsArray)
{
builder
.Append(Reference(type.GetElementType()!))
.Append("[");

var rank = type.GetArrayRank();
for (var i = 1; i < rank; i++)
{
builder.Append(",");
}

builder.Append("]");

return builder.ToString();
}

if (type.IsNested)
{
Check.DebugAssert(type.DeclaringType != null, "DeclaringType is null");
builder
.Append(Reference(type.DeclaringType))
.Append(".");
}

builder.Append(
useFullName
? type.DisplayName()
: type.ShortDisplayName());

return builder.ToString();
return type.DisplayName(fullName: useFullName, compilable: true);
}

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

Expand Down Expand Up @@ -298,6 +237,11 @@ public virtual string Identifier(string name, ICollection<string>? scope = null)
builder.Insert(0, "_");
}

if (capitalize != null)
{
ChangeFirstLetterCase(builder, capitalize.Value);
}

var identifier = builder.ToString();
if (scope != null)
{
Expand All @@ -315,6 +259,20 @@ public virtual string Identifier(string name, ICollection<string>? scope = null)
return _keywords.Contains(identifier) ? "@" + identifier : identifier;
}

private static StringBuilder ChangeFirstLetterCase(StringBuilder builder, bool capitalize)
{
if (builder.Length == 0)
{
return builder;
}

var first = builder[index: 0];
builder.Remove(startIndex: 0, length: 1)
.Insert(index: 0, value: capitalize ? char.ToUpper(first) : char.ToLower(first));

return builder;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down Expand Up @@ -566,6 +524,15 @@ public virtual string Literal(ushort value)
public virtual string Literal(BigInteger value)
=> $"BigInteger.Parse(\"{value.ToString(NumberFormatInfo.InvariantInfo)}\", NumberFormatInfo.InvariantInfo)";

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

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
13 changes: 11 additions & 2 deletions src/EFCore.Design/Design/Internal/LanguageBasedSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,28 @@ protected LanguageBasedSelector(IEnumerable<T> services)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual T Select(string? language)
=> Select(language, Services);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected virtual T Select(string? language, IEnumerable<T> services)
{
if (string.IsNullOrEmpty(language))
{
language = "C#";
}

var legacyService = Services.LastOrDefault(s => s.Language == null);
var legacyService = services.LastOrDefault(s => s.Language == null);
if (legacyService != null)
{
return legacyService;
}

var matches = Services.Where(s => string.Equals(s.Language, language, StringComparison.OrdinalIgnoreCase)).ToList();
var matches = services.Where(s => string.Equals(s.Language, language, StringComparison.OrdinalIgnoreCase)).ToList();
if (matches.Count == 0)
{
throw new OperationException(DesignStrings.NoLanguageService(language, typeof(T).ShortDisplayName()));
Expand Down
14 changes: 1 addition & 13 deletions src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,19 +100,7 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui
GenerateSequence(builderName, sequence, stringBuilder);
}

GenerateEntityTypes(builderName, Sort(model.GetEntityTypes()), stringBuilder);
}

private static IReadOnlyList<IEntityType> Sort(IEnumerable<IEntityType> entityTypes)
{
var entityTypeGraph = new Multigraph<IEntityType, int>();
entityTypeGraph.AddVertices(entityTypes);
foreach (var entityType in entityTypes.Where(et => et.BaseType != null))
{
entityTypeGraph.AddEdge(entityType.BaseType!, entityType, 0);
}

return entityTypeGraph.TopologicalSort();
GenerateEntityTypes(builderName, model.GetEntityTypesInHierarchicalOrder(), stringBuilder);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,5 @@ public MigrationsCodeGeneratorSelector(IEnumerable<IMigrationsCodeGenerator> ser
: base(services)
{
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IMigrationsCodeGenerator? Override { get; set; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public override IMigrationsCodeGenerator Select(string? language)
=> Override ?? base.Select(language);
}
}
57 changes: 56 additions & 1 deletion src/EFCore.Design/Properties/DesignStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions src/EFCore.Design/Properties/DesignStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,27 @@
<data name="CannotFindTypeMappingForColumn" xml:space="preserve">
<value>Could not find type mapping for column '{columnName}' with data type '{dateType}'. Skipping column.</value>
</data>
<data name="CompiledModelConstructorBinding" xml:space="preserve">
<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>
</data>
<data name="CompiledModelDefiningQuery" xml:space="preserve">
<value>The entity type '{entityType}' has a defining query configured. Compiled model can't be generated, because defining queries are not supported.</value>
</data>
<data name="CompiledModelQueryFilter" xml:space="preserve">
<value>The entity type '{entityType}' has a query filter configured. Compiled model can't be generated, because query filters are not supported.</value>
</data>
<data name="CompiledModelTypeMapping" xml:space="preserve">
<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>
</data>
<data name="CompiledModelValueComparer" xml:space="preserve">
<value>The property '{entityType}.{property}' has a value comparer configured. Compiled model can't be generated, because value comparers are not supported.</value>
</data>
<data name="CompiledModelValueConverter" xml:space="preserve">
<value>The property '{entityType}.{property}' has a value converter configured. Compiled model can't be generated, because value converters are not supported.</value>
</data>
<data name="CompiledModelValueGenerator" xml:space="preserve">
<value>The property '{entityType}.{property}' has a value generator configured. Compiled model can't be generated, because value generators are not supported.</value>
</data>
<data name="ConflictingContextAndMigrationName" xml:space="preserve">
<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>
</data>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;

namespace Microsoft.EntityFrameworkCore.Scaffolding
{
/// <summary>
/// Represents the options to use while generating code for compiled model metadata.
/// </summary>
public class CompiledModelCodeGenerationOptions
{
/// <summary>
/// Gets or sets the namespace for model metadata classes.
/// </summary>
/// <value> The namespace for model metadata classes. </value>
public virtual string ModelNamespace { get; set; } = null!;

/// <summary>
/// Gets or sets the type of the corresponding DbContext.
/// </summary>
/// <value> The type of the corresponding DbContext. </value>
public virtual Type ContextType { get; set; } = null!;

/// <summary>
/// Gets or sets the programming language to scaffold for.
/// </summary>
/// <value> The programming language to scaffold for. </value>
public virtual string? Language { get; set; }
}
}
25 changes: 25 additions & 0 deletions src/EFCore.Design/Scaffolding/ICompiledModelCodeGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Microsoft.EntityFrameworkCore.Scaffolding
{
/// <summary>
/// Used to generate code for compiled model metadata.
/// </summary>
public interface ICompiledModelCodeGenerator : ILanguageBasedService
{
/// <summary>
/// Generates code for compiled model metadata.
/// </summary>
/// <param name="model"> The source model. </param>
/// <param name="options"> The options to use during generation. </param>
/// <returns> The generated model metadata files. </returns>
IReadOnlyCollection<ScaffoldedFile> GenerateModel(
IModel model,
CompiledModelCodeGenerationOptions options);
}
}
Loading

0 comments on commit ce8ec4c

Please sign in to comment.