Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a compiled slim model generator #24612

Merged
merged 2 commits into from
May 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, CSharpRuntimeModelCodeGenerator>()
.TryAddSingleton<ICompiledModelCodeGeneratorSelector, CompiledModelCodeGeneratorSelector>()
.TryAddSingleton<INamedConnectionStringResolver>(
new DesignTimeConnectionStringResolver(applicationServiceProviderAccessor))
.TryAddSingleton<IPluralizer, HumanizerPluralizer>()
Expand Down
115 changes: 46 additions & 69 deletions src/EFCore.Design/Design/Internal/CSharpHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,38 +24,19 @@ namespace Microsoft.EntityFrameworkCore.Design.Internal
/// </summary>
public class CSharpHelper : ICSharpHelper
{
private readonly IRelationalTypeMappingSource _relationalTypeMappingSource;
private readonly ITypeMappingSource _typeMappingSource;

/// <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 CSharpHelper(IRelationalTypeMappingSource relationalTypeMappingSource)
public CSharpHelper(ITypeMappingSource typeMappingSource)
{
_relationalTypeMappingSource = relationalTypeMappingSource;
_typeMappingSource = typeMappingSource;
}

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,25 @@ 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];
AndriySvyryd marked this conversation as resolved.
Show resolved Hide resolved
if (char.IsUpper(first) == capitalize)
{
return builder;
}

builder.Remove(startIndex: 0, length: 1)
.Insert(index: 0, value: capitalize ? char.ToUpperInvariant(first) : char.ToLowerInvariant(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 +529,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 Expand Up @@ -821,12 +793,17 @@ public virtual string UnknownLiteral(object? value)
return Literal(enumValue);
}

if (value is Type type)
{
return Literal(type);
}

if (value is Array array)
{
return Array(literalType.GetElementType()!, array);
}

var mapping = _relationalTypeMappingSource.FindMapping(literalType);
var mapping = _typeMappingSource.FindMapping(literalType);
if (mapping != null)
{
var builder = new StringBuilder();
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 dynamic proxy types are not supported. If you are not using proxies configure the custom constructor binding in '{customize}' in a partial '{className}' class instead.</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. Configure it in '{customize}' in a partial '{className}' class instead.</value>
</data>
<data name="CompiledModelValueComparer" xml:space="preserve">
<value>The property '{entityType}.{property}' has a value comparer configured. Use '{method}' to configure the value comparer type.</value>
</data>
<data name="CompiledModelValueConverter" xml:space="preserve">
<value>The property '{entityType}.{property}' has a value converter configured. Use '{method}' to configure the value converter type.</value>
</data>
<data name="CompiledModelValueGenerator" xml:space="preserve">
<value>The property '{entityType}.{property}' has a value generator configured. Use '{method}' to configure the value generator factory type.</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
Loading