Skip to content

Commit

Permalink
Add a compiled slim model generator
Browse files Browse the repository at this point in the history
Add designTime parameter to IRelationalAnnotationProvider
Remove more design-time metadata from runtime model
Fix some configuration loss when reattaching entity types
Add missing SqlServer Metadata methods
Add more tests

Part of #1906
  • Loading branch information
AndriySvyryd authored May 18, 2021
1 parent 2b86d9f commit a60795c
Show file tree
Hide file tree
Showing 125 changed files with 7,223 additions and 588 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, 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];
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

0 comments on commit a60795c

Please sign in to comment.