From e8093b28f8aa0af8945e705c37ad87f2babd4b56 Mon Sep 17 00:00:00 2001 From: AndriySvyryd Date: Thu, 19 Aug 2021 14:18:00 -0700 Subject: [PATCH] Add a pubternal way to namespace-qualify ambiguous types in compiled model. Fixes #25523 --- .../Design/Internal/CSharpHelper.cs | 39 +- .../Design/CSharpSnapshotGenerator.cs | 2 +- .../CSharpRuntimeModelCodeGenerator.cs | 6 +- src/EFCore/Design/ICSharpHelper.cs | 6 +- .../Conventions/RuntimeModelConvention.cs | 2 +- .../Metadata/Internal/ModelExtensions.cs | 6 +- src/Shared/SharedTypeExtensions.cs | 2 + .../CSharpRuntimeModelCodeGeneratorTest.cs | 467 +++++++++++++++++- 8 files changed, 495 insertions(+), 35 deletions(-) diff --git a/src/EFCore.Design/Design/Internal/CSharpHelper.cs b/src/EFCore.Design/Design/Internal/CSharpHelper.cs index 77ab46df070..d3c12a158bb 100644 --- a/src/EFCore.Design/Design/Internal/CSharpHelper.cs +++ b/src/EFCore.Design/Design/Internal/CSharpHelper.cs @@ -191,16 +191,33 @@ public virtual string Lambda(IReadOnlyList 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. /// - 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); + fullName ??= type.IsNested ? ShouldUseFullName(type.DeclaringType!) : ShouldUseFullName(type); + + return type.DisplayName(fullName.Value, compilable: true); } + /// + /// 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. + /// + public virtual bool ShouldUseFullName(Type type) + => ShouldUseFullName(type.Name); + + /// + /// 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. + /// + public virtual bool ShouldUseFullName(string shortTypeName) + => false; + /// /// 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 @@ -536,8 +553,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. /// - public virtual string Literal(Type value) - => $"typeof({Reference(value)})"; + public virtual string Literal(Type value, bool? useFullName = null) + => $"typeof({Reference(value, useFullName)})"; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -845,14 +862,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: @@ -861,7 +878,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 { @@ -895,7 +912,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 { diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index c1570560fab..715e509b506 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -82,7 +82,7 @@ public virtual void Generate(string modelBuilderName, IModel model, IndentedStri /// The builder code is added to. protected virtual void GenerateEntityTypes( string modelBuilderName, - IReadOnlyList entityTypes, + IEnumerable entityTypes, IndentedStringBuilder stringBuilder) { Check.NotEmpty(modelBuilderName, nameof(modelBuilderName)); diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs index 1dff02c2613..fec8cb79364 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs @@ -236,8 +236,10 @@ private string CreateModelBuilder( var entityTypes = model.GetEntityTypesInHierarchicalOrder(); var variables = new HashSet(); + 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]; @@ -265,7 +267,7 @@ private string CreateModelBuilder( .AppendLine(");"); } - if (entityTypes.Count > 0) + if (anyEntityTypes) { mainBuilder.AppendLine(); } @@ -339,7 +341,7 @@ private string CreateModelBuilder( .AppendLine(");"); } - if (entityTypes.Count > 0) + if (anyEntityTypes) { mainBuilder.AppendLine(); } diff --git a/src/EFCore/Design/ICSharpHelper.cs b/src/EFCore/Design/ICSharpHelper.cs index ae255d40dfd..2add542d961 100644 --- a/src/EFCore/Design/ICSharpHelper.cs +++ b/src/EFCore/Design/ICSharpHelper.cs @@ -206,8 +206,9 @@ string Literal(T? value) /// Generates a literal. /// /// The value. + /// Whether the type should be namespace-qualified. /// The literal. - string Literal(Type value); + string Literal(Type value, bool? fullName = null); /// /// Generates an object array literal. @@ -228,8 +229,9 @@ string Literal(T? value) /// Generates a C# type reference. /// /// The type to reference. + /// Whether the type should be namespace-qualified. /// The reference. - string Reference(Type type); + string Reference(Type type, bool? fullName = null); /// /// Generates a literal for a type not known at compile time. diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs index 263ef038889..e4774d40d3f 100644 --- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs +++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs @@ -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) { diff --git a/src/EFCore/Metadata/Internal/ModelExtensions.cs b/src/EFCore/Metadata/Internal/ModelExtensions.cs index 2ea2dd46711..d9b9ef5dff8 100644 --- a/src/EFCore/Metadata/Internal/ModelExtensions.cs +++ b/src/EFCore/Metadata/Internal/ModelExtensions.cs @@ -40,10 +40,10 @@ public static IEnumerable 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. /// - public static IReadOnlyList GetEntityTypesInHierarchicalOrder(this IModel model) + public static IEnumerable GetEntityTypesInHierarchicalOrder(this IModel model) => Sort(model.GetEntityTypes()); - private static IReadOnlyList Sort(IEnumerable entityTypes) + private static IEnumerable Sort(IEnumerable entityTypes) { var entityTypeGraph = new Multigraph(); entityTypeGraph.AddVertices(entityTypes); @@ -52,7 +52,7 @@ private static IReadOnlyList Sort(IEnumerable entityTy entityTypeGraph.AddEdge(entityType.BaseType!, entityType, 0); } - return entityTypeGraph.TopologicalSort(); + return entityTypeGraph.BatchingTopologicalSort().SelectMany(b => b.OrderBy(et => et.Name)); } /// diff --git a/src/Shared/SharedTypeExtensions.cs b/src/Shared/SharedTypeExtensions.cs index 76cb5f45161..5fbb9058378 100644 --- a/src/Shared/SharedTypeExtensions.cs +++ b/src/Shared/SharedTypeExtensions.cs @@ -420,8 +420,10 @@ public static IEnumerable 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) }, diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 6b36696b1f5..898d4382b7c 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -4,9 +4,11 @@ using System.ComponentModel; using System.Linq.Expressions; using System.Reflection; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration.Internal; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal; @@ -15,6 +17,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Design.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; @@ -27,6 +30,7 @@ using NetTopologySuite.Geometries; using Newtonsoft.Json.Linq; using Xunit; +using static Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest; namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal { @@ -454,6 +458,400 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } + [ConditionalFact] + public void Fully_qualified_model() + { + Test( + new TestModel.Internal.DbContext(), + new CompiledModelCodeGenerationOptions() + { + ModelNamespace = "Internal" + }, + code => + Assert.Collection(code, + c => AssertFileContents( + "DbContextModel.cs", + @"// +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.TestModel.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace Internal +{ + [DbContext(typeof(DbContext))] + partial class DbContextModel : RuntimeModel + { + private static DbContextModel _instance; + public static IModel Instance + { + get + { + if (_instance == null) + { + _instance = new DbContextModel(); + _instance.Initialize(); + _instance.Customize(); + } + + return _instance; + } + } + + partial void Initialize(); + + partial void Customize(); + } +} +", + c), + c => AssertFileContents( + "DbContextModelBuilder.cs", + @"// +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace Internal +{ + partial class DbContextModel + { + partial void Initialize() + { + var index = IndexEntityType.Create(this); + var @internal = InternalEntityType.Create(this); + var identityUser = IdentityUserEntityType.Create(this); + var identityUser0 = IdentityUser0EntityType.Create(this, identityUser); + + IndexEntityType.CreateAnnotations(index); + InternalEntityType.CreateAnnotations(@internal); + IdentityUserEntityType.CreateAnnotations(identityUser); + IdentityUser0EntityType.CreateAnnotations(identityUser0); + + } + } +} +", + c), + c => AssertFileContents( + "IndexEntityType.cs", + @"// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace Internal +{ + partial class IndexEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.Index"", + typeof(Microsoft.EntityFrameworkCore.Scaffolding.Internal.Index), + baseEntityType); + + var id = runtimeEntityType.AddProperty( + ""Id"", + typeof(Guid), + propertyInfo: typeof(Microsoft.EntityFrameworkCore.Scaffolding.Internal.Index).GetProperty(""Id"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(Microsoft.EntityFrameworkCore.Scaffolding.Internal.Index).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw); + + var key = runtimeEntityType.AddKey( + new[] { id }); + runtimeEntityType.SetPrimaryKey(key); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", + c), + c => + AssertFileContents( + "InternalEntityType.cs", + @"// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace Internal +{ + partial class InternalEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.Internal"", + typeof(Microsoft.EntityFrameworkCore.Scaffolding.Internal.Internal), + baseEntityType); + + var id = runtimeEntityType.AddProperty( + ""Id"", + typeof(long), + propertyInfo: typeof(Microsoft.EntityFrameworkCore.Scaffolding.Internal.Internal).GetProperty(""Id"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(Microsoft.EntityFrameworkCore.Scaffolding.Internal.Internal).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw); + + var key = runtimeEntityType.AddKey( + new[] { id }); + runtimeEntityType.SetPrimaryKey(key); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", + c), + c => AssertFileContents( + "IdentityUserEntityType.cs", + @"// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.TestModels.AspNetIdentity; +using Microsoft.EntityFrameworkCore.ValueGeneration; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace Internal +{ + partial class IdentityUserEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.TestModels.AspNetIdentity.IdentityUser"", + typeof(IdentityUser), + baseEntityType, + discriminatorProperty: ""Discriminator""); + + var id = runtimeEntityType.AddProperty( + ""Id"", + typeof(string), + propertyInfo: typeof(IdentityUser).GetProperty(""Id"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + afterSaveBehavior: PropertySaveBehavior.Throw); + + var accessFailedCount = runtimeEntityType.AddProperty( + ""AccessFailedCount"", + typeof(int), + propertyInfo: typeof(IdentityUser).GetProperty(""AccessFailedCount"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + var concurrencyStamp = runtimeEntityType.AddProperty( + ""ConcurrencyStamp"", + typeof(string), + propertyInfo: typeof(IdentityUser).GetProperty(""ConcurrencyStamp"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + + var discriminator = runtimeEntityType.AddProperty( + ""Discriminator"", + typeof(string), + afterSaveBehavior: PropertySaveBehavior.Throw, + valueGeneratorFactory: new DiscriminatorValueGeneratorFactory().Create); + + var email = runtimeEntityType.AddProperty( + ""Email"", + typeof(string), + propertyInfo: typeof(IdentityUser).GetProperty(""Email"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + + var emailConfirmed = runtimeEntityType.AddProperty( + ""EmailConfirmed"", + typeof(bool), + propertyInfo: typeof(IdentityUser).GetProperty(""EmailConfirmed"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + var lockoutEnabled = runtimeEntityType.AddProperty( + ""LockoutEnabled"", + typeof(bool), + propertyInfo: typeof(IdentityUser).GetProperty(""LockoutEnabled"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + var lockoutEnd = runtimeEntityType.AddProperty( + ""LockoutEnd"", + typeof(DateTimeOffset?), + propertyInfo: typeof(IdentityUser).GetProperty(""LockoutEnd"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + + var normalizedEmail = runtimeEntityType.AddProperty( + ""NormalizedEmail"", + typeof(string), + propertyInfo: typeof(IdentityUser).GetProperty(""NormalizedEmail"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + + var normalizedUserName = runtimeEntityType.AddProperty( + ""NormalizedUserName"", + typeof(string), + propertyInfo: typeof(IdentityUser).GetProperty(""NormalizedUserName"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + + var passwordHash = runtimeEntityType.AddProperty( + ""PasswordHash"", + typeof(string), + propertyInfo: typeof(IdentityUser).GetProperty(""PasswordHash"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + + var phoneNumber = runtimeEntityType.AddProperty( + ""PhoneNumber"", + typeof(string), + propertyInfo: typeof(IdentityUser).GetProperty(""PhoneNumber"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + + var phoneNumberConfirmed = runtimeEntityType.AddProperty( + ""PhoneNumberConfirmed"", + typeof(bool), + propertyInfo: typeof(IdentityUser).GetProperty(""PhoneNumberConfirmed"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + var securityStamp = runtimeEntityType.AddProperty( + ""SecurityStamp"", + typeof(string), + propertyInfo: typeof(IdentityUser).GetProperty(""SecurityStamp"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + + var twoFactorEnabled = runtimeEntityType.AddProperty( + ""TwoFactorEnabled"", + typeof(bool), + propertyInfo: typeof(IdentityUser).GetProperty(""TwoFactorEnabled"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + var userName = runtimeEntityType.AddProperty( + ""UserName"", + typeof(string), + propertyInfo: typeof(IdentityUser).GetProperty(""UserName"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(IdentityUser).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + + var key = runtimeEntityType.AddKey( + new[] { id }); + runtimeEntityType.SetPrimaryKey(key); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""DiscriminatorValue"", ""IdentityUser""); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", + c), + c => + AssertFileContents( + "IdentityUser0EntityType.cs", + @"// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace Internal +{ + partial class IdentityUser0EntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.IdentityUser"", + typeof(IdentityUser), + baseEntityType, + discriminatorProperty: ""Discriminator""); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""DiscriminatorValue"", ""DerivedIdentityUser""); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", + c)), + model => { + Assert.Equal(4, model.GetEntityTypes().Count()); + Assert.Same(model, model.FindRuntimeAnnotationValue("ReadOnlyModel")); + }, + typeof(FullyQualifiedDesignTimeServices)); + } + + private class FullyQualifiedDesignTimeServices : IDesignTimeServices + { + public void ConfigureDesignTimeServices(IServiceCollection serviceCollection) + { + serviceCollection.AddSingleton(); + } + } + + private class FullyQualifiedCSharpHelper : CSharpHelper + { + public FullyQualifiedCSharpHelper(ITypeMappingSource typeMappingSource) : base(typeMappingSource) + { + } + + public override bool ShouldUseFullName(Type type) + => base.ShouldUseFullName(type); + + public override bool ShouldUseFullName(string shortTypeName) + => base.ShouldUseFullName(shortTypeName) + || shortTypeName == nameof(Index) + || shortTypeName == nameof(Internal); + } + [ConditionalFact] public void BigModel() { @@ -1405,13 +1803,13 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Same(principalKey, referenceOwnership.PrincipalKey); var ownedServiceProperty = referenceOwnedType.GetServiceProperties().Single(); - Assert.Equal(typeof(DbContext), ownedServiceProperty.ClrType); - Assert.Equal(typeof(DbContext), ownedServiceProperty.PropertyInfo.PropertyType); + Assert.Equal(typeof(EntityFrameworkCore.DbContext), ownedServiceProperty.ClrType); + Assert.Equal(typeof(EntityFrameworkCore.DbContext), ownedServiceProperty.PropertyInfo.PropertyType); Assert.Null(ownedServiceProperty.FieldInfo); Assert.Same(referenceOwnedType, ownedServiceProperty.DeclaringEntityType); var ownedServicePropertyBinding = ownedServiceProperty.ParameterBinding; Assert.IsType(ownedServicePropertyBinding); - Assert.Equal(typeof(DbContext), ownedServicePropertyBinding.ServiceType); + Assert.Equal(typeof(EntityFrameworkCore.DbContext), ownedServicePropertyBinding.ServiceType); Assert.Equal(ownedServiceProperty, ownedServicePropertyBinding.ConsumedProperties.Single()); Assert.Equal(PropertyAccessMode.PreferField, ownedServiceProperty.GetPropertyAccessMode()); Assert.Null(ownedServiceProperty[CoreAnnotationNames.PropertyAccessMode]); @@ -1805,20 +2203,23 @@ public class DependentDerived : DependentBase public enum Discriminator { + /// Base, + + /// Derived } public class OwnedType : INotifyPropertyChanged, INotifyPropertyChanging { - private DbContext _context; + private EntityFrameworkCore.DbContext _context; - public OwnedType(DbContext context) + public OwnedType(EntityFrameworkCore.DbContext context) { Context = context; } - public DbContext Context + public EntityFrameworkCore.DbContext Context { get => _context; set { @@ -2844,7 +3245,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) typeof(SqliteNetTopologySuiteDesignTimeServices)); } - public class SqliteContext : DbContext + public class SqliteContext : EntityFrameworkCore.DbContext { protected override void OnConfiguring(DbContextOptionsBuilder options) => options @@ -3139,7 +3540,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) }); } - public class CosmosContext : DbContext + public class CosmosContext : EntityFrameworkCore.DbContext { protected override void OnConfiguring(DbContextOptionsBuilder options) => options @@ -3171,7 +3572,7 @@ public class Data public byte[] Blob { get; set; } } - public abstract class ContextBase : DbContext + public abstract class ContextBase : EntityFrameworkCore.DbContext { protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -3184,7 +3585,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder options) .UseInMemoryDatabase(nameof(CSharpRuntimeModelCodeGeneratorTest)); } - public abstract class SqlServerContextBase : DbContext + public abstract class SqlServerContextBase : EntityFrameworkCore.DbContext { protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -3198,7 +3599,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder options) } protected void Test( - DbContext context, + EntityFrameworkCore.DbContext context, CompiledModelCodeGenerationOptions options, Action> assertScaffold = null, Action assertModel = null, @@ -3235,10 +3636,6 @@ protected void Test( var scaffoldedFiles = generator.GenerateModel( model, options); - if (assertScaffold != null) - { - assertScaffold(scaffoldedFiles); - } var build = new BuildSource { @@ -3253,6 +3650,7 @@ protected void Test( BuildReference.ByName("Microsoft.EntityFrameworkCore.Sqlite.NetTopologySuite"), BuildReference.ByName("Microsoft.EntityFrameworkCore.SqlServer"), BuildReference.ByName("Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite"), + BuildReference.ByName("Microsoft.EntityFrameworkCore.Specification.Tests"), BuildReference.ByName("NetTopologySuite"), BuildReference.ByName("Newtonsoft.Json"), BuildReference.ByName(typeof(CSharpRuntimeModelCodeGeneratorTest).Assembly.GetName().Name) @@ -3262,6 +3660,11 @@ protected void Test( var assembly = build.BuildInMemory(); + if (assertScaffold != null) + { + assertScaffold(scaffoldedFiles); + } + if (assertModel != null) { var modelType = assembly.GetType(options.ModelNamespace + "." + options.ContextType.Name + "Model"); @@ -3308,4 +3711,38 @@ protected static void AssertFileContents( Assert.Equal(expectedCode, file.Code, ignoreLineEndingDifferences: true); } } + + public class Internal + { + public long Id { get; set; } + } + + public class Index + { + public Guid Id { get; set; } + } + + public class IdentityUser : TestModels.AspNetIdentity.IdentityUser + { + + } +} + +namespace Microsoft.EntityFrameworkCore.Scaffolding.TestModel.Internal +{ + public class DbContext : ContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(eb => + { + eb.HasDiscriminator().HasValue("DerivedIdentityUser"); + }); + modelBuilder.Entity(); + } + } }