Skip to content

Commit

Permalink
Add a pubternal way to namespace-qualify ambiguous types in compiled …
Browse files Browse the repository at this point in the history
…model.

Fixes #25523
  • Loading branch information
AndriySvyryd committed Aug 19, 2021
1 parent 9c7aa58 commit b6a452a
Show file tree
Hide file tree
Showing 8 changed files with 492 additions and 35 deletions.
46 changes: 35 additions & 11 deletions src/EFCore.Design/Design/Internal/CSharpHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,16 +191,40 @@ public virtual string Lambda(IReadOnlyList<string> properties, string? lambdaIde
/// 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 Reference(Type type)
=> Reference(type, useFullName: false);

private string Reference(Type type, bool useFullName)
public virtual string Reference(Type type, bool? fullName = null)
{
Check.NotNull(type, nameof(type));

return type.DisplayName(fullName: useFullName, compilable: true);
if (fullName == null)
{
fullName = ShouldUseFullName(type);
if (!fullName.Value && type.IsNested)
{
fullName = ShouldUseFullName(type.DeclaringType!);
}
}

return type.DisplayName(fullName.Value, compilable: true);
}

/// <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 bool ShouldUseFullName(Type type)
=> ShouldUseFullName(type.Name);

/// <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 bool ShouldUseFullName(string shortTypeName)
=> false;

/// <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 @@ -536,8 +560,8 @@ public virtual string Literal(BigInteger value)
/// 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)})";
public virtual string Literal(Type value, bool? useFullName = null)
=> $"typeof({Reference(value, useFullName)})";

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -845,14 +869,14 @@ private bool HandleExpression(Expression expression, StringBuilder builder, bool
case ExpressionType.Convert:
builder
.Append('(')
.Append(Reference(expression.Type, useFullName: true))
.Append(Reference(expression.Type, fullName: true))
.Append(')');

return HandleExpression(((UnaryExpression)expression).Operand, builder);
case ExpressionType.New:
builder
.Append("new ")
.Append(Reference(expression.Type, useFullName: true));
.Append(Reference(expression.Type, fullName: true));

return HandleArguments(((NewExpression)expression).Arguments, builder);
case ExpressionType.Call:
Expand All @@ -861,7 +885,7 @@ private bool HandleExpression(Expression expression, StringBuilder builder, bool
if (callExpression.Method.IsStatic)
{
builder
.Append(Reference(callExpression.Method.DeclaringType!, useFullName: true));
.Append(Reference(callExpression.Method.DeclaringType!, fullName: true));
}
else
{
Expand Down Expand Up @@ -895,7 +919,7 @@ private bool HandleExpression(Expression expression, StringBuilder builder, bool
if (memberExpression.Expression == null)
{
builder
.Append(Reference(memberExpression.Member.DeclaringType!, useFullName: true));
.Append(Reference(memberExpression.Member.DeclaringType!, fullName: true));
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public virtual void Generate(string modelBuilderName, IModel model, IndentedStri
/// <param name="stringBuilder"> The builder code is added to. </param>
protected virtual void GenerateEntityTypes(
string modelBuilderName,
IReadOnlyList<IEntityType> entityTypes,
IEnumerable<IEntityType> entityTypes,
IndentedStringBuilder stringBuilder)
{
Check.NotEmpty(modelBuilderName, nameof(modelBuilderName));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,10 @@ private string CreateModelBuilder(
var entityTypes = model.GetEntityTypesInHierarchicalOrder();
var variables = new HashSet<string>();

var anyEntityTypes = false;
foreach (var entityType in entityTypes)
{
anyEntityTypes = true;
var variableName = _code.Identifier(entityType.ShortName(), variables, capitalize: false);

var firstChar = variableName[0] == '@' ? variableName[1] : variableName[0];
Expand Down Expand Up @@ -265,7 +267,7 @@ private string CreateModelBuilder(
.AppendLine(");");
}

if (entityTypes.Count > 0)
if (anyEntityTypes)
{
mainBuilder.AppendLine();
}
Expand Down Expand Up @@ -339,7 +341,7 @@ private string CreateModelBuilder(
.AppendLine(");");
}

if (entityTypes.Count > 0)
if (anyEntityTypes)
{
mainBuilder.AppendLine();
}
Expand Down
6 changes: 4 additions & 2 deletions src/EFCore/Design/ICSharpHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,9 @@ string Literal<T>(T? value)
/// Generates a <see cref="Type"/> literal.
/// </summary>
/// <param name="value"> The value. </param>
/// <param name="fullName"> Whether the type should be namespace-qualified. </param>
/// <returns> The literal. </returns>
string Literal(Type value);
string Literal(Type value, bool? fullName = null);

/// <summary>
/// Generates an object array literal.
Expand All @@ -228,8 +229,9 @@ string Literal<T>(T? value)
/// Generates a C# type reference.
/// </summary>
/// <param name="type"> The type to reference. </param>
/// <param name="fullName"> Whether the type should be namespace-qualified. </param>
/// <returns> The reference. </returns>
string Reference(Type type);
string Reference(Type type, bool? fullName = null);

/// <summary>
/// Generates a literal for a type not known at compile time.
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ protected virtual RuntimeModel Create(IModel model)
((IModel)runtimeModel).ModelDependencies = model.ModelDependencies!;

var entityTypes = model.GetEntityTypesInHierarchicalOrder();
var entityTypePairs = new List<(IEntityType Source, RuntimeEntityType Target)>(entityTypes.Count);
var entityTypePairs = new List<(IEntityType Source, RuntimeEntityType Target)>();

foreach (var entityType in entityTypes)
{
Expand Down
6 changes: 3 additions & 3 deletions src/EFCore/Metadata/Internal/ModelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ public static IEnumerable<IEntityType> GetRootEntityTypes(this IModel model)
/// 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 static IReadOnlyList<IEntityType> GetEntityTypesInHierarchicalOrder(this IModel model)
public static IEnumerable<IEntityType> GetEntityTypesInHierarchicalOrder(this IModel model)
=> Sort(model.GetEntityTypes());

private static IReadOnlyList<IEntityType> Sort(IEnumerable<IEntityType> entityTypes)
private static IEnumerable<IEntityType> Sort(IEnumerable<IEntityType> entityTypes)
{
var entityTypeGraph = new Multigraph<IEntityType, int>();
entityTypeGraph.AddVertices(entityTypes);
Expand All @@ -52,7 +52,7 @@ private static IReadOnlyList<IEntityType> Sort(IEnumerable<IEntityType> entityTy
entityTypeGraph.AddEdge(entityType.BaseType!, entityType, 0);
}

return entityTypeGraph.TopologicalSort();
return entityTypeGraph.BatchingTopologicalSort().SelectMany(b => b.OrderBy(et => et.Name));
}

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions src/Shared/SharedTypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,10 @@ public static IEnumerable<MemberInfo> GetMembersInHierarchy(this Type type, stri
#pragma warning disable IDE0034 // Simplify 'default' expression - default causes default(object)
{ typeof(int), default(int) },
{ typeof(Guid), default(Guid) },
{ typeof(DateOnly), default(DateOnly) },
{ typeof(DateTime), default(DateTime) },
{ typeof(DateTimeOffset), default(DateTimeOffset) },
{ typeof(TimeOnly), default(TimeOnly) },
{ typeof(long), default(long) },
{ typeof(bool), default(bool) },
{ typeof(double), default(double) },
Expand Down
Loading

0 comments on commit b6a452a

Please sign in to comment.