diff --git a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs index a6ff6039d9c..940135a604c 100644 --- a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs +++ b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs @@ -58,7 +58,7 @@ public static IServiceCollection AddEntityFrameworkDesignTimeServices( .TryAddSingleton() .TryAddSingleton() .TryAddSingleton() - .TryAddSingleton() + .TryAddSingleton() .TryAddSingleton() .TryAddSingleton( new DesignTimeConnectionStringResolver(applicationServiceProviderAccessor)) diff --git a/src/EFCore.Design/Design/Internal/CSharpHelper.cs b/src/EFCore.Design/Design/Internal/CSharpHelper.cs index 4d92c260747..b2d58b724b6 100644 --- a/src/EFCore.Design/Design/Internal/CSharpHelper.cs +++ b/src/EFCore.Design/Design/Internal/CSharpHelper.cs @@ -24,7 +24,7 @@ namespace Microsoft.EntityFrameworkCore.Design.Internal /// public class CSharpHelper : ICSharpHelper { - private readonly IRelationalTypeMappingSource _relationalTypeMappingSource; + private readonly ITypeMappingSource _typeMappingSource; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -32,9 +32,9 @@ public class CSharpHelper : ICSharpHelper /// 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 CSharpHelper(IRelationalTypeMappingSource relationalTypeMappingSource) + public CSharpHelper(ITypeMappingSource typeMappingSource) { - _relationalTypeMappingSource = relationalTypeMappingSource; + _typeMappingSource = typeMappingSource; } private static readonly IReadOnlyCollection _keywords = new[] @@ -267,8 +267,13 @@ private static StringBuilder ChangeFirstLetterCase(StringBuilder builder, bool c } var first = builder[index: 0]; + if (char.IsUpper(first) == capitalize) + { + return builder; + } + builder.Remove(startIndex: 0, length: 1) - .Insert(index: 0, value: capitalize ? char.ToUpper(first) : char.ToLower(first)); + .Insert(index: 0, value: capitalize ? char.ToUpperInvariant(first) : char.ToLowerInvariant(first)); return builder; } @@ -788,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(); diff --git a/src/EFCore.Design/Properties/DesignStrings.Designer.cs b/src/EFCore.Design/Properties/DesignStrings.Designer.cs index 58618625075..b7258aa1b36 100644 --- a/src/EFCore.Design/Properties/DesignStrings.Designer.cs +++ b/src/EFCore.Design/Properties/DesignStrings.Designer.cs @@ -60,12 +60,12 @@ public static string CannotFindTypeMappingForColumn(object? columnName, object? columnName, dateType); /// - /// 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. + /// 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. /// - public static string CompiledModelConstructorBinding(object? entityType) + public static string CompiledModelConstructorBinding(object? entityType, object? customize, object? className) => string.Format( - GetString("CompiledModelConstructorBinding", nameof(entityType)), - entityType); + GetString("CompiledModelConstructorBinding", nameof(entityType), nameof(customize), nameof(className)), + entityType, customize, className); /// /// The entity type '{entityType}' has a defining query configured. Compiled model can't be generated, because defining queries are not supported. @@ -84,36 +84,36 @@ public static string CompiledModelQueryFilter(object? entityType) entityType); /// - /// The property '{entityType}.{property}' has a custom type mapping configured. Compiled model can't be generated, because custom type mappings are not supported. + /// The property '{entityType}.{property}' has a custom type mapping configured. Configure it in '{customize}' in a partial '{className}' class instead. /// - public static string CompiledModelTypeMapping(object? entityType, object? property) + public static string CompiledModelTypeMapping(object? entityType, object? property, object? customize, object? className) => string.Format( - GetString("CompiledModelTypeMapping", nameof(entityType), nameof(property)), - entityType, property); + GetString("CompiledModelTypeMapping", nameof(entityType), nameof(property), nameof(customize), nameof(className)), + entityType, property, customize, className); /// - /// The property '{entityType}.{property}' has a value comparer configured. Compiled model can't be generated, because value comparers are not supported. + /// The property '{entityType}.{property}' has a value comparer configured. Use '{method}' to configure the value comparer type. /// - public static string CompiledModelValueComparer(object? entityType, object? property) + public static string CompiledModelValueComparer(object? entityType, object? property, object? method) => string.Format( - GetString("CompiledModelValueComparer", nameof(entityType), nameof(property)), - entityType, property); + GetString("CompiledModelValueComparer", nameof(entityType), nameof(property), nameof(method)), + entityType, property, method); /// - /// The property '{entityType}.{property}' has a value converter configured. Compiled model can't be generated, because value converters are not supported. + /// The property '{entityType}.{property}' has a value converter configured. Use '{method}' to configure the value converter type. /// - public static string CompiledModelValueConverter(object? entityType, object? property) + public static string CompiledModelValueConverter(object? entityType, object? property, object? method) => string.Format( - GetString("CompiledModelValueConverter", nameof(entityType), nameof(property)), - entityType, property); + GetString("CompiledModelValueConverter", nameof(entityType), nameof(property), nameof(method)), + entityType, property, method); /// - /// The property '{entityType}.{property}' has a value generator configured. Compiled model can't be generated, because value generators are not supported. + /// The property '{entityType}.{property}' has a value generator configured. Use '{method}' to configure the value generator factory type. /// - public static string CompiledModelValueGenerator(object? entityType, object? property) + public static string CompiledModelValueGenerator(object? entityType, object? property, object? method) => string.Format( - GetString("CompiledModelValueGenerator", nameof(entityType), nameof(property)), - entityType, property); + GetString("CompiledModelValueGenerator", nameof(entityType), nameof(property), nameof(method)), + entityType, property, method); /// /// 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? diff --git a/src/EFCore.Design/Properties/DesignStrings.resx b/src/EFCore.Design/Properties/DesignStrings.resx index 45bae42fbf4..962d0df69ed 100644 --- a/src/EFCore.Design/Properties/DesignStrings.resx +++ b/src/EFCore.Design/Properties/DesignStrings.resx @@ -133,7 +133,7 @@ Could not find type mapping for column '{columnName}' with data type '{dateType}'. Skipping column. - 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. + 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. The entity type '{entityType}' has a defining query configured. Compiled model can't be generated, because defining queries are not supported. @@ -142,16 +142,16 @@ The entity type '{entityType}' has a query filter configured. Compiled model can't be generated, because query filters are not supported. - The property '{entityType}.{property}' has a custom type mapping configured. Compiled model can't be generated, because custom type mappings are not supported. + The property '{entityType}.{property}' has a custom type mapping configured. Configure it in '{customize}' in a partial '{className}' class instead. - The property '{entityType}.{property}' has a value comparer configured. Compiled model can't be generated, because value comparers are not supported. + The property '{entityType}.{property}' has a value comparer configured. Use '{method}' to configure the value comparer type. - The property '{entityType}.{property}' has a value converter configured. Compiled model can't be generated, because value converters are not supported. + The property '{entityType}.{property}' has a value converter configured. Use '{method}' to configure the value converter type. - The property '{entityType}.{property}' has a value generator configured. Compiled model can't be generated, because value generators are not supported. + The property '{entityType}.{property}' has a value generator configured. Use '{method}' to configure the value generator factory type. 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? diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpSlimModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs similarity index 84% rename from src/EFCore.Design/Scaffolding/Internal/CSharpSlimModelCodeGenerator.cs rename to src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs index b8d9ca18e2e..91bb762b992 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpSlimModelCodeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs @@ -11,7 +11,9 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal @@ -22,10 +24,10 @@ namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal /// 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 class CSharpSlimModelCodeGenerator : ICompiledModelCodeGenerator + public class CSharpRuntimeModelCodeGenerator : ICompiledModelCodeGenerator { private readonly ICSharpHelper _code; - private readonly ICSharpSlimAnnotationCodeGenerator _annotationCodeGenerator; + private readonly ICSharpRuntimeAnnotationCodeGenerator _annotationCodeGenerator; private const string FileExtension = ".cs"; private const string ModelSuffix = "Model"; @@ -37,8 +39,8 @@ public class CSharpSlimModelCodeGenerator : ICompiledModelCodeGenerator /// 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 CSharpSlimModelCodeGenerator( - ICSharpSlimAnnotationCodeGenerator annotationCodeGenerator, + public CSharpRuntimeModelCodeGenerator( + ICSharpRuntimeAnnotationCodeGenerator annotationCodeGenerator, ICSharpHelper cSharpHelper) { Check.NotNull(annotationCodeGenerator, nameof(annotationCodeGenerator)); @@ -117,7 +119,7 @@ private string CreateModel( var methodBuilder = new IndentedStringBuilder(); var namespaces = new SortedSet(new NamespaceComparer()) { contextType.Namespace!, - typeof(SlimModel).Namespace!, + typeof(RuntimeModel).Namespace!, typeof(DbContextAttribute).Namespace! }; @@ -132,7 +134,7 @@ private string CreateModel( var className = _code.Identifier(contextType.ShortDisplayName()) + ModelSuffix; mainBuilder .Append("[DbContext(typeof(").Append(_code.Reference(contextType)).AppendLine("))]") - .Append("partial class ").Append(className).AppendLine(" : " + nameof(SlimModel)) + .Append("partial class ").Append(className).AppendLine(" : " + nameof(RuntimeModel)) .AppendLine("{"); using (mainBuilder.Indent()) @@ -170,7 +172,7 @@ private string CreateModel( var firstChar = variableName[0] == '@' ? variableName[1] : variableName[0]; var entityClassName = firstChar == '_' ? EntityTypeSuffix + variableName[1..] - : char.ToUpper(firstChar) + variableName[1..] + EntityTypeSuffix; + : char.ToUpperInvariant(firstChar) + variableName[(variableName[0] == '@' ? 2 : 1)..] + EntityTypeSuffix; entityTypeIds[entityType] = (variableName, entityClassName); @@ -193,12 +195,14 @@ private string CreateModel( mainBuilder.AppendLine(); } + var anyForeignKeys = false; foreach (var (entityType, namePair) in entityTypeIds) { var foreignKeyNumber = 1; var (variableName, entityClassName) = namePair; foreach (var foreignKey in entityType.GetDeclaredForeignKeys()) { + anyForeignKeys = true; var principalVariable = entityTypeIds[foreignKey.PrincipalEntityType].Variable; mainBuilder @@ -213,12 +217,19 @@ private string CreateModel( } } + if (anyForeignKeys) + { + mainBuilder.AppendLine(); + } + + var anySkipNavigations = false; foreach (var (entityType, namePair) in entityTypeIds) { var navigationNumber = 1; var (variableName, entityClassName) = namePair; foreach (var navigation in entityType.GetDeclaredSkipNavigations()) { + anySkipNavigations = true; var targetVariable = entityTypeIds[navigation.TargetEntityType].Variable; var joinVariable = entityTypeIds[navigation.JoinEntityType].Variable; @@ -236,6 +247,11 @@ private string CreateModel( } } + if (anySkipNavigations) + { + mainBuilder.AppendLine(); + } + foreach (var (entityType, namePair) in entityTypeIds) { var (variableName, entityClassName) = namePair; @@ -256,20 +272,29 @@ private string CreateModel( CreateAnnotations( model, _annotationCodeGenerator.Generate, - new CSharpSlimAnnotationCodeGeneratorParameters( + new CSharpRuntimeAnnotationCodeGeneratorParameters( "this", + className, mainBuilder, methodBuilder, namespaces, variables)); + + mainBuilder + .AppendLine() + .AppendLine("Customize();"); } - mainBuilder.AppendLine("}"); + mainBuilder + .AppendLine("}") + .AppendLine() + .AppendLine("partial void Customize();"); var methods = methodBuilder.ToString(); if (!string.IsNullOrEmpty(methods)) { - mainBuilder.AppendLines(methods); + mainBuilder.AppendLine() + .AppendLines(methods); } } @@ -291,7 +316,7 @@ private string GenerateEntityType(IEntityType entityType, string @namespace, str var methodBuilder = new IndentedStringBuilder(); var namespaces = new SortedSet(new NamespaceComparer()) { - typeof(SlimEntityType).Namespace!, + typeof(RuntimeEntityType).Namespace!, typeof(PropertyAccessMode).Namespace!, typeof(BindingFlags).Namespace! }; @@ -309,21 +334,21 @@ private string GenerateEntityType(IEntityType entityType, string @namespace, str .AppendLine("{"); using (mainBuilder.Indent()) { - CreateEntityType(entityType, mainBuilder, methodBuilder, namespaces); + CreateEntityType(entityType, mainBuilder, methodBuilder, namespaces, className); var foreignKeyNumber = 1; foreach (var foreignKey in entityType.GetDeclaredForeignKeys()) { - CreateForeignKey(foreignKey, foreignKeyNumber++, mainBuilder, methodBuilder, namespaces); + CreateForeignKey(foreignKey, foreignKeyNumber++, mainBuilder, methodBuilder, namespaces, className); } var navigationNumber = 1; foreach (var navigation in entityType.GetDeclaredSkipNavigations()) { - CreateSkipNavigation(navigation, navigationNumber++, mainBuilder, methodBuilder, namespaces); + CreateSkipNavigation(navigation, navigationNumber++, mainBuilder, methodBuilder, namespaces, className); } - CreateAnnotations(entityType, mainBuilder, methodBuilder, namespaces); + CreateAnnotations(entityType, mainBuilder, methodBuilder, namespaces, className); } mainBuilder.AppendLine("}"); @@ -342,16 +367,17 @@ private void CreateEntityType( IEntityType entityType, IndentedStringBuilder mainBuilder, IndentedStringBuilder methodBuilder, - SortedSet namespaces) + SortedSet namespaces, + string className) { mainBuilder - .Append("public static SlimEntityType Create") - .AppendLine("(SlimModel model, SlimEntityType baseEntityType)") + .Append("public static RuntimeEntityType Create") + .AppendLine("(RuntimeModel model, RuntimeEntityType baseEntityType)") .AppendLine("{"); using (mainBuilder.Indent()) { - var entityTypeVariable = "slimEntityType"; + var entityTypeVariable = "runtimeEntityType"; var variables = new HashSet { "model", @@ -359,19 +385,20 @@ private void CreateEntityType( entityTypeVariable }; - var parameters = new CSharpSlimAnnotationCodeGeneratorParameters( + var parameters = new CSharpRuntimeAnnotationCodeGeneratorParameters( entityTypeVariable, + className, mainBuilder, methodBuilder, namespaces, variables); - Create(entityType, parameters); + Create(entityType, parameters, className); var propertyVariables = new Dictionary(); foreach (var property in entityType.GetDeclaredProperties()) { - Create(property, propertyVariables, parameters); + Create(property, propertyVariables, parameters, className); } foreach (var property in entityType.GetDeclaredServiceProperties()) @@ -396,11 +423,10 @@ private void CreateEntityType( } mainBuilder - .AppendLine("}") - .AppendLine(); + .AppendLine("}"); } - private void Create(IEntityType entityType, CSharpSlimAnnotationCodeGeneratorParameters parameters) + private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters, string className) { var runtimeEntityType = entityType as IRuntimeEntityType; if ((entityType.ConstructorBinding is not null @@ -410,7 +436,8 @@ private void Create(IEntityType entityType, CSharpSlimAnnotationCodeGeneratorPar && (runtimeEntityType.GetServiceOnlyConstructorBindingConfigurationSource().OverridesStrictly(ConfigurationSource.Convention) || runtimeEntityType.ServiceOnlyConstructorBinding is FactoryMethodBinding))) { - throw new InvalidOperationException(DesignStrings.CompiledModelConstructorBinding(entityType.ShortName())); + throw new InvalidOperationException(DesignStrings.CompiledModelConstructorBinding( + entityType.ShortName(), "Customize()", className)); } if (entityType.GetQueryFilter() != null) @@ -421,7 +448,7 @@ private void Create(IEntityType entityType, CSharpSlimAnnotationCodeGeneratorPar #pragma warning disable CS0618 // Type or member is obsolete if (entityType.GetDefiningQuery() != null) { - // TODO: Move to InMemoryCSharpSlimAnnotationCodeGenerator, see #21624 + // TODO: Move to InMemoryCSharpRuntimeAnnotationCodeGenerator, see #21624 throw new InvalidOperationException(DesignStrings.CompiledModelDefiningQuery(entityType.ShortName())); } #pragma warning restore CS0618 // Type or member is obsolete @@ -465,7 +492,7 @@ private void Create(IEntityType entityType, CSharpSlimAnnotationCodeGeneratorPar if (indexerPropertyInfo != null) { mainBuilder.AppendLine(",") - .Append("indexerPropertyInfo: SlimEntityType.FindIndexerProperty(") + .Append("indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(") .Append(_code.Literal(entityType.ClrType)) .Append(")"); } @@ -486,31 +513,42 @@ private void Create(IEntityType entityType, CSharpSlimAnnotationCodeGeneratorPar private void Create( IProperty property, Dictionary propertyVariables, - CSharpSlimAnnotationCodeGeneratorParameters parameters) + CSharpRuntimeAnnotationCodeGeneratorParameters parameters, + string className) { - if (property.GetValueGeneratorFactory() != null) + var valueGeneratorFactoryType = (Type?)property[CoreAnnotationNames.ValueGeneratorFactoryType]; + if (valueGeneratorFactoryType == null + && property.GetValueGeneratorFactory() != null) { throw new InvalidOperationException( - DesignStrings.CompiledModelValueGenerator(property.DeclaringEntityType.ShortName(), property.Name)); + DesignStrings.CompiledModelValueGenerator( + property.DeclaringEntityType.ShortName(), property.Name, nameof(PropertyBuilder.HasValueGeneratorFactory))); } - if (property[CoreAnnotationNames.ValueComparer] != null) + var valueComparerType = (Type?)property[CoreAnnotationNames.ValueComparerType]; + if (valueComparerType == null + && property[CoreAnnotationNames.ValueComparer] != null) { throw new InvalidOperationException( - DesignStrings.CompiledModelValueComparer(property.DeclaringEntityType.ShortName(), property.Name)); + DesignStrings.CompiledModelValueComparer( + property.DeclaringEntityType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); } - if (property.GetValueConverter() != null) + var valueConverterType = (Type?)property[CoreAnnotationNames.ValueConverterType]; + if (valueConverterType == null + && property.GetValueConverter() != null) { throw new InvalidOperationException( - DesignStrings.CompiledModelValueConverter(property.DeclaringEntityType.ShortName(), property.Name)); + DesignStrings.CompiledModelValueConverter( + property.DeclaringEntityType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); } if (property is IConventionProperty conventionProperty && conventionProperty.GetTypeMappingConfigurationSource() != null) { throw new InvalidOperationException( - DesignStrings.CompiledModelTypeMapping(property.DeclaringEntityType.ShortName(), property.Name)); + DesignStrings.CompiledModelTypeMapping( + property.DeclaringEntityType.ShortName(), property.Name, "Customize()", className)); } var variableName = _code.Identifier(property.Name, parameters.ScopeVariables, capitalize: false); @@ -605,6 +643,45 @@ private void Create( .Append(_code.Literal(providerClrType)); } + if (valueGeneratorFactoryType != null) + { + if (valueGeneratorFactoryType.Namespace != null) + { + parameters.Namespaces.Add(valueGeneratorFactoryType.Namespace); + } + + mainBuilder.AppendLine(",") + .Append("valueGeneratorFactory: new ") + .Append(_code.Reference(valueGeneratorFactoryType)) + .Append("().Create"); + } + + if (valueConverterType != null) + { + if (valueConverterType.Namespace != null) + { + parameters.Namespaces.Add(valueConverterType.Namespace); + } + + mainBuilder.AppendLine(",") + .Append("valueConverter: new ") + .Append(_code.Reference(valueConverterType)) + .Append("()"); + } + + if (valueComparerType != null) + { + if (valueComparerType.Namespace != null) + { + parameters.Namespaces.Add(valueComparerType.Namespace); + } + + mainBuilder.AppendLine(",") + .Append("valueComparer: new ") + .Append(_code.Reference(valueComparerType)) + .Append("()"); + } + mainBuilder .AppendLine(");") .DecrementIndent(); @@ -619,7 +696,7 @@ private void Create( private void PropertyBaseParameters( IPropertyBase property, - CSharpSlimAnnotationCodeGeneratorParameters parameters, + CSharpRuntimeAnnotationCodeGeneratorParameters parameters, bool skipType = false) { var mainBuilder = parameters.MainBuilder; @@ -677,7 +754,7 @@ private void PropertyBaseParameters( .Append(", ") .Append(fieldInfo.IsPublic ? "BindingFlags.Public" : "BindingFlags.NonPublic") .Append(fieldInfo.IsStatic ? " | BindingFlags.Static" : " | BindingFlags.Instance") - .AppendLine(" | BindingFlags.DeclaredOnly)"); + .Append(" | BindingFlags.DeclaredOnly)"); } var propertyAccessMode = property.GetPropertyAccessMode(); @@ -728,7 +805,7 @@ private void FindProperties( private void Create( IServiceProperty property, - CSharpSlimAnnotationCodeGeneratorParameters parameters) + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var variableName = _code.Identifier(property.Name, parameters.ScopeVariables, capitalize: false); @@ -755,7 +832,7 @@ private void Create( private void Create( IKey key, Dictionary propertyVariables, - CSharpSlimAnnotationCodeGeneratorParameters parameters) + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var variableName = _code.Identifier("key", parameters.ScopeVariables); @@ -788,7 +865,7 @@ private void Create( private void Create( IIndex index, Dictionary propertyVariables, - CSharpSlimAnnotationCodeGeneratorParameters parameters) + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var variableName = _code.Identifier(index.Name ?? "index", parameters.ScopeVariables, capitalize: false); @@ -830,19 +907,20 @@ private void CreateForeignKey( int foreignKeyNumber, IndentedStringBuilder mainBuilder, IndentedStringBuilder methodBuilder, - SortedSet namespaces) + SortedSet namespaces, + string className) { var declaringEntityType = "declaringEntityType"; var principalEntityType = "principalEntityType"; mainBuilder.AppendLine() - .Append("public static SlimForeignKey CreateForeignKey").Append(foreignKeyNumber.ToString()) - .Append("(SlimEntityType ").Append(declaringEntityType) - .Append(", SlimEntityType ").Append(principalEntityType).AppendLine(")") + .Append("public static RuntimeForeignKey CreateForeignKey").Append(foreignKeyNumber.ToString()) + .Append("(RuntimeEntityType ").Append(declaringEntityType) + .Append(", RuntimeEntityType ").Append(principalEntityType).AppendLine(")") .AppendLine("{"); using (mainBuilder.Indent()) { - var foreignKeyVariable = "slimForeignKey"; + var foreignKeyVariable = "runtimeForeignKey"; var variables = new HashSet { declaringEntityType, @@ -901,8 +979,9 @@ private void CreateForeignKey( .AppendLine() .DecrementIndent(); - var parameters = new CSharpSlimAnnotationCodeGeneratorParameters( + var parameters = new CSharpRuntimeAnnotationCodeGeneratorParameters( foreignKeyVariable, + className, mainBuilder, methodBuilder, namespaces, @@ -938,7 +1017,7 @@ private void CreateForeignKey( private void Create( INavigation navigation, string foreignKeyVariable, - CSharpSlimAnnotationCodeGeneratorParameters parameters) + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var mainBuilder = parameters.MainBuilder; var navigationVariable = _code.Identifier(navigation.Name, parameters.ScopeVariables, capitalize: false); @@ -966,8 +1045,6 @@ private void Create( navigation, _annotationCodeGenerator.Generate, parameters with { TargetName = navigationVariable }); - - mainBuilder.AppendLine(); } private void CreateSkipNavigation( @@ -975,17 +1052,18 @@ private void CreateSkipNavigation( int navigationNumber, IndentedStringBuilder mainBuilder, IndentedStringBuilder methodBuilder, - SortedSet namespaces) + SortedSet namespaces, + string className) { var declaringEntityType = "declaringEntityType"; var targetEntityType = "targetEntityType"; var joinEntityType = "joinEntityType"; mainBuilder.AppendLine() - .Append("public static SlimSkipNavigation CreateSkipNavigation") + .Append("public static RuntimeSkipNavigation CreateSkipNavigation") .Append(navigationNumber.ToString()) - .Append("(SlimEntityType ").Append(declaringEntityType) - .Append(", SlimEntityType ").Append(targetEntityType) - .Append(", SlimEntityType ").Append(joinEntityType).AppendLine(")") + .Append("(RuntimeEntityType ").Append(declaringEntityType) + .Append(", RuntimeEntityType ").Append(targetEntityType) + .Append(", RuntimeEntityType ").Append(joinEntityType).AppendLine(")") .AppendLine("{"); using (mainBuilder.Indent()) @@ -999,8 +1077,9 @@ private void CreateSkipNavigation( navigationVariable }; - var parameters = new CSharpSlimAnnotationCodeGeneratorParameters( + var parameters = new CSharpRuntimeAnnotationCodeGeneratorParameters( navigationVariable, + className, mainBuilder, methodBuilder, namespaces, @@ -1011,16 +1090,15 @@ private void CreateSkipNavigation( .Append(declaringEntityType).AppendLine(".AddSkipNavigation(").IncrementIndent() .Append(_code.Literal(navigation.Name)).AppendLine(",") .Append(targetEntityType).AppendLine(",") - .Append(_code.Literal(navigation.Name)).AppendLine(",") .Append(joinEntityType).AppendLine(".FindForeignKey("); using (mainBuilder.Indent()) { - FindProperties(joinEntityType, navigation.ForeignKey.PrincipalKey.Properties, mainBuilder); + FindProperties(joinEntityType, navigation.ForeignKey.Properties, mainBuilder); mainBuilder.AppendLine(",") .Append(declaringEntityType).Append(".FindKey("); FindProperties(declaringEntityType, navigation.ForeignKey.PrincipalKey.Properties, mainBuilder); mainBuilder.AppendLine("),") - .Append(declaringEntityType).AppendLine(")"); + .Append(declaringEntityType).Append(")"); } mainBuilder.AppendLine(",") @@ -1076,16 +1154,17 @@ private void CreateAnnotations( IEntityType entityType, IndentedStringBuilder mainBuilder, IndentedStringBuilder methodBuilder, - SortedSet namespaces) + SortedSet namespaces, + string className) { mainBuilder.AppendLine() .Append("public static void CreateAnnotations") - .AppendLine("(SlimEntityType slimEntityType)") + .AppendLine("(RuntimeEntityType runtimeEntityType)") .AppendLine("{"); using (mainBuilder.Indent()) { - var entityTypeVariable = "slimEntityType"; + var entityTypeVariable = "runtimeEntityType"; var variables = new HashSet { entityTypeVariable @@ -1094,22 +1173,29 @@ private void CreateAnnotations( CreateAnnotations( entityType, _annotationCodeGenerator.Generate, - new CSharpSlimAnnotationCodeGeneratorParameters( + new CSharpRuntimeAnnotationCodeGeneratorParameters( entityTypeVariable, + className, mainBuilder, methodBuilder, namespaces, variables)); + + mainBuilder + .AppendLine() + .AppendLine("Customize(runtimeEntityType);"); } mainBuilder - .AppendLine("}"); + .AppendLine("}") + .AppendLine() + .AppendLine("static partial void Customize(RuntimeEntityType runtimeEntityType);"); } private void CreateAnnotations( TAnnotatable annotatable, - Action process, - CSharpSlimAnnotationCodeGeneratorParameters parameters) + Action process, + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) where TAnnotatable : IAnnotatable { process(annotatable, diff --git a/src/EFCore.InMemory/Design/Internal/InMemoryCSharpSlimAnnotationCodeGenerator.cs b/src/EFCore.InMemory/Design/Internal/InMemoryCSharpRuntimeAnnotationCodeGenerator.cs similarity index 84% rename from src/EFCore.InMemory/Design/Internal/InMemoryCSharpSlimAnnotationCodeGenerator.cs rename to src/EFCore.InMemory/Design/Internal/InMemoryCSharpRuntimeAnnotationCodeGenerator.cs index 094f9c5dd98..e50c30a4fcb 100644 --- a/src/EFCore.InMemory/Design/Internal/InMemoryCSharpSlimAnnotationCodeGenerator.cs +++ b/src/EFCore.InMemory/Design/Internal/InMemoryCSharpRuntimeAnnotationCodeGenerator.cs @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Design.Internal /// 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 class InMemoryCSharpSlimAnnotationCodeGenerator : CSharpSlimAnnotationCodeGenerator + public class InMemoryCSharpRuntimeAnnotationCodeGenerator : CSharpRuntimeAnnotationCodeGenerator { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -19,8 +19,8 @@ public class InMemoryCSharpSlimAnnotationCodeGenerator : CSharpSlimAnnotationCod /// 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 InMemoryCSharpSlimAnnotationCodeGenerator( - CSharpSlimAnnotationCodeGeneratorDependencies dependencies) + public InMemoryCSharpRuntimeAnnotationCodeGenerator( + CSharpRuntimeAnnotationCodeGeneratorDependencies dependencies) : base(dependencies) { } diff --git a/src/EFCore.InMemory/Design/Internal/InMemoryDesignTimeServices.cs b/src/EFCore.InMemory/Design/Internal/InMemoryDesignTimeServices.cs index b173b5133cf..80a7393e7b9 100644 --- a/src/EFCore.InMemory/Design/Internal/InMemoryDesignTimeServices.cs +++ b/src/EFCore.InMemory/Design/Internal/InMemoryDesignTimeServices.cs @@ -29,7 +29,7 @@ public virtual void ConfigureDesignTimeServices(IServiceCollection serviceCollec serviceCollection.AddEntityFrameworkInMemoryDatabase(); new EntityFrameworkDesignServicesBuilder(serviceCollection) - .TryAdd() + .TryAdd() .TryAddCoreServices(); } } diff --git a/src/EFCore.Relational/Design/EntityFrameworkRelationalDesignServicesBuilder.cs b/src/EFCore.Relational/Design/EntityFrameworkRelationalDesignServicesBuilder.cs index ed8ec45ed71..53d64a35f14 100644 --- a/src/EFCore.Relational/Design/EntityFrameworkRelationalDesignServicesBuilder.cs +++ b/src/EFCore.Relational/Design/EntityFrameworkRelationalDesignServicesBuilder.cs @@ -77,12 +77,12 @@ protected override ServiceCharacteristics GetServiceCharacteristics(Type service public override EntityFrameworkServicesBuilder TryAddCoreServices() { TryAdd(); - TryAdd(); + TryAdd(); ServiceCollectionMap.GetInfrastructure() .AddDependencySingleton() .AddDependencySingleton() - .AddDependencySingleton(); + .AddDependencySingleton(); return base.TryAddCoreServices(); } diff --git a/src/EFCore.Relational/Design/RelationalCSharpSlimAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/RelationalCSharpRuntimeAnnotationCodeGenerator.cs similarity index 82% rename from src/EFCore.Relational/Design/RelationalCSharpSlimAnnotationCodeGenerator.cs rename to src/EFCore.Relational/Design/RelationalCSharpRuntimeAnnotationCodeGenerator.cs index 36825261e75..dfaba72a23c 100644 --- a/src/EFCore.Relational/Design/RelationalCSharpSlimAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/RelationalCSharpRuntimeAnnotationCodeGenerator.cs @@ -15,19 +15,19 @@ namespace Microsoft.EntityFrameworkCore.Design { /// /// - /// Base class to be used by relational database providers when implementing an + /// Base class to be used by relational database providers when implementing an /// /// - public class RelationalCSharpSlimAnnotationCodeGenerator : CSharpSlimAnnotationCodeGenerator + public class RelationalCSharpRuntimeAnnotationCodeGenerator : CSharpRuntimeAnnotationCodeGenerator { /// /// Initializes a new instance of this class. /// /// Parameter object containing dependencies for this service. /// Parameter object containing relational dependencies for this service. - public RelationalCSharpSlimAnnotationCodeGenerator( - CSharpSlimAnnotationCodeGeneratorDependencies dependencies, - RelationalCSharpSlimAnnotationCodeGeneratorDependencies relationalDependencies) + public RelationalCSharpRuntimeAnnotationCodeGenerator( + CSharpRuntimeAnnotationCodeGeneratorDependencies dependencies, + RelationalCSharpRuntimeAnnotationCodeGeneratorDependencies relationalDependencies) : base(dependencies) { Check.NotNull(relationalDependencies, nameof(relationalDependencies)); @@ -38,19 +38,22 @@ public RelationalCSharpSlimAnnotationCodeGenerator( /// /// Parameter object containing relational dependencies for this service. /// - protected virtual RelationalCSharpSlimAnnotationCodeGeneratorDependencies RelationalDependencies { get; } + protected virtual RelationalCSharpRuntimeAnnotationCodeGeneratorDependencies RelationalDependencies { get; } /// - public override void Generate(IModel model, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public override void Generate(IModel model, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { + var annotations = parameters.Annotations; if (parameters.IsRuntime) { - parameters.Annotations.Remove(RelationalAnnotationNames.ModelDependencies); - parameters.Annotations.Remove(RelationalAnnotationNames.RelationalModel); + annotations.Remove(RelationalAnnotationNames.ModelDependencies); + annotations.Remove(RelationalAnnotationNames.RelationalModel); } else { - if (parameters.Annotations.TryGetAndRemove(RelationalAnnotationNames.DbFunctions, + annotations.Remove(RelationalAnnotationNames.Collation); + + if (annotations.TryGetAndRemove(RelationalAnnotationNames.DbFunctions, out SortedDictionary functions)) { parameters.Namespaces.Add(typeof(SortedDictionary<,>).Namespace!); @@ -67,7 +70,7 @@ public override void Generate(IModel model, CSharpSlimAnnotationCodeGeneratorPar GenerateSimpleAnnotation(RelationalAnnotationNames.DbFunctions, functionsVariable, parameters); } - if (parameters.Annotations.TryGetAndRemove(RelationalAnnotationNames.Sequences, + if (annotations.TryGetAndRemove(RelationalAnnotationNames.Sequences, out SortedDictionary<(string, string?), ISequence> sequences)) { parameters.Namespaces.Add(typeof(SortedDictionary<,>).Namespace!); @@ -90,7 +93,7 @@ public override void Generate(IModel model, CSharpSlimAnnotationCodeGeneratorPar private void Create( IDbFunction function, string functionsVariable, - CSharpSlimAnnotationCodeGeneratorParameters parameters) + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { if (function.Translation != null) { @@ -100,7 +103,8 @@ private void Create( if (function is IConventionDbFunction conventionFunction && conventionFunction.GetTypeMappingConfigurationSource() != null) { - throw new InvalidOperationException(RelationalStrings.CompiledModelFunctionTypeMapping(function.Name)); + throw new InvalidOperationException(RelationalStrings.CompiledModelFunctionTypeMapping( + function.Name, "Customize()", parameters.ClassName)); } AddNamespace(function.ReturnType, parameters.Namespaces); @@ -110,7 +114,7 @@ private void Create( function.MethodInfo?.Name ?? function.Name, parameters.ScopeVariables, capitalize: false); var mainBuilder = parameters.MainBuilder; mainBuilder - .Append("var ").Append(functionVariable).AppendLine(" = new SlimDbFunction(").IncrementIndent() + .Append("var ").Append(functionVariable).AppendLine(" = new RuntimeDbFunction(").IncrementIndent() .Append(code.Literal(function.ModelName)).AppendLine(",") .Append(parameters.TargetName).AppendLine(",") .Append(code.Literal(function.ReturnType)).AppendLine(",") @@ -190,18 +194,19 @@ private void Create( /// /// The function to which the annotations are applied. /// Additional parameters used during code generation. - public virtual void Generate(IDbFunction function, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public virtual void Generate(IDbFunction function, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { GenerateSimpleAnnotations(parameters); } - private void Create(IDbFunctionParameter parameter, CSharpSlimAnnotationCodeGeneratorParameters parameters) + private void Create(IDbFunctionParameter parameter, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { if (parameter is IConventionDbFunctionParameter conventionParameter && conventionParameter.GetTypeMappingConfigurationSource() != null) { throw new InvalidOperationException( - RelationalStrings.CompiledModelFunctionParameterTypeMapping(parameter.Function.Name, parameter.Name)); + RelationalStrings.CompiledModelFunctionParameterTypeMapping( + parameter.Function.Name, parameter.Name, "Customize()", parameters.ClassName)); } AddNamespace(parameter.ClrType, parameters.Namespaces); @@ -230,23 +235,21 @@ private void Create(IDbFunctionParameter parameter, CSharpSlimAnnotationCodeGene /// /// The function parameter to which the annotations are applied. /// Additional parameters used during code generation. - public virtual void Generate(IDbFunctionParameter functionParameter, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public virtual void Generate(IDbFunctionParameter functionParameter, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { GenerateSimpleAnnotations(parameters); } - private void Create(ISequence sequence, string sequencesVariable, CSharpSlimAnnotationCodeGeneratorParameters parameters) + private void Create(ISequence sequence, string sequencesVariable, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var code = Dependencies.CSharpHelper; var sequenceVariable = code.Identifier(sequence.Name, parameters.ScopeVariables, capitalize: false); var mainBuilder = parameters.MainBuilder; mainBuilder - .Append("var ").Append(sequenceVariable).AppendLine(" = new SlimSequence(").IncrementIndent() + .Append("var ").Append(sequenceVariable).AppendLine(" = new RuntimeSequence(").IncrementIndent() .Append(code.Literal(sequence.Name)).AppendLine(",") .Append(parameters.TargetName).AppendLine(",") - .Append(code.Literal(sequence.Type)).AppendLine(",") - .Append(code.Literal(sequence.StartValue)).AppendLine(",") - .Append(code.Literal(sequence.IncrementBy)); + .Append(code.Literal(sequence.Type)); if (sequence.Schema != null) { @@ -254,6 +257,18 @@ private void Create(ISequence sequence, string sequencesVariable, CSharpSlimAnno .Append("schema: ").Append(code.Literal(sequence.Schema)); } + if (sequence.StartValue != Sequence.DefaultStartValue) + { + mainBuilder.AppendLine(",") + .Append("startValue: ").Append(code.Literal(sequence.StartValue)); + } + + if (sequence.IncrementBy != Sequence.DefaultIncrementBy) + { + mainBuilder.AppendLine(",") + .Append("incrementBy: ").Append(code.Literal(sequence.IncrementBy)); + } + if (sequence.IsCyclic) { mainBuilder.AppendLine(",") @@ -280,8 +295,6 @@ private void Create(ISequence sequence, string sequencesVariable, CSharpSlimAnno Generate, parameters with { TargetName = sequenceVariable }); - mainBuilder.AppendLine(); - mainBuilder .Append(sequencesVariable).Append("[(").Append(code.Literal(sequence.Name)).Append(", ") .Append(code.UnknownLiteral(sequence.Schema)).Append(")] = ") @@ -294,13 +307,13 @@ private void Create(ISequence sequence, string sequencesVariable, CSharpSlimAnno /// /// The sequence to which the annotations are applied. /// Additional parameters used during code generation. - public virtual void Generate(ISequence sequence, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public virtual void Generate(ISequence sequence, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { GenerateSimpleAnnotations(parameters); } /// - public override void Generate(IEntityType entityType, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var annotations = parameters.Annotations; if (parameters.IsRuntime) @@ -314,9 +327,9 @@ public override void Generate(IEntityType entityType, CSharpSlimAnnotationCodeGe else { if (annotations.TryGetAndRemove(RelationalAnnotationNames.CheckConstraints, - out Dictionary constraints)) + out SortedDictionary constraints)) { - parameters.Namespaces.Add(typeof(SortedDictionary<,>).Namespace!); + parameters.Namespaces.Add(typeof(SortedDictionary).Namespace!); var constraintsVariable = Dependencies.CSharpHelper.Identifier("constraints", parameters.ScopeVariables, capitalize: false); parameters.MainBuilder .Append("var ").Append(constraintsVariable).AppendLine(" = new SortedDictionary();"); @@ -329,6 +342,9 @@ public override void Generate(IEntityType entityType, CSharpSlimAnnotationCodeGe GenerateSimpleAnnotation(RelationalAnnotationNames.CheckConstraints, constraintsVariable, parameters); } + annotations.Remove(RelationalAnnotationNames.Comment); + annotations.Remove(RelationalAnnotationNames.IsTableExcludedFromMigrations); + // These need to be set explicitly to prevent default values from being generated annotations[RelationalAnnotationNames.TableName] = entityType.GetTableName(); annotations[RelationalAnnotationNames.Schema] = entityType.GetSchema(); @@ -341,13 +357,13 @@ public override void Generate(IEntityType entityType, CSharpSlimAnnotationCodeGe base.Generate(entityType, parameters); } - private void Create(ICheckConstraint constraint, string constraintsVariable, CSharpSlimAnnotationCodeGeneratorParameters parameters) + private void Create(ICheckConstraint constraint, string constraintsVariable, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var code = Dependencies.CSharpHelper; var constraintVariable = code.Identifier(constraint.Name, parameters.ScopeVariables, capitalize: false); var mainBuilder = parameters.MainBuilder; mainBuilder - .Append("var ").Append(constraintVariable).AppendLine(" = new SlimCheckConstraint(").IncrementIndent() + .Append("var ").Append(constraintVariable).AppendLine(" = new RuntimeCheckConstraint(").IncrementIndent() .Append(code.Literal(constraint.Name)).AppendLine(",") .Append(parameters.TargetName).AppendLine(",") .Append(code.Literal(constraint.Sql)).AppendLine(");").DecrementIndent() @@ -358,8 +374,6 @@ private void Create(ICheckConstraint constraint, string constraintsVariable, CSh Generate, parameters with { TargetName = constraintVariable }); - mainBuilder.AppendLine(); - mainBuilder .Append(constraintsVariable).Append("[").Append(code.Literal(constraint.Name)).Append("] = ") .Append(constraintVariable).AppendLine(";") @@ -371,13 +385,13 @@ private void Create(ICheckConstraint constraint, string constraintsVariable, CSh /// /// The check constraint to which the annotations are applied. /// Additional parameters used during code generation. - public virtual void Generate(ICheckConstraint constraint, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public virtual void Generate(ICheckConstraint constraint, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { GenerateSimpleAnnotations(parameters); } /// - public override void Generate(IProperty property, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public override void Generate(IProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var annotations = parameters.Annotations; if (parameters.IsRuntime) @@ -390,10 +404,13 @@ public override void Generate(IProperty property, CSharpSlimAnnotationCodeGenera } else { + annotations.Remove(RelationalAnnotationNames.Comment); + annotations.Remove(RelationalAnnotationNames.Collation); + if (annotations.TryGetAndRemove(RelationalAnnotationNames.RelationalOverrides, out SortedDictionary overrides)) { - parameters.Namespaces.Add(typeof(SortedDictionary<,>).Namespace!); + parameters.Namespaces.Add(typeof(SortedDictionary).Namespace!); var overridesVariable = Dependencies.CSharpHelper.Identifier("overrides", parameters.ScopeVariables, capitalize: false); parameters.MainBuilder .Append("var ").Append(overridesVariable).AppendLine(" = new SortedDictionary();"); @@ -414,26 +431,24 @@ private void Create( IRelationalPropertyOverrides overrides, StoreObjectIdentifier storeObject, string overridesVariable, - CSharpSlimAnnotationCodeGeneratorParameters parameters) + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var code = Dependencies.CSharpHelper; var overrideVariable = code.Identifier(parameters.TargetName + Capitalize(storeObject.Name), parameters.ScopeVariables, capitalize: false); var mainBuilder = parameters.MainBuilder; mainBuilder - .Append("var ").Append(overrideVariable).AppendLine(" = new SlimRelationalPropertyOverrides(").IncrementIndent() + .Append("var ").Append(overrideVariable).AppendLine(" = new RuntimeRelationalPropertyOverrides(").IncrementIndent() .Append(parameters.TargetName).AppendLine(",") .Append(code.Literal(overrides.ColumnNameOverriden)).AppendLine(",") - .Append(code.UnknownLiteral(overrides.ColumnName)).AppendLine(");").DecrementIndent() - .AppendLine(); + .Append(code.UnknownLiteral(overrides.ColumnName)).AppendLine(");").DecrementIndent(); CreateAnnotations( overrides, GenerateOverrides, parameters with { TargetName = overrideVariable }); - mainBuilder.AppendLine() - .Append(overridesVariable).Append("[StoreObjectIdentifier."); + mainBuilder.Append(overridesVariable).Append("[StoreObjectIdentifier."); switch (storeObject.StoreObjectType) { @@ -459,8 +474,7 @@ private void Create( mainBuilder .Append("] = ") - .Append(overrideVariable).AppendLine(";") - .AppendLine(); + .Append(overrideVariable).AppendLine(";"); } /// @@ -468,13 +482,13 @@ private void Create( /// /// The property overrides to which the annotations are applied. /// Additional parameters used during code generation. - public virtual void GenerateOverrides(IAnnotatable overrides, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public virtual void GenerateOverrides(IAnnotatable overrides, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { GenerateSimpleAnnotations(parameters); } /// - public override void Generate(IKey key, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public override void Generate(IKey key, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { if (parameters.IsRuntime) { @@ -485,7 +499,7 @@ public override void Generate(IKey key, CSharpSlimAnnotationCodeGeneratorParamet } /// - public override void Generate(IForeignKey foreignKey, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public override void Generate(IForeignKey foreignKey, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { if (parameters.IsRuntime) { @@ -496,45 +510,37 @@ public override void Generate(IForeignKey foreignKey, CSharpSlimAnnotationCodeGe } /// - public override void Generate(INavigation navigation, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public override void Generate(INavigation navigation, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { base.Generate(navigation, parameters); } /// - public override void Generate(ISkipNavigation navigation, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public override void Generate(ISkipNavigation navigation, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { base.Generate(navigation, parameters); } /// - public override void Generate(IIndex index, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public override void Generate(IIndex index, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { + var annotations = parameters.Annotations; if (parameters.IsRuntime) { - parameters.Annotations.Remove(RelationalAnnotationNames.TableIndexMappings); + annotations.Remove(RelationalAnnotationNames.TableIndexMappings); } - - base.Generate(index, parameters); - } - - private void GenerateSimpleAnnotations(CSharpSlimAnnotationCodeGeneratorParameters parameters) - { - foreach (var (name, value) in parameters.Annotations) + else { - if (value != null) - { - AddNamespace(value.GetType(), parameters.Namespaces); - } - - GenerateSimpleAnnotation(name, Dependencies.CSharpHelper.UnknownLiteral(value), parameters); + annotations.Remove(RelationalAnnotationNames.Filter); } + + base.Generate(index, parameters); } private void CreateAnnotations( TAnnotatable annotatable, - Action process, - CSharpSlimAnnotationCodeGeneratorParameters parameters) + Action process, + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) where TAnnotatable : IAnnotatable { process(annotatable, @@ -559,9 +565,13 @@ private string Capitalize(string @string) case 0: return @string; case 1: - return char.ToUpper(@string[0]).ToString(); + return char.ToUpperInvariant(@string[0]).ToString(); default: - return char.ToUpper(@string[0]) + @string[1..]; + if (char.IsUpper(@string[0])) + { + return @string; + } + return char.ToUpperInvariant(@string[0]) + @string[1..]; } } } diff --git a/src/EFCore.Relational/Design/RelationalCSharpSlimAnnotationCodeGeneratorDependencies.cs b/src/EFCore.Relational/Design/RelationalCSharpRuntimeAnnotationCodeGeneratorDependencies.cs similarity index 90% rename from src/EFCore.Relational/Design/RelationalCSharpSlimAnnotationCodeGeneratorDependencies.cs rename to src/EFCore.Relational/Design/RelationalCSharpRuntimeAnnotationCodeGeneratorDependencies.cs index eeb1972fc10..0d45dd23bcb 100644 --- a/src/EFCore.Relational/Design/RelationalCSharpSlimAnnotationCodeGeneratorDependencies.cs +++ b/src/EFCore.Relational/Design/RelationalCSharpRuntimeAnnotationCodeGeneratorDependencies.cs @@ -8,7 +8,7 @@ namespace Microsoft.EntityFrameworkCore.Design { /// /// - /// Service dependencies parameter class for + /// Service dependencies parameter class for /// /// /// This type is typically used by database providers (and other extensions). It is generally @@ -23,11 +23,11 @@ namespace Microsoft.EntityFrameworkCore.Design /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// - public sealed record RelationalCSharpSlimAnnotationCodeGeneratorDependencies + public sealed record RelationalCSharpRuntimeAnnotationCodeGeneratorDependencies { /// /// - /// Creates the service dependencies parameter object for a . + /// Creates the service dependencies parameter object for a . /// /// /// Do not call this constructor directly from either provider or application code as it may change @@ -45,7 +45,7 @@ public sealed record RelationalCSharpSlimAnnotationCodeGeneratorDependencies /// /// [EntityFrameworkInternal] - public RelationalCSharpSlimAnnotationCodeGeneratorDependencies() + public RelationalCSharpRuntimeAnnotationCodeGeneratorDependencies() { } } diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index eb7f853affb..ed4cc697437 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -727,7 +728,9 @@ public static IEnumerable GetCheckConstraints(this IEntityType /// The entity type. /// The comment for the table this entity is mapped to. public static string? GetComment(this IReadOnlyEntityType entityType) - => (string?)entityType[RelationalAnnotationNames.Comment]; + => entityType is RuntimeEntityType + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (string?)entityType[RelationalAnnotationNames.Comment]; /// /// Configures a comment to be applied to the table this entity is mapped to. @@ -866,6 +869,11 @@ public static IEnumerable FindRowInternalForeignKeys( /// A value indicating whether the associated table is ignored by Migrations. public static bool IsTableExcludedFromMigrations(this IReadOnlyEntityType entityType) { + if (entityType is RuntimeEntityType) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + var excluded = (bool?)entityType[RelationalAnnotationNames.IsTableExcludedFromMigrations]; if (excluded != null) { diff --git a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs index 4ad059c803e..6b7c5b83988 100644 --- a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; @@ -206,7 +207,9 @@ public static void SetName(this IConventionIndex index, string? name, bool fromD /// The index. /// The index filter expression. public static string? GetFilter(this IReadOnlyIndex index) - => (string?)index.FindAnnotation(RelationalAnnotationNames.Filter)?.Value; + => index is RuntimeIndex + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (string?)index.FindAnnotation(RelationalAnnotationNames.Filter)?.Value; /// /// Returns the index filter expression. @@ -216,6 +219,11 @@ public static void SetName(this IConventionIndex index, string? name, bool fromD /// The index filter expression. public static string? GetFilter(this IReadOnlyIndex index, in StoreObjectIdentifier storeObject) { + if (index is RuntimeIndex) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + var annotation = index.FindAnnotation(RelationalAnnotationNames.Filter); if (annotation != null) { diff --git a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs index 4ae4798b476..24fe265bcb0 100644 --- a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs @@ -483,7 +483,9 @@ public static IEnumerable GetDbFunctions(this IModel model) /// The model to get the collation for. /// The collation. public static string? GetCollation(this IReadOnlyModel model) - => (string?)model[RelationalAnnotationNames.Collation]; + => model is RuntimeModel + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (string?)model[RelationalAnnotationNames.Collation]; /// /// Sets the database collation. diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index 5e1c12c32ff..f491e3c8fad 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -1050,7 +1050,9 @@ private static bool IsOptionalSharingDependent( /// The property. /// The comment for the column this property is mapped to. public static string? GetComment(this IReadOnlyProperty property) - => (string?)property.FindAnnotation(RelationalAnnotationNames.Comment)?.Value; + => property is RuntimeProperty + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (string?)property.FindAnnotation(RelationalAnnotationNames.Comment)?.Value; /// /// Returns the comment for the column this property is mapped to. @@ -1060,6 +1062,11 @@ private static bool IsOptionalSharingDependent( /// The comment for the column this property is mapped to. public static string? GetComment(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { + if (property is RuntimeProperty) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + var annotation = property.FindAnnotation(RelationalAnnotationNames.Comment); if (annotation != null) { @@ -1114,7 +1121,9 @@ public static void SetComment(this IMutableProperty property, string? comment) /// The property. /// The collation for the column this property is mapped to. public static string? GetCollation(this IReadOnlyProperty property) - => (string?)property.FindAnnotation(RelationalAnnotationNames.Collation)?.Value; + => property is RuntimeProperty + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (string?)property.FindAnnotation(RelationalAnnotationNames.Collation)?.Value; /// /// Returns the collation to be used for the column. @@ -1124,6 +1133,11 @@ public static void SetComment(this IMutableProperty property, string? comment) /// The collation for the column this property is mapped to. public static string? GetCollation(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { + if (property is RuntimeProperty) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + var annotation = property.FindAnnotation(RelationalAnnotationNames.Collation); if (annotation != null) { diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs b/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs index 860c980df2a..bcc8b337ec0 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs @@ -48,11 +48,12 @@ public RelationalModelRuntimeInitializer( /// Initializes the given model with runtime dependencies. /// /// The model to initialize. + /// Whether the model should contain design-time configuration. /// /// indicates that only pre-validation initialization should be performed; /// indicates that only post-validation initialization should be performed. /// - protected override void InitializeModel(IModel model, bool preValidation) + protected override void InitializeModel(IModel model, bool designTime, bool preValidation) { if (preValidation) { @@ -60,7 +61,7 @@ protected override void InitializeModel(IModel model, bool preValidation) } else { - RelationalModel.Add(model, RelationalDependencies.RelationalAnnotationProvider); + RelationalModel.Add(model, RelationalDependencies.RelationalAnnotationProvider, designTime); } } } diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalModelConvention.cs index db44367da91..7aa249ffd77 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalModelConvention.cs @@ -32,6 +32,6 @@ public RelationalModelConvention( /// public virtual IModel ProcessModelFinalized(IModel model) - => RelationalModel.Add(model, RelationalDependencies.RelationalAnnotationProvider); + => RelationalModel.Add(model, RelationalDependencies.RelationalAnnotationProvider, designTime: true); } } diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs index 1d4bddc8ace..bd2a118645e 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs @@ -51,10 +51,12 @@ protected override void ProcessModelAnnotations( if (runtime) { annotations[RelationalAnnotationNames.RelationalModel] = - RelationalModel.Create(runtimeModel, RelationalDependencies.RelationalAnnotationProvider); + RelationalModel.Create(runtimeModel, RelationalDependencies.RelationalAnnotationProvider, designTime: false); } else { + annotations.Remove(RelationalAnnotationNames.Collation); + if (annotations.TryGetValue(RelationalAnnotationNames.DbFunctions, out var functions)) { var runtimeFunctions = new SortedDictionary(); @@ -119,8 +121,8 @@ protected override void ProcessEntityTypeAnnotations( { if (annotations.TryGetValue(RelationalAnnotationNames.CheckConstraints, out var constraints)) { - var runtimeCheckConstraints = new Dictionary(); - foreach (var constraintPair in (Dictionary?)constraints!) + var runtimeCheckConstraints = new SortedDictionary(); + foreach (var constraintPair in (SortedDictionary?)constraints!) { var runtimeCheckConstraint = Create(constraintPair.Value, runtimeEntityType); runtimeCheckConstraints[constraintPair.Key] = runtimeCheckConstraint; @@ -132,6 +134,9 @@ protected override void ProcessEntityTypeAnnotations( annotations[RelationalAnnotationNames.CheckConstraints] = runtimeCheckConstraints; } + annotations.Remove(RelationalAnnotationNames.Comment); + annotations.Remove(RelationalAnnotationNames.IsTableExcludedFromMigrations); + // These need to be set explicitly to prevent default values from being generated annotations[RelationalAnnotationNames.TableName] = entityType.GetTableName(); annotations[RelationalAnnotationNames.Schema] = entityType.GetSchema(); @@ -217,9 +222,9 @@ private RuntimeSequence Create(ISequence sequence, RuntimeModel runtimeModel) sequence.Name, runtimeModel, sequence.Type, + sequence.Schema, sequence.StartValue, sequence.IncrementBy, - sequence.Schema, sequence.IsCyclic, sequence.MinValue, sequence.MaxValue); @@ -282,13 +287,16 @@ protected override void ProcessPropertyAnnotations( } else { + annotations.Remove(RelationalAnnotationNames.Comment); + annotations.Remove(RelationalAnnotationNames.Collation); + if (annotations.TryGetValue(RelationalAnnotationNames.RelationalOverrides, out var overrides)) { var runtimePropertyOverrides = new SortedDictionary(); foreach (var overridesPair in (SortedDictionary?)overrides!) { - var runtimeOverrides = Create((IRelationalPropertyOverrides)overridesPair.Value, slimProperty); - slimPropertyOverrides[overridesPair.Key] = slimOverrides; + var runtimeOverrides = Create((IRelationalPropertyOverrides)overridesPair.Value, runtimeProperty); + runtimePropertyOverrides[overridesPair.Key] = runtimeOverrides; CreateAnnotations((IRelationalPropertyOverrides)overridesPair.Value, runtimeOverrides, static (convention, annotations, source, target, runtime) => @@ -363,6 +371,10 @@ protected override void ProcessIndexAnnotations( { annotations.Remove(RelationalAnnotationNames.TableIndexMappings); } + else + { + annotations.Remove(RelationalAnnotationNames.Filter); + } } /// diff --git a/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs b/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs index f11c0d012bc..8b51b8224d8 100644 --- a/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs +++ b/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs @@ -25,97 +25,111 @@ public interface IRelationalAnnotationProvider /// /// The database model. /// The annotations. - IEnumerable For(IRelationalModel model); + /// Whether the model should contain design-time configuration. + IEnumerable For(IRelationalModel model, bool designTime); /// /// Gets provider-specific annotations for the given . /// /// The table. /// The annotations. - IEnumerable For(ITable table); + /// Whether the model should contain design-time configuration. + IEnumerable For(ITable table, bool designTime); /// /// Gets provider-specific annotations for the given . /// /// The column. /// The annotations. - IEnumerable For(IColumn column); + /// Whether the model should contain design-time configuration. + IEnumerable For(IColumn column, bool designTime); /// /// Gets provider-specific annotations for the given . /// /// The view. /// The annotations. - IEnumerable For(IView view); + /// Whether the model should contain design-time configuration. + IEnumerable For(IView view, bool designTime); /// /// Gets provider-specific annotations for the given . /// /// The column. /// The annotations. - IEnumerable For(IViewColumn column); + /// Whether the model should contain design-time configuration. + IEnumerable For(IViewColumn column, bool designTime); /// /// Gets provider-specific annotations for the given . /// /// The SQL query. /// The annotations. - IEnumerable For(ISqlQuery sqlQuery); + /// Whether the model should contain design-time configuration. + IEnumerable For(ISqlQuery sqlQuery, bool designTime); /// /// Gets provider-specific annotations for the given . /// /// The column. /// The annotations. - IEnumerable For(ISqlQueryColumn column); + /// Whether the model should contain design-time configuration. + IEnumerable For(ISqlQueryColumn column, bool designTime); /// /// Gets provider-specific annotations for the given . /// /// The function. /// The annotations. - IEnumerable For(IStoreFunction function); + /// Whether the model should contain design-time configuration. + IEnumerable For(IStoreFunction function, bool designTime); /// /// Gets provider-specific annotations for the given . /// /// The column. /// The annotations. - IEnumerable For(IFunctionColumn column); + /// Whether the model should contain design-time configuration. + IEnumerable For(IFunctionColumn column, bool designTime); /// /// Gets provider-specific annotations for the given . /// /// The unique constraint. /// The annotations. - IEnumerable For(IUniqueConstraint constraint); + /// Whether the model should contain design-time configuration. + IEnumerable For(IUniqueConstraint constraint, bool designTime); /// /// Gets provider-specific annotations for the given . /// /// The index. /// The annotations. - IEnumerable For(ITableIndex index); + /// Whether the model should contain design-time configuration. + IEnumerable For(ITableIndex index, bool designTime); /// /// Gets provider-specific annotations for the given . /// /// The foreign key. /// The annotations. - IEnumerable For(IForeignKeyConstraint foreignKey); + /// Whether the model should contain design-time configuration. + IEnumerable For(IForeignKeyConstraint foreignKey, bool designTime); /// /// Gets provider-specific annotations for the given . /// /// The sequence. /// The annotations. - IEnumerable For(ISequence sequence); + /// Whether the model should contain design-time configuration. + IEnumerable For(ISequence sequence, bool designTime); /// /// Gets provider-specific annotations for the given . /// /// The check constraint. /// The annotations. - IEnumerable For(ICheckConstraint checkConstraint); + /// Whether the model should contain design-time configuration. + IEnumerable For(ICheckConstraint checkConstraint, bool designTime); } } diff --git a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs index 1613dba5fc8..dbcff4117dd 100644 --- a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs +++ b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs @@ -45,7 +45,7 @@ public CheckConstraint( var constraints = GetConstraintsDictionary(EntityType); if (constraints == null) { - constraints = new Dictionary(); + constraints = new SortedDictionary(); ((IMutableEntityType)EntityType).SetOrRemoveAnnotation(RelationalAnnotationNames.CheckConstraints, constraints); } @@ -165,8 +165,8 @@ public virtual void UpdateConfigurationSource(ConfigurationSource configurationS _configurationSource = configurationSource.Max(_configurationSource); } - private static Dictionary? GetConstraintsDictionary(IReadOnlyEntityType entityType) - => (Dictionary?)entityType[RelationalAnnotationNames.CheckConstraints]; + private static SortedDictionary? GetConstraintsDictionary(IReadOnlyEntityType entityType) + => (SortedDictionary?)entityType[RelationalAnnotationNames.CheckConstraints]; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 45e906ed7f7..960d97c54c8 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -120,9 +120,10 @@ public RelationalModel(IModel model) /// public static IModel Add( IModel model, - IRelationalAnnotationProvider? relationalAnnotationProvider) + IRelationalAnnotationProvider? relationalAnnotationProvider, + bool designTime) { - model.AddRuntimeAnnotation(RelationalAnnotationNames.RelationalModel, Create(model, relationalAnnotationProvider)); + model.AddRuntimeAnnotation(RelationalAnnotationNames.RelationalModel, Create(model, relationalAnnotationProvider, designTime)); return model; } @@ -134,7 +135,8 @@ public static IModel Add( /// public static IRelationalModel Create( IModel model, - IRelationalAnnotationProvider? relationalAnnotationProvider) + IRelationalAnnotationProvider? relationalAnnotationProvider, + bool designTime) { var databaseModel = new RelationalModel(model); @@ -162,30 +164,30 @@ public static IRelationalModel Create( { foreach (Column column in table.Columns.Values) { - column.AddAnnotations(relationalAnnotationProvider.For(column)); + column.AddAnnotations(relationalAnnotationProvider.For(column, designTime)); } foreach (var constraint in table.UniqueConstraints.Values) { - constraint.AddAnnotations(relationalAnnotationProvider.For(constraint)); + constraint.AddAnnotations(relationalAnnotationProvider.For(constraint, designTime)); } foreach (var index in table.Indexes.Values) { - index.AddAnnotations(relationalAnnotationProvider.For(index)); + index.AddAnnotations(relationalAnnotationProvider.For(index, designTime)); } foreach (var constraint in table.ForeignKeyConstraints.Values) { - constraint.AddAnnotations(relationalAnnotationProvider.For(constraint)); + constraint.AddAnnotations(relationalAnnotationProvider.For(constraint, designTime)); } foreach (var checkConstraint in ((ITable)table).CheckConstraints) { - ((AnnotatableBase)checkConstraint).AddAnnotations(relationalAnnotationProvider.For(checkConstraint)); + ((AnnotatableBase)checkConstraint).AddAnnotations(relationalAnnotationProvider.For(checkConstraint, designTime)); } - table.AddAnnotations(relationalAnnotationProvider.For(table)); + table.AddAnnotations(relationalAnnotationProvider.For(table, designTime)); } } @@ -197,10 +199,10 @@ public static IRelationalModel Create( { foreach (ViewColumn viewColumn in view.Columns.Values) { - viewColumn.AddAnnotations(relationalAnnotationProvider.For(viewColumn)); + viewColumn.AddAnnotations(relationalAnnotationProvider.For(viewColumn, designTime)); } - view.AddAnnotations(relationalAnnotationProvider.For(view)); + view.AddAnnotations(relationalAnnotationProvider.For(view, designTime)); } } @@ -210,10 +212,10 @@ public static IRelationalModel Create( { foreach (SqlQueryColumn queryColumn in query.Columns.Values) { - queryColumn.AddAnnotations(relationalAnnotationProvider.For(queryColumn)); + queryColumn.AddAnnotations(relationalAnnotationProvider.For(queryColumn, designTime)); } - query.AddAnnotations(relationalAnnotationProvider.For(query)); + query.AddAnnotations(relationalAnnotationProvider.For(query, designTime)); } } @@ -223,10 +225,10 @@ public static IRelationalModel Create( { foreach (FunctionColumn functionColumn in function.Columns.Values) { - functionColumn.AddAnnotations(relationalAnnotationProvider.For(functionColumn)); + functionColumn.AddAnnotations(relationalAnnotationProvider.For(functionColumn, designTime)); } - function.AddAnnotations(relationalAnnotationProvider.For(function)); + function.AddAnnotations(relationalAnnotationProvider.For(function, designTime)); } } @@ -234,10 +236,10 @@ public static IRelationalModel Create( { foreach (var sequence in ((IRelationalModel)databaseModel).Sequences) { - ((AnnotatableBase)sequence).AddAnnotations(relationalAnnotationProvider.For(sequence)); + ((AnnotatableBase)sequence).AddAnnotations(relationalAnnotationProvider.For(sequence, designTime)); } - databaseModel.AddAnnotations(relationalAnnotationProvider.For(databaseModel)); + databaseModel.AddAnnotations(relationalAnnotationProvider.For(databaseModel, designTime)); } databaseModel._isReadOnly = true; @@ -339,16 +341,6 @@ private static void AddTables(RelationalModel databaseModel, IEntityType entityT databaseModel.Tables.Add((mappedTableName, mappedSchema), table); } - if (mappedType == entityType) - { - Check.DebugAssert( - table.EntityTypeMappings.Count == 0 - || table.IsExcludedFromMigrations == entityType.IsTableExcludedFromMigrations(), - "Table should be excluded on all entity types"); - - table.IsExcludedFromMigrations = entityType.IsTableExcludedFromMigrations(); - } - var tableMapping = new TableMapping(entityType, table, includesDerivedTypes: mappedType == entityType) { IsSplitEntityTypePrincipal = true @@ -963,7 +955,7 @@ private static void PopulateConstraints(Table table) continue; } - tableIndex = new TableIndex(name, table, columns, index.GetFilter(storeObject), index.IsUnique); + tableIndex = new TableIndex(name, table, columns, index.IsUnique); table.Indexes.Add(name, tableIndex); } diff --git a/src/EFCore.Relational/Metadata/Internal/Table.cs b/src/EFCore.Relational/Metadata/Internal/Table.cs index 923b872658b..b9dbc61098c 100644 --- a/src/EFCore.Relational/Metadata/Internal/Table.cs +++ b/src/EFCore.Relational/Metadata/Internal/Table.cs @@ -119,7 +119,7 @@ public virtual UniqueConstraint? PrimaryKey = new(); /// - public virtual bool IsExcludedFromMigrations { get; set; } + public virtual bool IsExcludedFromMigrations => EntityTypeMappings.First().EntityType.IsTableExcludedFromMigrations(); /// public override IColumnBase? FindColumn(IProperty property) diff --git a/src/EFCore.Relational/Metadata/Internal/TableIndex.cs b/src/EFCore.Relational/Metadata/Internal/TableIndex.cs index 838f02fdea3..a4ab3d662c4 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableIndex.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableIndex.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Linq; using Microsoft.EntityFrameworkCore.Infrastructure; namespace Microsoft.EntityFrameworkCore.Metadata.Internal @@ -24,13 +25,11 @@ public TableIndex( string name, Table table, IReadOnlyList columns, - string? filter, bool unique) { Name = name; Table = table; Columns = columns; - Filter = filter; IsUnique = unique; } @@ -73,7 +72,7 @@ public TableIndex( public virtual bool IsUnique { get; } /// - public virtual string? Filter { get; } + public virtual string? Filter => MappedIndexes.First().GetFilter(StoreObjectIdentifier.Table(Table.Name, Table.Schema)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs index afd6caf6478..bb57ed290da 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs @@ -32,59 +32,59 @@ public RelationalAnnotationProvider(RelationalAnnotationProviderDependencies dep } /// - public virtual IEnumerable For(IRelationalModel model) + public virtual IEnumerable For(IRelationalModel model, bool designTime) => Enumerable.Empty(); /// - public virtual IEnumerable For(ITable table) + public virtual IEnumerable For(ITable table, bool designTime) => Enumerable.Empty(); /// - public virtual IEnumerable For(IColumn column) + public virtual IEnumerable For(IColumn column, bool designTime) => Enumerable.Empty(); /// - public virtual IEnumerable For(IView view) + public virtual IEnumerable For(IView view, bool designTime) => Enumerable.Empty(); /// - public virtual IEnumerable For(IViewColumn column) + public virtual IEnumerable For(IViewColumn column, bool designTime) => Enumerable.Empty(); /// - public virtual IEnumerable For(ISqlQuery sqlQuery) + public virtual IEnumerable For(ISqlQuery sqlQuery, bool designTime) => Enumerable.Empty(); /// - public virtual IEnumerable For(ISqlQueryColumn column) + public virtual IEnumerable For(ISqlQueryColumn column, bool designTime) => Enumerable.Empty(); /// - public virtual IEnumerable For(IStoreFunction function) + public virtual IEnumerable For(IStoreFunction function, bool designTime) => Enumerable.Empty(); /// - public virtual IEnumerable For(IFunctionColumn column) + public virtual IEnumerable For(IFunctionColumn column, bool designTime) => Enumerable.Empty(); /// - public virtual IEnumerable For(IForeignKeyConstraint foreignKey) + public virtual IEnumerable For(IForeignKeyConstraint foreignKey, bool designTime) => Enumerable.Empty(); /// - public virtual IEnumerable For(ITableIndex index) + public virtual IEnumerable For(ITableIndex index, bool designTime) => Enumerable.Empty(); /// - public virtual IEnumerable For(IUniqueConstraint constraint) + public virtual IEnumerable For(IUniqueConstraint constraint, bool designTime) => Enumerable.Empty(); /// - public virtual IEnumerable For(ISequence sequence) + public virtual IEnumerable For(ISequence sequence, bool designTime) => Enumerable.Empty(); /// - public virtual IEnumerable For(ICheckConstraint checkConstraint) + public virtual IEnumerable For(ICheckConstraint checkConstraint, bool designTime) => Enumerable.Empty(); } } diff --git a/src/EFCore.Relational/Metadata/RuntimeCheckConstraint.cs b/src/EFCore.Relational/Metadata/RuntimeCheckConstraint.cs index 6a2f5991875..29ef96876e4 100644 --- a/src/EFCore.Relational/Metadata/RuntimeCheckConstraint.cs +++ b/src/EFCore.Relational/Metadata/RuntimeCheckConstraint.cs @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata public class RuntimeCheckConstraint : AnnotatableBase, ICheckConstraint { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The constraint name. /// The affected entity type. diff --git a/src/EFCore.Relational/Metadata/RuntimeDbFunction.cs b/src/EFCore.Relational/Metadata/RuntimeDbFunction.cs index d86b99319c5..bf6430fb7b8 100644 --- a/src/EFCore.Relational/Metadata/RuntimeDbFunction.cs +++ b/src/EFCore.Relational/Metadata/RuntimeDbFunction.cs @@ -33,7 +33,7 @@ public class RuntimeDbFunction : AnnotatableBase, IRuntimeDbFunction private IStoreFunction? _storeFunction; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The model name. /// The model. @@ -50,7 +50,7 @@ public class RuntimeDbFunction : AnnotatableBase, IRuntimeDbFunction /// The function translation. public RuntimeDbFunction( string modelName, - SlimModel model, + RuntimeModel model, Type returnType, string storeName, string? schema = null, @@ -88,6 +88,25 @@ public RuntimeDbFunction( /// public virtual string ModelName { get; } + /// + /// Gets or sets the type mapping for the function's return type. + /// + /// The type mapping. + public virtual RelationalTypeMapping? TypeMapping + { + get => _isScalar + ? NonCapturingLazyInitializer.EnsureInitialized(ref _typeMapping, this, static dbFunction => + { + var relationalTypeMappingSource = + (IRelationalTypeMappingSource)((IModel)dbFunction.Model).GetModelDependencies().TypeMappingSource; + return !string.IsNullOrEmpty(dbFunction._storeType) + ? relationalTypeMappingSource.FindMapping(dbFunction._storeType)! + : relationalTypeMappingSource.FindMapping(dbFunction._returnType)!; + }) + : _typeMapping; + set => _typeMapping = value; + } + /// /// Adds a parameter to the function. /// @@ -104,8 +123,9 @@ public virtual RuntimeDbFunctionParameter AddParameter( string storeType, RelationalTypeMapping? typeMapping = null) { - var runtimeFunctionParameter = new SlimDbFunctionParameter(name, + var runtimeFunctionParameter = new RuntimeDbFunctionParameter( this, + name, clrType, propagatesNullability, storeType, @@ -249,16 +269,7 @@ string IReadOnlyDbFunction.Name RelationalTypeMapping? IReadOnlyDbFunction.TypeMapping { [DebuggerStepThrough] - get => _isScalar - ? NonCapturingLazyInitializer.EnsureInitialized(ref _typeMapping, this, static dbFunction => - { - var relationalTypeMappingSource = - (IRelationalTypeMappingSource)((IModel)dbFunction.Model).GetModelDependencies().TypeMappingSource; - return !string.IsNullOrEmpty(dbFunction._storeType) - ? relationalTypeMappingSource.FindMapping(dbFunction._storeType)! - : relationalTypeMappingSource.FindMapping(dbFunction._returnType)!; - }) - : _typeMapping; + get => TypeMapping; } } } diff --git a/src/EFCore.Relational/Metadata/RuntimeDbFunctionParameter.cs b/src/EFCore.Relational/Metadata/RuntimeDbFunctionParameter.cs index 9f4472f6d1b..3d27e8d5490 100644 --- a/src/EFCore.Relational/Metadata/RuntimeDbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/RuntimeDbFunctionParameter.cs @@ -59,6 +59,22 @@ public virtual string Name /// public virtual RuntimeDbFunction Function { get; } + /// + /// Gets or sets the type mapping for this parameter. + /// + /// The type mapping. + public virtual RelationalTypeMapping? TypeMapping + { + get => NonCapturingLazyInitializer.EnsureInitialized(ref _typeMapping, this, static parameter => + { + var relationalTypeMappingSource = + (IRelationalTypeMappingSource)((IModel)parameter.Function.Model).GetModelDependencies().TypeMappingSource; + return relationalTypeMappingSource.FindMapping(parameter._storeType)!; + }); + + set => _typeMapping = value; + } + /// /// Returns a string that represents the current object. /// @@ -135,11 +151,9 @@ bool IReadOnlyDbFunctionParameter.PropagatesNullability /// RelationalTypeMapping? IReadOnlyDbFunctionParameter.TypeMapping - => NonCapturingLazyInitializer.EnsureInitialized(ref _typeMapping, this, static parameter => - { - var relationalTypeMappingSource = - (IRelationalTypeMappingSource)((IModel)parameter.Function.Model).GetModelDependencies().TypeMappingSource; - return relationalTypeMappingSource.FindMapping(parameter._storeType)!; - }); + { + [DebuggerStepThrough] + get => TypeMapping; + } } } diff --git a/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs index 337695be078..0f114f21621 100644 --- a/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata public class RuntimeRelationalPropertyOverrides : AnnotatableBase, IRelationalPropertyOverrides { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The property for which the overrides are applied. /// Whether the column name is overriden. diff --git a/src/EFCore.Relational/Metadata/RuntimeSequence.cs b/src/EFCore.Relational/Metadata/RuntimeSequence.cs index 7fffc0dddd5..98335fe2d95 100644 --- a/src/EFCore.Relational/Metadata/RuntimeSequence.cs +++ b/src/EFCore.Relational/Metadata/RuntimeSequence.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata { @@ -21,14 +22,14 @@ public class RuntimeSequence : AnnotatableBase, ISequence private readonly bool _isCyclic; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The sequence name. /// The model. /// The type of values generated. + /// The schema. /// The initial value. /// The value increment. - /// The schema. /// Whether the sequence is cyclic. /// The minimum value. /// The maximum value. @@ -36,9 +37,9 @@ public RuntimeSequence( string name, RuntimeModel model, Type type, - long startValue, - int incrementBy, string? schema = null, + long startValue = Sequence.DefaultStartValue, + int incrementBy = Sequence.DefaultIncrementBy, bool cyclic = false, long? minValue = null, long? maxValue = null) diff --git a/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs b/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs index d9e59691c6b..683127ad526 100644 --- a/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs +++ b/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs @@ -53,7 +53,7 @@ private StoreObjectIdentifier(StoreObjectType storeObjectType, string name, stri /// The table name. /// The table schema. /// The table id. - public static StoreObjectIdentifier Table(string name, string? schema) + public static StoreObjectIdentifier Table(string name, string? schema = null) { Check.NotNull(name, nameof(name)); @@ -66,7 +66,7 @@ public static StoreObjectIdentifier Table(string name, string? schema) /// The view name. /// The view schema. /// The view id. - public static StoreObjectIdentifier View(string name, string? schema) + public static StoreObjectIdentifier View(string name, string? schema = null) { Check.NotNull(name, nameof(name)); diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index fb304a72101..a93c36795be 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -52,12 +52,12 @@ public static string ClientGroupByNotSupported => GetString("ClientGroupByNotSupported"); /// - /// The function parameter '{function}({parameter})' has a custom type mapping configured. Compiled model can't be generated, because custom type mappings are not supported. + /// The function parameter '{function}({parameter})' has a custom type mapping configured. Configure it in '{customize}' in a partial '{className}' class instead. /// - public static string CompiledModelFunctionParameterTypeMapping(object? function, object? parameter) + public static string CompiledModelFunctionParameterTypeMapping(object? function, object? parameter, object? customize, object? className) => string.Format( - GetString("CompiledModelFunctionParameterTypeMapping", nameof(function), nameof(parameter)), - function, parameter); + GetString("CompiledModelFunctionParameterTypeMapping", nameof(function), nameof(parameter), nameof(customize), nameof(className)), + function, parameter, customize, className); /// /// The function '{function}' has a custom translation. Compiled model can't be generated, because custom function translations are not supported. @@ -68,12 +68,12 @@ public static string CompiledModelFunctionTranslation(object? function) function); /// - /// The function '{function}' has a custom type mapping configured. Compiled model can't be generated, because custom type mappings are not supported. + /// The function '{function}' has a custom type mapping configured. Configure it in '{customize}' in a partial '{className}' class instead. /// - public static string CompiledModelFunctionTypeMapping(object? function) + public static string CompiledModelFunctionTypeMapping(object? function, object? customize, object? className) => string.Format( - GetString("CompiledModelFunctionTypeMapping", nameof(function)), - function); + GetString("CompiledModelFunctionTypeMapping", nameof(function), nameof(customize), nameof(className)), + function, customize, className); /// /// The computed column SQL has not been specified for the column '{table}.{column}'. Specify the SQL before using Entity Framework to create the database schema. diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 7342eae6844..30275791f6d 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -131,13 +131,13 @@ Unable to translate the given 'GroupBy' pattern. Call 'AsEnumerable' before 'GroupBy' to evaluate it client-side. - The function parameter '{function}({parameter})' has a custom type mapping configured. Compiled model can't be generated, because custom type mappings are not supported. + The function parameter '{function}({parameter})' has a custom type mapping configured. Configure it in '{customize}' in a partial '{className}' class instead. The function '{function}' has a custom translation. Compiled model can't be generated, because custom function translations are not supported. - The function '{function}' has a custom type mapping configured. Compiled model can't be generated, because custom type mappings are not supported. + The function '{function}' has a custom type mapping configured. Configure it in '{customize}' in a partial '{className}' class instead. The computed column SQL has not been specified for the column '{table}.{column}'. Specify the SQL before using Entity Framework to create the database schema. diff --git a/src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs new file mode 100644 index 00000000000..846949da2aa --- /dev/null +++ b/src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs @@ -0,0 +1,93 @@ +// 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 Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Design.Internal +{ + /// + /// 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 class SqlServerCSharpRuntimeAnnotationCodeGenerator : RelationalCSharpRuntimeAnnotationCodeGenerator + { + /// + /// 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 SqlServerCSharpRuntimeAnnotationCodeGenerator( + CSharpRuntimeAnnotationCodeGeneratorDependencies dependencies, + RelationalCSharpRuntimeAnnotationCodeGeneratorDependencies relationalDependencies) + : base(dependencies, relationalDependencies) + { + } + + /// + public override void Generate(IModel model, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + if (!parameters.IsRuntime) + { + var annotations = parameters.Annotations; + annotations.Remove(SqlServerAnnotationNames.IdentityIncrement); + annotations.Remove(SqlServerAnnotationNames.IdentitySeed); + annotations.Remove(SqlServerAnnotationNames.MaxDatabaseSize); + annotations.Remove(SqlServerAnnotationNames.PerformanceLevelSql); + annotations.Remove(SqlServerAnnotationNames.ServiceTierSql); + } + + base.Generate(model, parameters); + } + + /// + public override void Generate(IProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + if (!parameters.IsRuntime) + { + var annotations = parameters.Annotations; + annotations.Remove(SqlServerAnnotationNames.IdentityIncrement); + annotations.Remove(SqlServerAnnotationNames.IdentitySeed); + annotations.Remove(SqlServerAnnotationNames.Sparse); + + if (!annotations.ContainsKey(SqlServerAnnotationNames.ValueGenerationStrategy)) + { + annotations[SqlServerAnnotationNames.ValueGenerationStrategy] = property.GetValueGenerationStrategy(); + } + } + + base.Generate(property, parameters); + } + + /// + public override void Generate(IIndex index, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + if (!parameters.IsRuntime) + { + var annotations = parameters.Annotations; + annotations.Remove(SqlServerAnnotationNames.Clustered); + annotations.Remove(SqlServerAnnotationNames.CreatedOnline); + annotations.Remove(SqlServerAnnotationNames.Include); + annotations.Remove(SqlServerAnnotationNames.FillFactor); + } + + base.Generate(index, parameters); + } + + /// + public override void Generate(IKey key, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + if (!parameters.IsRuntime) + { + var annotations = parameters.Annotations; + annotations.Remove(SqlServerAnnotationNames.Clustered); + } + + base.Generate(key, parameters); + } + } +} diff --git a/src/EFCore.SqlServer/Design/Internal/SqlServerDesignTimeServices.cs b/src/EFCore.SqlServer/Design/Internal/SqlServerDesignTimeServices.cs index 9f8f4205eac..68c53da3471 100644 --- a/src/EFCore.SqlServer/Design/Internal/SqlServerDesignTimeServices.cs +++ b/src/EFCore.SqlServer/Design/Internal/SqlServerDesignTimeServices.cs @@ -32,6 +32,7 @@ public virtual void ConfigureDesignTimeServices(IServiceCollection serviceCollec serviceCollection.AddEntityFrameworkSqlServer(); new EntityFrameworkRelationalDesignServicesBuilder(serviceCollection) .TryAdd() + .TryAdd() .TryAdd() .TryAdd() .TryAddCoreServices(); diff --git a/src/EFCore.SqlServer/Extensions/SqlServerIndexExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerIndexExtensions.cs index 4bf323706d7..72a6e6679be 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerIndexExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerIndexExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; @@ -20,7 +21,9 @@ public static class SqlServerIndexExtensions /// The index. /// if the index is clustered. public static bool? IsClustered(this IReadOnlyIndex index) - => (bool?)index[SqlServerAnnotationNames.Clustered]; + => index is RuntimeIndex + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (bool?)index[SqlServerAnnotationNames.Clustered]; /// /// Returns a value indicating whether the index is clustered. @@ -30,17 +33,17 @@ public static class SqlServerIndexExtensions /// if the index is clustered. public static bool? IsClustered(this IReadOnlyIndex index, in StoreObjectIdentifier storeObject) { + if (index is RuntimeIndex) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + var annotation = index.FindAnnotation(SqlServerAnnotationNames.Clustered); if (annotation != null) { return (bool?)annotation.Value; } - return GetDefaultIsClustered(index, storeObject); - } - - private static bool? GetDefaultIsClustered(IReadOnlyIndex index, in StoreObjectIdentifier storeObject) - { var sharedTableRootIndex = index.FindSharedObjectRootIndex(storeObject); return sharedTableRootIndex?.IsClustered(storeObject); } @@ -51,7 +54,7 @@ public static class SqlServerIndexExtensions /// The value to set. /// The index. public static void SetIsClustered(this IMutableIndex index, bool? value) - => index.SetOrRemoveAnnotation( + => index.SetAnnotation( SqlServerAnnotationNames.Clustered, value); @@ -67,7 +70,7 @@ public static void SetIsClustered(this IMutableIndex index, bool? value) bool? value, bool fromDataAnnotation = false) { - index.SetOrRemoveAnnotation( + index.SetAnnotation( SqlServerAnnotationNames.Clustered, value, fromDataAnnotation); @@ -89,7 +92,32 @@ public static void SetIsClustered(this IMutableIndex index, bool? value) /// The index. /// The included property names, or if they have not been specified. public static IReadOnlyList? GetIncludeProperties(this IReadOnlyIndex index) - => (string[]?)index[SqlServerAnnotationNames.Include]; + => index is RuntimeIndex + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (string[]?)index[SqlServerAnnotationNames.Include]; + + /// + /// Returns included property names, or if they have not been specified. + /// + /// The index. + /// The identifier of the store object. + /// The included property names, or if they have not been specified. + public static IReadOnlyList? GetIncludeProperties(this IReadOnlyIndex index, in StoreObjectIdentifier storeObject) + { + if (index is RuntimeIndex) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + + var annotation = index.FindAnnotation(SqlServerAnnotationNames.Include); + if (annotation != null) + { + return (IReadOnlyList?)annotation.Value; + } + + var sharedTableRootIndex = index.FindSharedObjectRootIndex(storeObject); + return sharedTableRootIndex?.GetIncludeProperties(storeObject); + } /// /// Sets included property names. @@ -97,7 +125,7 @@ public static void SetIsClustered(this IMutableIndex index, bool? value) /// The index. /// The value to set. public static void SetIncludeProperties(this IMutableIndex index, IReadOnlyList properties) - => index.SetOrRemoveAnnotation( + => index.SetAnnotation( SqlServerAnnotationNames.Include, properties); @@ -113,7 +141,7 @@ public static void SetIncludeProperties(this IMutableIndex index, IReadOnlyList< IReadOnlyList? properties, bool fromDataAnnotation = false) { - index.SetOrRemoveAnnotation( + index.SetAnnotation( SqlServerAnnotationNames.Include, properties, fromDataAnnotation); @@ -135,7 +163,32 @@ public static void SetIncludeProperties(this IMutableIndex index, IReadOnlyList< /// The index. /// if the index is online. public static bool? IsCreatedOnline(this IReadOnlyIndex index) - => (bool?)index[SqlServerAnnotationNames.CreatedOnline]; + => index is RuntimeIndex + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (bool?)index[SqlServerAnnotationNames.CreatedOnline]; + + /// + /// Returns a value indicating whether the index is online. + /// + /// The index. + /// The identifier of the store object. + /// if the index is online. + public static bool? IsCreatedOnline(this IReadOnlyIndex index, in StoreObjectIdentifier storeObject) + { + if (index is RuntimeIndex) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + + var annotation = index.FindAnnotation(SqlServerAnnotationNames.CreatedOnline); + if (annotation != null) + { + return (bool?)annotation.Value; + } + + var sharedTableRootIndex = index.FindSharedObjectRootIndex(storeObject); + return sharedTableRootIndex?.IsCreatedOnline(storeObject); + } /// /// Sets a value indicating whether the index is online. @@ -143,7 +196,7 @@ public static void SetIncludeProperties(this IMutableIndex index, IReadOnlyList< /// The index. /// The value to set. public static void SetIsCreatedOnline(this IMutableIndex index, bool? createdOnline) - => index.SetOrRemoveAnnotation( + => index.SetAnnotation( SqlServerAnnotationNames.CreatedOnline, createdOnline); @@ -159,7 +212,7 @@ public static void SetIsCreatedOnline(this IMutableIndex index, bool? createdOnl bool? createdOnline, bool fromDataAnnotation = false) { - index.SetOrRemoveAnnotation( + index.SetAnnotation( SqlServerAnnotationNames.CreatedOnline, createdOnline, fromDataAnnotation); @@ -176,12 +229,37 @@ public static void SetIsCreatedOnline(this IMutableIndex index, bool? createdOnl => index.FindAnnotation(SqlServerAnnotationNames.CreatedOnline)?.GetConfigurationSource(); /// - /// Returns a value indicating whether the index uses the fill factor. + /// Returns the fill factor that the index uses. /// /// The index. - /// if the index is online. + /// The fill factor that the index uses public static int? GetFillFactor(this IReadOnlyIndex index) - => (int?)index[SqlServerAnnotationNames.FillFactor]; + => index is RuntimeIndex + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (int?)index[SqlServerAnnotationNames.FillFactor]; + + /// + /// Returns the fill factor that the index uses. + /// + /// The index. + /// The identifier of the store object. + /// The fill factor that the index uses + public static int? GetFillFactor(this IReadOnlyIndex index, in StoreObjectIdentifier storeObject) + { + if (index is RuntimeIndex) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + + var annotation = index.FindAnnotation(SqlServerAnnotationNames.FillFactor); + if (annotation != null) + { + return (int?)annotation.Value; + } + + var sharedTableRootIndex = index.FindSharedObjectRootIndex(storeObject); + return sharedTableRootIndex?.GetFillFactor(storeObject); + } /// /// Sets a value indicating whether the index uses the fill factor. @@ -195,7 +273,7 @@ public static void SetFillFactor(this IMutableIndex index, int? fillFactor) throw new ArgumentOutOfRangeException(nameof(fillFactor)); } - index.SetOrRemoveAnnotation( + index.SetAnnotation( SqlServerAnnotationNames.FillFactor, fillFactor); } @@ -217,7 +295,7 @@ public static void SetFillFactor(this IMutableIndex index, int? fillFactor) throw new ArgumentOutOfRangeException(nameof(fillFactor)); } - index.SetOrRemoveAnnotation( + index.SetAnnotation( SqlServerAnnotationNames.FillFactor, fillFactor, fromDataAnnotation); diff --git a/src/EFCore.SqlServer/Extensions/SqlServerKeyExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerKeyExtensions.cs index bb1b8c1a90c..6c584198c74 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerKeyExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerKeyExtensions.cs @@ -1,6 +1,8 @@ // 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; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; @@ -18,7 +20,9 @@ public static class SqlServerKeyExtensions /// The key. /// if the key is clustered. public static bool? IsClustered(this IReadOnlyKey key) - => (bool?)key[SqlServerAnnotationNames.Clustered]; + => key is RuntimeKey + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (bool?)key[SqlServerAnnotationNames.Clustered]; /// /// Returns a value indicating whether the key is clustered. @@ -28,6 +32,11 @@ public static class SqlServerKeyExtensions /// if the key is clustered. public static bool? IsClustered(this IReadOnlyKey key, in StoreObjectIdentifier storeObject) { + if (key is RuntimeKey) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + var annotation = key.FindAnnotation(SqlServerAnnotationNames.Clustered); if (annotation != null) { diff --git a/src/EFCore.SqlServer/Extensions/SqlServerModelExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerModelExtensions.cs index 805243ac56f..bf84229df04 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerModelExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerModelExtensions.cs @@ -1,6 +1,8 @@ // 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; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; @@ -120,7 +122,9 @@ public static void SetHiLoSequenceSchema(this IMutableModel model, string? value /// The model. /// The default identity seed. public static int GetIdentitySeed(this IReadOnlyModel model) - => (int?)model[SqlServerAnnotationNames.IdentitySeed] ?? 1; + => model is RuntimeModel + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (int?)model[SqlServerAnnotationNames.IdentitySeed] ?? 1; /// /// Sets the default identity seed. @@ -163,7 +167,9 @@ public static void SetIdentitySeed(this IMutableModel model, int? seed) /// The model. /// The default identity increment. public static int GetIdentityIncrement(this IReadOnlyModel model) - => (int?)model[SqlServerAnnotationNames.IdentityIncrement] ?? 1; + => model is RuntimeModel + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (int?)model[SqlServerAnnotationNames.IdentityIncrement] ?? 1; /// /// Sets the default identity increment. @@ -255,7 +261,9 @@ public static void SetValueGenerationStrategy( /// The model. /// The maximum size of the database. public static string? GetDatabaseMaxSize(this IReadOnlyModel model) - => (string?)model[SqlServerAnnotationNames.MaxDatabaseSize]; + => model is RuntimeModel + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (string?)model[SqlServerAnnotationNames.MaxDatabaseSize]; /// /// Sets the maximum size of the database. @@ -296,7 +304,9 @@ public static void SetDatabaseMaxSize(this IMutableModel model, string? value) /// The model. /// The service tier of the database. public static string? GetServiceTierSql(this IReadOnlyModel model) - => (string?)model[SqlServerAnnotationNames.ServiceTierSql]; + => model is RuntimeModel + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (string?)model[SqlServerAnnotationNames.ServiceTierSql]; /// /// Sets the service tier of the database. @@ -337,7 +347,9 @@ public static void SetServiceTierSql(this IMutableModel model, string? value) /// The model. /// The performance level of the database. public static string? GetPerformanceLevelSql(this IReadOnlyModel model) - => (string?)model[SqlServerAnnotationNames.PerformanceLevelSql]; + => model is RuntimeModel + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (string?)model[SqlServerAnnotationNames.PerformanceLevelSql]; /// /// Sets the performance level of the database. diff --git a/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs index 647ba730459..9ca720f18be 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.SqlServer.Internal; @@ -204,7 +205,9 @@ public static void SetHiLoSequenceSchema(this IMutableProperty property, string? /// The property. /// The identity seed. public static int? GetIdentitySeed(this IReadOnlyProperty property) - => (int?)property[SqlServerAnnotationNames.IdentitySeed]; + => property is RuntimeProperty + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (int?)property[SqlServerAnnotationNames.IdentitySeed]; /// /// Returns the identity seed. @@ -214,6 +217,11 @@ public static void SetHiLoSequenceSchema(this IMutableProperty property, string? /// The identity seed. public static int? GetIdentitySeed(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { + if (property is RuntimeProperty) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + var annotation = property.FindAnnotation(SqlServerAnnotationNames.IdentitySeed); if (annotation != null) { @@ -267,7 +275,9 @@ public static void SetIdentitySeed(this IMutableProperty property, int? seed) /// The property. /// The identity increment. public static int? GetIdentityIncrement(this IReadOnlyProperty property) - => (int?)property[SqlServerAnnotationNames.IdentityIncrement]; + => property is RuntimeProperty + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (int?)property[SqlServerAnnotationNames.IdentityIncrement]; /// /// Returns the identity increment. @@ -277,6 +287,11 @@ public static void SetIdentitySeed(this IMutableProperty property, int? seed) /// The identity increment. public static int? GetIdentityIncrement(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { + if (property is RuntimeProperty) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + var annotation = property.FindAnnotation(SqlServerAnnotationNames.IdentityIncrement); if (annotation != null) { @@ -541,7 +556,34 @@ private static bool IsCompatibleWithValueGeneration( /// The property. /// if the property's column is sparse. public static bool? IsSparse(this IReadOnlyProperty property) - => (bool?)property[SqlServerAnnotationNames.Sparse]; + => property is RuntimeProperty + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (bool?)property[SqlServerAnnotationNames.Sparse]; + + /// + /// Returns a value indicating whether the property's column is sparse. + /// + /// The property. + /// The identifier of the store object. + /// if the property's column is sparse. + public static bool? IsSparse(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) + { + if (property is RuntimeProperty) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + + var annotation = property.FindAnnotation(SqlServerAnnotationNames.Sparse); + if (annotation != null) + { + return (bool?)annotation.Value; + } + + var sharedTableRootProperty = property.FindSharedStoreObjectRootProperty(storeObject); + return sharedTableRootProperty != null + ? sharedTableRootProperty.IsSparse(storeObject) + : null; + } /// /// Sets a value indicating whether the property's column is sparse. @@ -549,7 +591,7 @@ private static bool IsCompatibleWithValueGeneration( /// The property. /// The value to set. public static void SetIsSparse(this IMutableProperty property, bool? sparse) - => property.SetOrRemoveAnnotation(SqlServerAnnotationNames.Sparse, sparse); + => property.SetAnnotation(SqlServerAnnotationNames.Sparse, sparse); /// /// Sets a value indicating whether the property's column is sparse. @@ -563,7 +605,7 @@ public static void SetIsSparse(this IMutableProperty property, bool? sparse) bool? sparse, bool fromDataAnnotation = false) { - property.SetOrRemoveAnnotation( + property.SetAnnotation( SqlServerAnnotationNames.Sparse, sparse, fromDataAnnotation); diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs index 83989029de3..393c4f9fb6c 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs @@ -363,7 +363,7 @@ protected override void ValidateCompatible( } } - if (property.IsSparse() != duplicateProperty.IsSparse()) + if (property.IsSparse(storeObject) != duplicateProperty.IsSparse(storeObject)) { throw new InvalidOperationException( SqlServerStrings.DuplicateColumnSparsenessMismatch( diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs index 7a5e5bad138..3dc8e37e187 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs @@ -101,6 +101,10 @@ public override ConventionSet CreateConventionSet() (SharedTableConvention)new SqlServerSharedTableConvention(Dependencies, RelationalDependencies)); conventionSet.ModelFinalizingConventions.Add(new SqlServerDbFunctionConvention(Dependencies, RelationalDependencies)); + ReplaceConvention( + conventionSet.ModelFinalizedConventions, + (RuntimeModelConvention)new SqlServerRuntimeModelConvention(Dependencies, RelationalDependencies)); + return conventionSet; } diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerRuntimeModelConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerRuntimeModelConvention.cs new file mode 100644 index 00000000000..6fcca5bce4f --- /dev/null +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerRuntimeModelConvention.cs @@ -0,0 +1,121 @@ +// 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.Metadata.Conventions.Infrastructure; +using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; + +#nullable enable + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions +{ + /// + /// A convention that creates an optimized copy of the mutable model. + /// + public class SqlServerRuntimeModelConvention : RelationalRuntimeModelConvention + { + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + /// Parameter object containing relational dependencies for this convention. + public SqlServerRuntimeModelConvention( + ProviderConventionSetBuilderDependencies dependencies, + RelationalConventionSetBuilderDependencies relationalDependencies) + : base(dependencies, relationalDependencies) + { + } + + /// + /// Updates the model annotations that will be set on the read-only object. + /// + /// The annotations to be processed. + /// The source model. + /// The target model that will contain the annotations. + /// Indicates whether the given annotations are runtime annotations. + protected override void ProcessModelAnnotations( + Dictionary annotations, IModel model, RuntimeModel runtimeModel, bool runtime) + { + base.ProcessModelAnnotations(annotations, model, runtimeModel, runtime); + + if (!runtime) + { + annotations.Remove(SqlServerAnnotationNames.IdentityIncrement); + annotations.Remove(SqlServerAnnotationNames.IdentitySeed); + annotations.Remove(SqlServerAnnotationNames.MaxDatabaseSize); + annotations.Remove(SqlServerAnnotationNames.PerformanceLevelSql); + annotations.Remove(SqlServerAnnotationNames.ServiceTierSql); + } + } + + /// + /// Updates the property annotations that will be set on the read-only object. + /// + /// The annotations to be processed. + /// The source property. + /// The target property that will contain the annotations. + /// Indicates whether the given annotations are runtime annotations. + protected override void ProcessPropertyAnnotations( + Dictionary annotations, IProperty property, RuntimeProperty runtimeProperty, bool runtime) + { + base.ProcessPropertyAnnotations(annotations, property, runtimeProperty, runtime); + + if (!runtime) + { + annotations.Remove(SqlServerAnnotationNames.IdentityIncrement); + annotations.Remove(SqlServerAnnotationNames.IdentitySeed); + annotations.Remove(SqlServerAnnotationNames.Sparse); + + if (!annotations.ContainsKey(SqlServerAnnotationNames.ValueGenerationStrategy)) + { + annotations[SqlServerAnnotationNames.ValueGenerationStrategy] = property.GetValueGenerationStrategy(); + } + } + } + + /// + /// Updates the index annotations that will be set on the read-only object. + /// + /// The annotations to be processed. + /// The source index. + /// The target index that will contain the annotations. + /// Indicates whether the given annotations are runtime annotations. + protected override void ProcessIndexAnnotations( + Dictionary annotations, + IIndex index, + RuntimeIndex runtimeIndex, + bool runtime) + { + base.ProcessIndexAnnotations(annotations, index, runtimeIndex, runtime); + + if (!runtime) + { + annotations.Remove(SqlServerAnnotationNames.Clustered); + annotations.Remove(SqlServerAnnotationNames.CreatedOnline); + annotations.Remove(SqlServerAnnotationNames.Include); + annotations.Remove(SqlServerAnnotationNames.FillFactor); + } + } + + /// + /// Updates the key annotations that will be set on the read-only object. + /// + /// The annotations to be processed. + /// The source key. + /// The target key that will contain the annotations. + /// Indicates whether the given annotations are runtime annotations. + protected override void ProcessKeyAnnotations( + IDictionary annotations, + IKey key, + RuntimeKey runtimeKey, + bool runtime) + { + base.ProcessKeyAnnotations(annotations, key, runtimeKey, runtime); + + if (!runtime) + { + annotations.Remove(SqlServerAnnotationNames.Clustered); + } + } + } +} diff --git a/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs b/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs index fd56c73fdc1..f911acafd75 100644 --- a/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs +++ b/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs @@ -41,8 +41,13 @@ public SqlServerAnnotationProvider(RelationalAnnotationProviderDependencies depe /// 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 override IEnumerable For(IRelationalModel model) + public override IEnumerable For(IRelationalModel model, bool designTime) { + if (!designTime) + { + yield break; + } + var maxSize = model.Model.GetDatabaseMaxSize(); var serviceTier = model.Model.GetServiceTierSql(); var performanceLevel = model.Model.GetPerformanceLevelSql(); @@ -90,8 +95,13 @@ public override IEnumerable For(IRelationalModel 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 override IEnumerable For(ITable table) + public override IEnumerable For(ITable table, bool designTime) { + if (!designTime) + { + yield break; + } + // Model validation ensures that these facets are the same on all mapped entity types if (table.EntityTypeMappings.First().EntityType.IsMemoryOptimized()) { @@ -105,8 +115,13 @@ public override IEnumerable For(ITable table) /// 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 override IEnumerable For(IUniqueConstraint constraint) + public override IEnumerable For(IUniqueConstraint constraint, bool designTime) { + if (!designTime) + { + yield break; + } + // Model validation ensures that these facets are the same on all mapped keys var key = constraint.MappedKeys.First(); @@ -124,19 +139,22 @@ public override IEnumerable For(IUniqueConstraint constraint) /// 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 override IEnumerable For(ITableIndex index) + public override IEnumerable For(ITableIndex index, bool designTime) { + if (!designTime) + { + yield break; + } + // Model validation ensures that these facets are the same on all mapped indexes var modelIndex = index.MappedIndexes.First(); - - var table = index.Table; - - if (modelIndex.IsClustered(StoreObjectIdentifier.Table(table.Name, table.Schema)) is bool isClustered) + var table = StoreObjectIdentifier.Table(index.Table.Name, index.Table.Schema); + if (modelIndex.IsClustered(table) is bool isClustered) { yield return new Annotation(SqlServerAnnotationNames.Clustered, isClustered); } - if (modelIndex.GetIncludeProperties() is IReadOnlyList includeProperties) + if (modelIndex.GetIncludeProperties(table) is IReadOnlyList includeProperties) { var includeColumns = includeProperties .Select( @@ -149,12 +167,12 @@ public override IEnumerable For(ITableIndex index) includeColumns); } - if (modelIndex.IsCreatedOnline() is bool isOnline) + if (modelIndex.IsCreatedOnline(table) is bool isOnline) { yield return new Annotation(SqlServerAnnotationNames.CreatedOnline, isOnline); } - if (modelIndex.GetFillFactor() is int fillFactor) + if (modelIndex.GetFillFactor(table) is int fillFactor) { yield return new Annotation(SqlServerAnnotationNames.FillFactor, fillFactor); } @@ -166,12 +184,16 @@ public override IEnumerable For(ITableIndex index) /// 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 override IEnumerable For(IColumn column) + public override IEnumerable For(IColumn column, bool designTime) { + if (!designTime) + { + yield break; + } + var table = StoreObjectIdentifier.Table(column.Table.Name, column.Table.Schema); var identityProperty = column.PropertyMappings.Where( - m => - m.TableMapping.IsSharedTablePrincipal && m.TableMapping.EntityType == m.Property.DeclaringEntityType) + m => m.TableMapping.IsSharedTablePrincipal && m.TableMapping.EntityType == m.Property.DeclaringEntityType) .Select(m => m.Property) .FirstOrDefault( p => p.GetValueGenerationStrategy(table) diff --git a/src/EFCore.Sqlite.Core/Design/Internal/SqliteCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Sqlite.Core/Design/Internal/SqliteCSharpRuntimeAnnotationCodeGenerator.cs new file mode 100644 index 00000000000..8c109349191 --- /dev/null +++ b/src/EFCore.Sqlite.Core/Design/Internal/SqliteCSharpRuntimeAnnotationCodeGenerator.cs @@ -0,0 +1,43 @@ +// 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 Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Sqlite.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Sqlite.Design.Internal +{ + /// + /// 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 class SqliteCSharpRuntimeAnnotationCodeGenerator : RelationalCSharpRuntimeAnnotationCodeGenerator + { + /// + /// 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 SqliteCSharpRuntimeAnnotationCodeGenerator( + CSharpRuntimeAnnotationCodeGeneratorDependencies dependencies, + RelationalCSharpRuntimeAnnotationCodeGeneratorDependencies relationalDependencies) + : base(dependencies, relationalDependencies) + { + } + + /// + public override void Generate(IProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var annotations = parameters.Annotations; + if (!parameters.IsRuntime) + { + annotations.Remove(SqliteAnnotationNames.Srid); + } + + base.Generate(property, parameters); + } + } +} diff --git a/src/EFCore.Sqlite.Core/Design/Internal/SqliteDesignTimeServices.cs b/src/EFCore.Sqlite.Core/Design/Internal/SqliteDesignTimeServices.cs index a0e94395a9f..070de7283b0 100644 --- a/src/EFCore.Sqlite.Core/Design/Internal/SqliteDesignTimeServices.cs +++ b/src/EFCore.Sqlite.Core/Design/Internal/SqliteDesignTimeServices.cs @@ -28,6 +28,7 @@ public virtual void ConfigureDesignTimeServices(IServiceCollection serviceCollec { serviceCollection.AddEntityFrameworkSqlite(); new EntityFrameworkRelationalDesignServicesBuilder(serviceCollection) + .TryAdd() .TryAdd() .TryAdd() .TryAddCoreServices(); diff --git a/src/EFCore.Sqlite.Core/Metadata/Conventions/SqlServerRuntimeModelConvention.cs b/src/EFCore.Sqlite.Core/Metadata/Conventions/SqlServerRuntimeModelConvention.cs new file mode 100644 index 00000000000..f23cc8b2f40 --- /dev/null +++ b/src/EFCore.Sqlite.Core/Metadata/Conventions/SqlServerRuntimeModelConvention.cs @@ -0,0 +1,47 @@ +// 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.Metadata.Conventions.Infrastructure; +using Microsoft.EntityFrameworkCore.Sqlite.Metadata.Internal; + +#nullable enable + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions +{ + /// + /// A convention that creates an optimized copy of the mutable model. + /// + public class SqliteRuntimeModelConvention : RelationalRuntimeModelConvention + { + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + /// Parameter object containing relational dependencies for this convention. + public SqliteRuntimeModelConvention( + ProviderConventionSetBuilderDependencies dependencies, + RelationalConventionSetBuilderDependencies relationalDependencies) + : base(dependencies, relationalDependencies) + { + } + + /// + /// Updates the property annotations that will be set on the read-only object. + /// + /// The annotations to be processed. + /// The source property. + /// The target property that will contain the annotations. + /// Indicates whether the given annotations are runtime annotations. + protected override void ProcessPropertyAnnotations( + Dictionary annotations, IProperty property, RuntimeProperty runtimeProperty, bool runtime) + { + base.ProcessPropertyAnnotations(annotations, property, runtimeProperty, runtime); + + if (!runtime) + { + annotations.Remove(SqliteAnnotationNames.Srid); + } + } + } +} diff --git a/src/EFCore.Sqlite.Core/Metadata/Conventions/SqliteConventionSetBuilder.cs b/src/EFCore.Sqlite.Core/Metadata/Conventions/SqliteConventionSetBuilder.cs index 0f98648374e..b07e1d10ec5 100644 --- a/src/EFCore.Sqlite.Core/Metadata/Conventions/SqliteConventionSetBuilder.cs +++ b/src/EFCore.Sqlite.Core/Metadata/Conventions/SqliteConventionSetBuilder.cs @@ -34,6 +34,21 @@ public SqliteConventionSetBuilder( { } + /// + /// Builds and returns the convention set for the current database provider. + /// + /// The convention set for the current database provider. + public override ConventionSet CreateConventionSet() + { + var conventionSet = base.CreateConventionSet(); + + ReplaceConvention( + conventionSet.ModelFinalizedConventions, + (RuntimeModelConvention)new SqliteRuntimeModelConvention(Dependencies, RelationalDependencies)); + + return conventionSet; + } + /// /// /// Call this method to build a for SQLite when using diff --git a/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs b/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs index 9c23bc0e112..baaf4120d31 100644 --- a/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs +++ b/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs @@ -43,7 +43,7 @@ public SqliteAnnotationProvider(RelationalAnnotationProviderDependencies depende /// 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 override IEnumerable For(IRelationalModel model) + public override IEnumerable For(IRelationalModel model, bool designTime) { if (model.Tables.SelectMany(t => t.Columns).Any( c => SqliteTypeMappingSource.IsSpatialiteType(c.StoreType))) @@ -58,7 +58,7 @@ public override IEnumerable For(IRelationalModel 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 override IEnumerable For(IColumn column) + public override IEnumerable For(IColumn column, bool designTime) { // Model validation ensures that these facets are the same on all mapped properties var property = column.PropertyMappings.First().Property; diff --git a/src/EFCore/Design/CSharpSlimAnnotationCodeGenerator.cs b/src/EFCore/Design/CSharpRuntimeAnnotationCodeGenerator.cs similarity index 73% rename from src/EFCore/Design/CSharpSlimAnnotationCodeGenerator.cs rename to src/EFCore/Design/CSharpRuntimeAnnotationCodeGenerator.cs index f5767a35ba8..8edc6ce0eb7 100644 --- a/src/EFCore/Design/CSharpSlimAnnotationCodeGenerator.cs +++ b/src/EFCore/Design/CSharpRuntimeAnnotationCodeGenerator.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; @@ -11,16 +12,16 @@ namespace Microsoft.EntityFrameworkCore.Design { /// /// - /// Base class to be used by database providers when implementing an + /// Base class to be used by database providers when implementing an /// /// - public class CSharpSlimAnnotationCodeGenerator : ICSharpSlimAnnotationCodeGenerator + public class CSharpRuntimeAnnotationCodeGenerator : ICSharpRuntimeAnnotationCodeGenerator { /// /// Initializes a new instance of this class. /// /// Parameter object containing dependencies for this service. - public CSharpSlimAnnotationCodeGenerator(CSharpSlimAnnotationCodeGeneratorDependencies dependencies) + public CSharpRuntimeAnnotationCodeGenerator(CSharpRuntimeAnnotationCodeGeneratorDependencies dependencies) { Check.NotNull(dependencies, nameof(dependencies)); @@ -30,10 +31,10 @@ public CSharpSlimAnnotationCodeGenerator(CSharpSlimAnnotationCodeGeneratorDepend /// /// Parameter object containing dependencies for this service. /// - protected virtual CSharpSlimAnnotationCodeGeneratorDependencies Dependencies { get; } + protected virtual CSharpRuntimeAnnotationCodeGeneratorDependencies Dependencies { get; } /// - public virtual void Generate(IModel model, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public virtual void Generate(IModel model, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { if (parameters.IsRuntime) { @@ -50,7 +51,7 @@ public virtual void Generate(IModel model, CSharpSlimAnnotationCodeGeneratorPara } /// - public virtual void Generate(IEntityType entityType, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public virtual void Generate(IEntityType entityType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var annotations = parameters.Annotations; if (!parameters.IsRuntime) @@ -64,7 +65,7 @@ public virtual void Generate(IEntityType entityType, CSharpSlimAnnotationCodeGen } /// - public virtual void Generate(IProperty property, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public virtual void Generate(IProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { if (!parameters.IsRuntime) { @@ -77,15 +78,19 @@ public virtual void Generate(IProperty property, CSharpSlimAnnotationCodeGenerat annotations.Remove(CoreAnnotationNames.Precision); annotations.Remove(CoreAnnotationNames.Scale); annotations.Remove(CoreAnnotationNames.ProviderClrType); + annotations.Remove(CoreAnnotationNames.ValueGeneratorFactory); + annotations.Remove(CoreAnnotationNames.ValueGeneratorFactoryType); annotations.Remove(CoreAnnotationNames.ValueConverter); + annotations.Remove(CoreAnnotationNames.ValueConverterType); annotations.Remove(CoreAnnotationNames.ValueComparer); + annotations.Remove(CoreAnnotationNames.ValueComparerType); } GenerateSimpleAnnotations(parameters); } /// - public virtual void Generate(IServiceProperty property, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public virtual void Generate(IServiceProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { if (!parameters.IsRuntime) { @@ -97,19 +102,19 @@ public virtual void Generate(IServiceProperty property, CSharpSlimAnnotationCode } /// - public virtual void Generate(IKey key, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public virtual void Generate(IKey key, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { GenerateSimpleAnnotations(parameters); } /// - public virtual void Generate(IForeignKey foreignKey, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public virtual void Generate(IForeignKey foreignKey, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { GenerateSimpleAnnotations(parameters); } /// - public virtual void Generate(INavigation navigation, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public virtual void Generate(INavigation navigation, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { if (!parameters.IsRuntime) { @@ -122,7 +127,7 @@ public virtual void Generate(INavigation navigation, CSharpSlimAnnotationCodeGen } /// - public virtual void Generate(ISkipNavigation navigation, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public virtual void Generate(ISkipNavigation navigation, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { if (!parameters.IsRuntime) { @@ -135,18 +140,22 @@ public virtual void Generate(ISkipNavigation navigation, CSharpSlimAnnotationCod } /// - public virtual void Generate(IIndex index, CSharpSlimAnnotationCodeGeneratorParameters parameters) + public virtual void Generate(IIndex index, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { GenerateSimpleAnnotations(parameters); } - private void GenerateSimpleAnnotations(CSharpSlimAnnotationCodeGeneratorParameters parameters) + /// + /// Generates code to create the given annotations using literals. + /// + /// Parameters used during code generation. + protected virtual void GenerateSimpleAnnotations(CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { - foreach (var (name, value) in parameters.Annotations) + foreach (var (name, value) in parameters.Annotations.OrderBy(a => a.Key)) { if (value != null) { - AddNamespace(value.GetType(), parameters.Namespaces); + AddNamespace(value as Type ?? value.GetType(), parameters.Namespaces); } GenerateSimpleAnnotation(name, Dependencies.CSharpHelper.UnknownLiteral(value), parameters); @@ -157,16 +166,22 @@ private void GenerateSimpleAnnotations(CSharpSlimAnnotationCodeGeneratorParamete /// Generates code to create the given annotation. /// /// The annotation name. - /// The annotation value. + /// The annotation value as a literal. /// Additional parameters used during code generation. protected virtual void GenerateSimpleAnnotation( string annotationName, string valueString, - CSharpSlimAnnotationCodeGeneratorParameters parameters) + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { + if (parameters.TargetName != "this") + { + parameters.MainBuilder + .Append(parameters.TargetName) + .Append('.'); + } + parameters.MainBuilder - .Append(parameters.TargetName) - .Append(parameters.IsRuntime ? ".AddRuntimeAnnotation(" : ".AddAnnotation(") + .Append(parameters.IsRuntime ? "AddRuntimeAnnotation(" : "AddAnnotation(") .Append(Dependencies.CSharpHelper.Literal(annotationName)) .Append(", ") .Append(valueString) diff --git a/src/EFCore/Design/CSharpSlimAnnotationCodeGeneratorDependencies.cs b/src/EFCore/Design/CSharpRuntimeAnnotationCodeGeneratorDependencies.cs similarity index 91% rename from src/EFCore/Design/CSharpSlimAnnotationCodeGeneratorDependencies.cs rename to src/EFCore/Design/CSharpRuntimeAnnotationCodeGeneratorDependencies.cs index c3c0b525ac7..bf49b38cb23 100644 --- a/src/EFCore/Design/CSharpSlimAnnotationCodeGeneratorDependencies.cs +++ b/src/EFCore/Design/CSharpRuntimeAnnotationCodeGeneratorDependencies.cs @@ -8,7 +8,7 @@ namespace Microsoft.EntityFrameworkCore.Design { /// /// - /// Service dependencies parameter class for + /// Service dependencies parameter class for /// /// /// This type is typically used by database providers (and other extensions). It is generally @@ -23,11 +23,11 @@ namespace Microsoft.EntityFrameworkCore.Design /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// - public sealed record CSharpSlimAnnotationCodeGeneratorDependencies + public sealed record CSharpRuntimeAnnotationCodeGeneratorDependencies { /// /// - /// Creates the service dependencies parameter object for a . + /// Creates the service dependencies parameter object for a . /// /// /// Do not call this constructor directly from either provider or application code as it may change @@ -45,7 +45,7 @@ public sealed record CSharpSlimAnnotationCodeGeneratorDependencies /// /// [EntityFrameworkInternal] - public CSharpSlimAnnotationCodeGeneratorDependencies(ICSharpHelper cSharpHelper) + public CSharpRuntimeAnnotationCodeGeneratorDependencies(ICSharpHelper cSharpHelper) { Check.NotNull(cSharpHelper, nameof(cSharpHelper)); diff --git a/src/EFCore/Design/CSharpSlimAnnotationCodeGeneratorParameters.cs b/src/EFCore/Design/CSharpRuntimeAnnotationCodeGeneratorParameters.cs similarity index 86% rename from src/EFCore/Design/CSharpSlimAnnotationCodeGeneratorParameters.cs rename to src/EFCore/Design/CSharpRuntimeAnnotationCodeGeneratorParameters.cs index 644f72cdaf7..ef8acd9a75f 100644 --- a/src/EFCore/Design/CSharpSlimAnnotationCodeGeneratorParameters.cs +++ b/src/EFCore/Design/CSharpRuntimeAnnotationCodeGeneratorParameters.cs @@ -7,13 +7,13 @@ namespace Microsoft.EntityFrameworkCore.Design { /// - /// The parameter object for a + /// The parameter object for a /// - public sealed record CSharpSlimAnnotationCodeGeneratorParameters + public sealed record CSharpRuntimeAnnotationCodeGeneratorParameters { /// /// - /// Creates the parameter object for a . + /// Creates the parameter object for a . /// /// /// Do not call this constructor directly from either provider or application code as it may change @@ -27,14 +27,16 @@ public sealed record CSharpSlimAnnotationCodeGeneratorParameters /// /// [EntityFrameworkInternal] - public CSharpSlimAnnotationCodeGeneratorParameters( + public CSharpRuntimeAnnotationCodeGeneratorParameters( string targetName, + string className, IndentedStringBuilder mainBuilder, IndentedStringBuilder methodBuilder, ISet namespaces, ISet scopeVariables) { TargetName = targetName; + ClassName = className; MainBuilder = mainBuilder; MethodBuilder = methodBuilder; Namespaces = namespaces; @@ -51,6 +53,11 @@ public CSharpSlimAnnotationCodeGeneratorParameters( /// public string TargetName { get; init; } + /// + /// The name of the current class. + /// + public string ClassName { get; init; } + /// /// The builder for the code building the metadata item. /// diff --git a/src/EFCore/Design/EntityFrameworkDesignServicesBuilder.cs b/src/EFCore/Design/EntityFrameworkDesignServicesBuilder.cs index 7fb73a9da7b..bad7cbdc3d9 100644 --- a/src/EFCore/Design/EntityFrameworkDesignServicesBuilder.cs +++ b/src/EFCore/Design/EntityFrameworkDesignServicesBuilder.cs @@ -44,7 +44,7 @@ public static readonly IDictionary Services = new Dictionary{ { typeof(IDbContextLogger), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IDiagnosticsLogger<>), new ServiceCharacteristics(ServiceLifetime.Singleton) }, - { typeof(ICSharpSlimAnnotationCodeGenerator), new ServiceCharacteristics(ServiceLifetime.Singleton) } + { typeof(ICSharpRuntimeAnnotationCodeGenerator), new ServiceCharacteristics(ServiceLifetime.Singleton) } }; /// @@ -79,10 +79,10 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(typeof(IDiagnosticsLogger<>), typeof(DiagnosticsLogger<>)); TryAdd(); - TryAdd(); + TryAdd(); ServiceCollectionMap.GetInfrastructure() - .AddDependencySingleton(); + .AddDependencySingleton(); return this; } diff --git a/src/EFCore/Design/ICSharpHelper.cs b/src/EFCore/Design/ICSharpHelper.cs index 2c3b5d70164..883c31b3100 100644 --- a/src/EFCore/Design/ICSharpHelper.cs +++ b/src/EFCore/Design/ICSharpHelper.cs @@ -199,7 +199,7 @@ string Literal(T? value) string Literal(ushort value); /// - /// Generates a literal. + /// Generates a literal. /// /// The value. /// The literal. diff --git a/src/EFCore/Design/ICSharpSlimAnnotationCodeGenerator.cs b/src/EFCore/Design/ICSharpRuntimeAnnotationCodeGenerator.cs similarity index 77% rename from src/EFCore/Design/ICSharpSlimAnnotationCodeGenerator.cs rename to src/EFCore/Design/ICSharpRuntimeAnnotationCodeGenerator.cs index ac80a922785..b4ee17db354 100644 --- a/src/EFCore/Design/ICSharpSlimAnnotationCodeGenerator.cs +++ b/src/EFCore/Design/ICSharpRuntimeAnnotationCodeGenerator.cs @@ -8,69 +8,69 @@ namespace Microsoft.EntityFrameworkCore.Design /// /// Implemented by database providers to generate the code for annotations. /// - public interface ICSharpSlimAnnotationCodeGenerator + public interface ICSharpRuntimeAnnotationCodeGenerator { /// /// Generates code to create the given annotations. /// /// The model to which the annotations are applied. /// Additional parameters used during code generation. - void Generate(IModel model, CSharpSlimAnnotationCodeGeneratorParameters parameters); + void Generate(IModel model, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); /// /// Generates code to create the given annotations. /// /// The entity type to which the annotations are applied. /// Additional parameters used during code generation. - void Generate(IEntityType entityType, CSharpSlimAnnotationCodeGeneratorParameters parameters); + void Generate(IEntityType entityType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); /// /// Generates code to create the given annotations. /// /// The property to which the annotations are applied. /// Additional parameters used during code generation. - void Generate(IProperty property, CSharpSlimAnnotationCodeGeneratorParameters parameters); + void Generate(IProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); /// /// Generates code to create the given annotations. /// /// The property to which the annotations are applied. /// Additional parameters used during code generation. - void Generate(IServiceProperty property, CSharpSlimAnnotationCodeGeneratorParameters parameters); + void Generate(IServiceProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); /// /// Generates code to create the given annotations. /// /// The key to which the annotations are applied. /// Additional parameters used during code generation. - void Generate(IKey key, CSharpSlimAnnotationCodeGeneratorParameters parameters); + void Generate(IKey key, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); /// /// Generates code to create the given annotations. /// /// The foreign key to which the annotations are applied. /// Additional parameters used during code generation. - void Generate(IForeignKey foreignKey, CSharpSlimAnnotationCodeGeneratorParameters parameters); + void Generate(IForeignKey foreignKey, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); /// /// Generates code to create the given annotations. /// /// The navigation to which the annotations are applied. /// Additional parameters used during code generation. - void Generate(INavigation navigation, CSharpSlimAnnotationCodeGeneratorParameters parameters); + void Generate(INavigation navigation, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); /// /// Generates code to create the given annotations. /// /// The skip navigation to which the annotations are applied. /// Additional parameters used during code generation. - void Generate(ISkipNavigation navigation, CSharpSlimAnnotationCodeGeneratorParameters parameters); + void Generate(ISkipNavigation navigation, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); /// /// Generates code to create the given annotations. /// /// The index to which the annotations are applied. /// Additional parameters used during code generation. - void Generate(IIndex index, CSharpSlimAnnotationCodeGeneratorParameters parameters); + void Generate(IIndex index, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); } } diff --git a/src/EFCore/Infrastructure/IndentedStringBuilder.cs b/src/EFCore/Infrastructure/IndentedStringBuilder.cs index 2a8a7c9cd4b..0590fe97df8 100644 --- a/src/EFCore/Infrastructure/IndentedStringBuilder.cs +++ b/src/EFCore/Infrastructure/IndentedStringBuilder.cs @@ -45,6 +45,20 @@ public virtual IndentedStringBuilder Append(string value) return this; } + /// + /// Appends the current indent and then the given char to the string being built. + /// + /// The char to append. + /// This builder so that additional calls can be chained. + public virtual IndentedStringBuilder Append(char value) + { + DoIndent(); + + _stringBuilder.Append(value); + + return this; + } + /// /// Appends the current indent and then the given strings to the string being built. /// @@ -62,6 +76,23 @@ public virtual IndentedStringBuilder Append(IEnumerable value) return this; } + /// + /// Appends the current indent and then the given chars to the string being built. + /// + /// The chars to append. + /// This builder so that additional calls can be chained. + public virtual IndentedStringBuilder Append(IEnumerable value) + { + DoIndent(); + + foreach (var chr in value) + { + _stringBuilder.Append(chr); + } + + return this; + } + /// /// Appends a new line to the string being built. /// diff --git a/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs b/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs index 9dd5ab8a30f..52a20f7153e 100644 --- a/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs +++ b/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs @@ -63,7 +63,7 @@ public virtual IModel Initialize( model.ModelDependencies = initializer.Dependencies.ModelDependencies; - initializer.InitializeModel(model, preValidation: true); + initializer.InitializeModel(model, designTime, preValidation: true); if (validationLogger != null && model is IConventionModel) @@ -71,7 +71,7 @@ public virtual IModel Initialize( initializer.Dependencies.ModelValidator.Validate(model, validationLogger); } - initializer.InitializeModel(model, preValidation: false); + initializer.InitializeModel(model, designTime, preValidation: false); if (!designTime && model is Model mutableModel) @@ -111,11 +111,12 @@ public virtual IModel Initialize( /// Initializes the given model with runtime dependencies. /// /// The model to initialize. + /// Whether the model should contain design-time configuration. /// /// indicates that only pre-validation initialization should be performed; /// indicates that only post-validation initialization should be performed. /// - protected virtual void InitializeModel(IModel model, bool preValidation) + protected virtual void InitializeModel(IModel model, bool designTime, bool preValidation) { } } diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs index 7419690123b..47c4676a28b 100644 --- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs +++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs @@ -52,7 +52,7 @@ protected virtual RuntimeModel Create(IModel model) { var runtimeModel = new RuntimeModel(); runtimeModel.SetSkipDetectChanges(((IRuntimeModel)model).SkipDetectChanges); - ((IModel)slimModel).ModelDependencies = model.ModelDependencies!; + ((IModel)runtimeModel).ModelDependencies = model.ModelDependencies!; var entityTypes = model.GetEntityTypesInHierarchicalOrder(); var entityTypePairs = new List<(IEntityType Source, RuntimeEntityType Target)>(entityTypes.Count); @@ -405,7 +405,7 @@ private RuntimeNavigation Create(INavigation navigation, RuntimeForeignKey runti => (navigation.IsOnDependent ? runtimeForeigKey.DeclaringEntityType : runtimeForeigKey.PrincipalEntityType) .AddNavigation( navigation.Name, - slimForeigKey, + runtimeForeigKey, navigation.IsOnDependent, navigation.ClrType, navigation.PropertyInfo, diff --git a/src/EFCore/Metadata/IReadOnlyEntityType.cs b/src/EFCore/Metadata/IReadOnlyEntityType.cs index 30fffdde020..4602ac8ce39 100644 --- a/src/EFCore/Metadata/IReadOnlyEntityType.cs +++ b/src/EFCore/Metadata/IReadOnlyEntityType.cs @@ -830,7 +830,7 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt builder.Append(" Keyless"); } - if (this is Model + if (this is EntityType && GetChangeTrackingStrategy() != ChangeTrackingStrategy.Snapshot) { builder.Append(" ChangeTrackingStrategy.").Append(GetChangeTrackingStrategy()); diff --git a/src/EFCore/Metadata/IReadOnlyTypeBase.cs b/src/EFCore/Metadata/IReadOnlyTypeBase.cs index bb6b0aa70b5..b37bf01ae88 100644 --- a/src/EFCore/Metadata/IReadOnlyTypeBase.cs +++ b/src/EFCore/Metadata/IReadOnlyTypeBase.cs @@ -37,7 +37,8 @@ public interface IReadOnlyTypeBase : IReadOnlyAnnotatable Type ClrType { get; } /// - /// Gets a value indicating whether this entity type can share its with other entities. + /// Gets a value indicating whether this entity type is mapped to a that + /// other entity types are also mapped to. /// bool HasSharedClrType { get; } @@ -137,9 +138,7 @@ string ShortName() /// /// /// The access mode being used. - PropertyAccessMode GetPropertyAccessMode() - => (PropertyAccessMode?)this[CoreAnnotationNames.PropertyAccessMode] - ?? Model.GetPropertyAccessMode(); + PropertyAccessMode GetPropertyAccessMode(); /// /// @@ -151,14 +150,12 @@ PropertyAccessMode GetPropertyAccessMode() /// /// /// The access mode being used. - PropertyAccessMode GetNavigationAccessMode() - => (PropertyAccessMode?)this[CoreAnnotationNames.NavigationAccessMode] - ?? GetPropertyAccessMode(); + PropertyAccessMode GetNavigationAccessMode(); /// - /// Returns the for the indexer on the associated CLR type if one exists. + /// Returns the for the indexer on the associated CLR type if one exists. /// - /// The for the indexer on the associated CLR type if one exists. + /// The for the indexer on the associated CLR type if one exists. PropertyInfo? FindIndexerPropertyInfo(); } } diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index 6fcf88bb270..ade139c5dec 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -5256,11 +5256,33 @@ public virtual void Attach(InternalEntityTypeBuilder entityTypeBuilder) entityTypeBuilder.Ignore(ignoredMember, EntityType.FindDeclaredIgnoredConfigurationSource(ignoredMember)!.Value); } + if (EntityType._baseTypeConfigurationSource != null) + { + var baseType = EntityType._baseType; + if (baseType?.IsInModel == false) + { + baseType = EntityType.Model.FindActualEntityType(baseType); + } + + entityTypeBuilder.Metadata.SetBaseType(baseType, EntityType._baseTypeConfigurationSource.Value); + } + + if (EntityType._isKeylessConfigurationSource != null) + { + entityTypeBuilder.Metadata.SetIsKeyless(EntityType.IsKeyless, EntityType._isKeylessConfigurationSource.Value); + } + + if (EntityType._changeTrackingStrategyConfigurationSource != null) + { + entityTypeBuilder.Metadata.SetChangeTrackingStrategy( + EntityType.GetChangeTrackingStrategy(), EntityType._changeTrackingStrategyConfigurationSource.Value); + } + if (ServiceProperties != null) { foreach (var detachedServiceProperty in ServiceProperties) { - detachedServiceProperty.Attach(detachedServiceProperty.Metadata.DeclaringEntityType.Builder); + detachedServiceProperty.Attach(entityTypeBuilder); } } @@ -5274,12 +5296,36 @@ public virtual void Attach(InternalEntityTypeBuilder entityTypeBuilder) } } + if (EntityType._constructorBindingConfigurationSource != null) + { + entityTypeBuilder.Metadata.SetConstructorBinding( + Create(EntityType.ConstructorBinding, entityTypeBuilder.Metadata), + EntityType._constructorBindingConfigurationSource.Value); + } + + if (EntityType._serviceOnlyConstructorBindingConfigurationSource != null) + { + entityTypeBuilder.Metadata.SetServiceOnlyConstructorBinding( + Create(EntityType.ServiceOnlyConstructorBinding, entityTypeBuilder.Metadata), + EntityType._serviceOnlyConstructorBindingConfigurationSource.Value); + } + var rawData = EntityType._data; if (rawData != null) { entityTypeBuilder.Metadata.AddData(rawData); } } + + private InstantiationBinding? Create(InstantiationBinding? instantiationBinding, EntityType entityType) + => instantiationBinding?.With(instantiationBinding.ParameterBindings.Select(binding => Create(binding, entityType)).ToList()); + + private ParameterBinding Create(ParameterBinding parameterBinding, EntityType entityType) + => parameterBinding.With(parameterBinding.ConsumedProperties.Select(property => + (entityType.FindProperty(property.Name) + ?? entityType.FindServiceProperty(property.Name) + ?? entityType.FindNavigation(property.Name) + ?? (IPropertyBase?)entityType.FindSkipNavigation(property.Name))!).ToArray()); } /// diff --git a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs index 282e58bd6df..1f8856b9aa4 100644 --- a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs @@ -2240,7 +2240,7 @@ private bool CanSetPrincipalKey( isOwnership ??= !oldRelationshipInverted && (Metadata.GetIsOwnershipConfigurationSource()?.Overrides(configurationSource) ?? false) ? Metadata.IsOwnership - : (bool?)null; + : null; deleteBehavior ??= ((Metadata.GetDeleteBehaviorConfigurationSource()?.Overrides(configurationSource) ?? false) ? Metadata.DeleteBehavior diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs index acba284ccbe..83476c710cf 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs @@ -767,6 +767,13 @@ public virtual bool CanSetKeyValueComparer(ValueComparer? comparer, Configuratio newPropertyBuilder.HasField(Metadata.FieldInfo, oldFieldInfoConfigurationSource.Value); } + var oldTypeMappingConfigurationSource = Metadata.GetTypeMappingConfigurationSource(); + if (oldTypeMappingConfigurationSource.HasValue + && newPropertyBuilder.CanSetTypeMapping(Metadata.TypeMapping, oldTypeMappingConfigurationSource)) + { + newPropertyBuilder.HasTypeMapping(Metadata.TypeMapping, oldTypeMappingConfigurationSource.Value); + } + return newPropertyBuilder; } diff --git a/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs index c2cebe67146..352b6b8bf36 100644 --- a/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs @@ -117,6 +117,13 @@ public virtual bool CanSetParameterBinding( newPropertyBuilder.HasField(Metadata.FieldInfo, oldFieldInfoConfigurationSource.Value); } + var propertyAccessModeConfigurationSource = Metadata.GetPropertyAccessModeConfigurationSource(); + if (propertyAccessModeConfigurationSource.HasValue) + { + newPropertyBuilder.UsePropertyAccessMode( + Metadata.GetPropertyAccessMode(), propertyAccessModeConfigurationSource.Value); + } + return newPropertyBuilder; } diff --git a/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs b/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs index 0e1f4a62ffc..56b98b9e0f4 100644 --- a/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs @@ -295,7 +295,7 @@ public virtual bool CanSetInverse( if (propertyAccessModeConfigurationSource.HasValue) { newSkipNavigationBuilder.UsePropertyAccessMode( - ((IReadOnlySkipNavigation)Metadata).GetPropertyAccessMode(), propertyAccessModeConfigurationSource.Value); + Metadata.GetPropertyAccessMode(), propertyAccessModeConfigurationSource.Value); } var oldFieldInfoConfigurationSource = Metadata.GetFieldInfoConfigurationSource(); diff --git a/src/EFCore/Metadata/Internal/Model.cs b/src/EFCore/Metadata/Internal/Model.cs index 837c51db4d2..412f3342f5c 100644 --- a/src/EFCore/Metadata/Internal/Model.cs +++ b/src/EFCore/Metadata/Internal/Model.cs @@ -736,7 +736,7 @@ public virtual void AddShared(Type type, ConfigurationSource configurationSource /// public virtual PropertyAccessMode GetPropertyAccessMode() => (PropertyAccessMode?)this[CoreAnnotationNames.PropertyAccessMode] - ?? PropertyAccessMode.PreferField; + ?? DefaultPropertyAccessMode; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/TypeBase.cs b/src/EFCore/Metadata/Internal/TypeBase.cs index 9986b0d1437..641b17930b8 100644 --- a/src/EFCore/Metadata/Internal/TypeBase.cs +++ b/src/EFCore/Metadata/Internal/TypeBase.cs @@ -206,6 +206,16 @@ public virtual IReadOnlyDictionary GetRuntimeFields() return _indexerPropertyInfo; } + /// + /// 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 PropertyAccessMode GetPropertyAccessMode() + => (PropertyAccessMode?)this[CoreAnnotationNames.PropertyAccessMode] + ?? Model.GetPropertyAccessMode(); + /// /// 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 @@ -218,6 +228,16 @@ public virtual IReadOnlyDictionary GetRuntimeFields() => (PropertyAccessMode?)SetOrRemoveAnnotation( CoreAnnotationNames.PropertyAccessMode, propertyAccessMode, configurationSource)?.Value; + /// + /// 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 PropertyAccessMode GetNavigationAccessMode() + => (PropertyAccessMode?)this[CoreAnnotationNames.NavigationAccessMode] + ?? GetPropertyAccessMode(); + /// /// 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 diff --git a/src/EFCore/Metadata/RuntimeEntityType.cs b/src/EFCore/Metadata/RuntimeEntityType.cs index 06c1641d128..f0b2f4d12bb 100644 --- a/src/EFCore/Metadata/RuntimeEntityType.cs +++ b/src/EFCore/Metadata/RuntimeEntityType.cs @@ -1363,6 +1363,12 @@ IEnumerable IEntityType.GetServiceProperties() IEnumerable> IReadOnlyEntityType.GetSeedData(bool providerValues) => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + PropertyAccessMode IReadOnlyTypeBase.GetPropertyAccessMode() + => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + + PropertyAccessMode IReadOnlyTypeBase.GetNavigationAccessMode() + => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + ConfigurationSource? IRuntimeEntityType.GetConstructorBindingConfigurationSource() => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); diff --git a/src/EFCore/Metadata/RuntimeModel.cs b/src/EFCore/Metadata/RuntimeModel.cs index 6212f250bc3..c3d35a6b342 100644 --- a/src/EFCore/Metadata/RuntimeModel.cs +++ b/src/EFCore/Metadata/RuntimeModel.cs @@ -35,7 +35,7 @@ public class RuntimeModel : AnnotatableBase, IRuntimeModel private bool _skipDetectChanges; /// - /// Creates a new instance of + /// Creates a new instance of /// public RuntimeModel() { @@ -65,7 +65,7 @@ public virtual void SetSkipDetectChanges(bool skipDetectChanges) /// The base type of this entity type. /// The name of the property that will be used for storing a discriminator value. /// The change tracking strategy for this entity type - /// The for the indexer on the associated CLR type if one exists. + /// The for the indexer on the associated CLR type if one exists. /// /// A value indicating whether this entity type has an indexer which is able to contain arbitrary properties /// and a method that can be used to determine whether a given indexer property contains a value. diff --git a/src/EFCore/Metadata/RuntimeProperty.cs b/src/EFCore/Metadata/RuntimeProperty.cs index f3b13c331de..d6a0594f597 100644 --- a/src/EFCore/Metadata/RuntimeProperty.cs +++ b/src/EFCore/Metadata/RuntimeProperty.cs @@ -96,7 +96,7 @@ public RuntimeProperty( _typeMapping = typeMapping; _valueComparer = valueComparer; - _keyValueComparer = keyValueComparer; + _keyValueComparer = keyValueComparer ?? valueComparer; } /// @@ -154,11 +154,18 @@ private IEnumerable GetContainingForeignKeys() private IEnumerable GetContainingIndexes() => Indexes ?? Enumerable.Empty(); - private CoreTypeMapping TypeMapping - => NonCapturingLazyInitializer.EnsureInitialized( - ref _typeMapping, (IProperty)this, + /// + /// Gets or sets the type mapping for this property. + /// + /// The type mapping. + public virtual CoreTypeMapping TypeMapping + { + get => NonCapturingLazyInitializer.EnsureInitialized( + ref _typeMapping, (IProperty)this, static property => property.DeclaringEntityType.Model.GetModelDependencies().TypeMappingSource.FindMapping(property)!); + set => _typeMapping = value; + } /// /// Returns a string that represents the current object. @@ -260,6 +267,7 @@ IEntityType IProperty.DeclaringEntityType => TypeMapping; /// + [DebuggerStepThrough] ValueComparer? IReadOnlyProperty.GetValueComparer() => _valueComparer ?? TypeMapping.Comparer; @@ -269,6 +277,7 @@ ValueComparer IProperty.GetValueComparer() => _valueComparer ?? TypeMapping.Comparer; /// + [DebuggerStepThrough] ValueComparer? IReadOnlyProperty.GetKeyValueComparer() => _keyValueComparer ?? TypeMapping.KeyComparer; diff --git a/src/EFCore/Metadata/RuntimeServiceProperty.cs b/src/EFCore/Metadata/RuntimeServiceProperty.cs index 4bfb62205b5..a24398a239a 100644 --- a/src/EFCore/Metadata/RuntimeServiceProperty.cs +++ b/src/EFCore/Metadata/RuntimeServiceProperty.cs @@ -6,7 +6,6 @@ using System.Reflection; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Metadata @@ -98,11 +97,5 @@ IEntityType IServiceProperty.DeclaringEntityType [DebuggerStepThrough] get => DeclaringEntityType; } - - /// - [DebuggerStepThrough] - PropertyAccessMode IReadOnlyPropertyBase.GetPropertyAccessMode() - => (PropertyAccessMode)(this[CoreAnnotationNames.PropertyAccessMode] - ?? PropertyAccessMode.PreferField); } } diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs index d1ef01dfc86..2cd2fb8a4e0 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs @@ -449,9 +449,7 @@ public IIndex FindIndex(IReadOnlyList properties) => throw new NotImplementedException(); public PropertyInfo FindIndexerPropertyInfo() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); public IKey FindKey(IReadOnlyList properties) => throw new NotImplementedException(); @@ -463,9 +461,7 @@ public IKey FindPrimaryKey() => throw new NotImplementedException(); public IReadOnlyList FindProperties(IReadOnlyList propertyNames) - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); public IProperty FindProperty(string name) => null; @@ -477,9 +473,7 @@ public ISkipNavigation FindSkipNavigation(string name) => throw new NotImplementedException(); public ChangeTrackingStrategy GetChangeTrackingStrategy() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); public IEnumerable GetDeclaredForeignKeys() => throw new NotImplementedException(); @@ -503,9 +497,7 @@ public IEnumerable GetDeclaredServiceProperties() => throw new NotImplementedException(); public IEnumerable GetDeclaredSkipNavigations() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); public IEnumerable GetDerivedForeignKeys() => throw new NotImplementedException(); @@ -514,29 +506,19 @@ public IEnumerable GetDerivedIndexes() => throw new NotImplementedException(); public IEnumerable GetDerivedNavigations() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); public IEnumerable GetDerivedProperties() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); public IEnumerable GetDerivedServiceProperties() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); public IEnumerable GetDerivedSkipNavigations() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); public IEnumerable GetDerivedTypes() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); public IEnumerable GetDirectlyDerivedTypes() => throw new NotImplementedException(); @@ -556,26 +538,26 @@ public IEnumerable GetIndexes() public IEnumerable GetKeys() => throw new NotImplementedException(); + public PropertyAccessMode GetNavigationAccessMode() + => throw new NotImplementedException(); + public IEnumerable GetNavigations() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); public IEnumerable GetProperties() => throw new NotImplementedException(); + public PropertyAccessMode GetPropertyAccessMode() + => throw new NotImplementedException(); + public LambdaExpression GetQueryFilter() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); public IEnumerable GetReferencingForeignKeys() => throw new NotImplementedException(); public IEnumerable> GetSeedData(bool providerValues = false) - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); public IEnumerable GetServiceProperties() => throw new NotImplementedException(); @@ -587,28 +569,20 @@ public IEnumerable GetValueGeneratingProperties() => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.FindDeclaredForeignKeys(IReadOnlyList properties) - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IReadOnlyNavigation IReadOnlyEntityType.FindDeclaredNavigation(string name) - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IReadOnlyProperty IReadOnlyEntityType.FindDeclaredProperty(string name) - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IReadOnlyForeignKey IReadOnlyEntityType.FindForeignKey( IReadOnlyList properties, IReadOnlyKey principalKey, IReadOnlyEntityType principalEntityType) => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.FindForeignKeys(IReadOnlyList properties) - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IReadOnlyIndex IReadOnlyEntityType.FindIndex(IReadOnlyList properties) => throw new NotImplementedException(); @@ -632,54 +606,34 @@ IReadOnlySkipNavigation IReadOnlyEntityType.FindSkipNavigation(string name) => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetDeclaredForeignKeys() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetDeclaredIndexes() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetDeclaredKeys() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetDeclaredNavigations() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetDeclaredProperties() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetDeclaredReferencingForeignKeys() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetDeclaredServiceProperties() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetDerivedForeignKeys() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetDerivedIndexes() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetDirectlyDerivedTypes() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetForeignKeys() => throw new NotImplementedException(); @@ -691,17 +645,13 @@ IEnumerable IReadOnlyEntityType.GetKeys() => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetNavigations() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetProperties() => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetReferencingForeignKeys() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetServiceProperties() => throw new NotImplementedException(); diff --git a/test/EFCore.Design.Tests/EFCore.Design.Tests.csproj b/test/EFCore.Design.Tests/EFCore.Design.Tests.csproj index 518d49a2698..85f51856f52 100644 --- a/test/EFCore.Design.Tests/EFCore.Design.Tests.csproj +++ b/test/EFCore.Design.Tests/EFCore.Design.Tests.csproj @@ -8,10 +8,11 @@ + - + diff --git a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs index b24c4f30348..9f7ce630c78 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs @@ -223,7 +223,7 @@ public void Can_diff_against_older_sequence_model(Type snapshotType) var processor = new SnapshotModelProcessor(reporter, setBuilder); var model = processor.Process(snapshot.Model); - var differences = differ.GetDifferences(model.GetRelationalModel(), context.Model.GetRelationalModel()); + var differences = differ.GetDifferences(model.GetRelationalModel(), context.GetService().Model.GetRelationalModel()); Assert.Empty(differences); } diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeAnnotationCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeAnnotationCodeGeneratorTest.cs new file mode 100644 index 00000000000..2ae6ca3efb9 --- /dev/null +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeAnnotationCodeGeneratorTest.cs @@ -0,0 +1,3230 @@ +// 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; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration.Internal; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Sqlite.Design.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.EntityFrameworkCore.ValueGeneration.Internal; +using Microsoft.Extensions.DependencyInjection; +using NetTopologySuite; +using NetTopologySuite.Geometries; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal +{ + public class CSharpRuntimeAnnotationCodeGeneratorTest + { + [ConditionalFact] + public void Empty_model() + { + Test( + new EmptyContext(), + new CompiledModelCodeGenerationOptions(), + code => AssertFileContents( + "EmptyContextModel.cs", + @"// +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + [DbContext(typeof(CSharpRuntimeAnnotationCodeGeneratorTest.EmptyContext))] + partial class EmptyContextModel : RuntimeModel + { + private static EmptyContextModel _instance; + public static IModel Instance + { + get + { + if (_instance == null) + { + _instance = new EmptyContextModel(); + _instance.Initialize(); + } + + return _instance; + } + } + + protected override void Initialize() + { + + Customize(); + } + + partial void Customize(); + } +} +", + code.Single()), + model => { + Assert.Empty(model.GetEntityTypes()); + Assert.Same(model, model.FindRuntimeAnnotationValue("ReadOnlyModel")); + }); + } + + public class EmptyContext : ContextBase + { + } + + [ConditionalFact] + public void Global_namespace_works() + { + Test( + new GlobalNamespaceContext(), + new CompiledModelCodeGenerationOptions + { + ModelNamespace = string.Empty + }, + code => + { + Assert.All(code, f => Assert.DoesNotContain("namespace ", f.Code)); + }, + model => + { + Assert.NotNull(model.FindEntityType("1")); + }); + } + + public class GlobalNamespaceContext : ContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity("1", e => + { + e.Property("Id"); + e.HasKey("Id"); + }); + } + } + + [ConditionalFact] + public void Throws_for_constructor_binding() + { + Test( + new LazyLoadingProxiesContext(), + new CompiledModelCodeGenerationOptions(), + expectedExceptionMessage: DesignStrings.CompiledModelConstructorBinding("Lazy", "Customize()", "LazyEntityType")); + } + + public class LazyLoadingProxiesContext : ContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity("Lazy", e => + { + e.Property("Id"); + e.HasKey("Id"); + }); + } + + protected override void OnConfiguring(DbContextOptionsBuilder options) + => base.OnConfiguring(options.UseLazyLoadingProxies()); + } + + [ConditionalFact] + public void Manual_lazy_loading() + { + Test( + new LazyLoadingContext(), + new CompiledModelCodeGenerationOptions(), + assertModel: model => + { + var lazyConstructorEntity = model.FindEntityType(typeof(LazyConstructorEntity)); + var lazyParameterBinding = lazyConstructorEntity.ConstructorBinding.ParameterBindings.Single(); + Assert.Equal(typeof(ILazyLoader), lazyParameterBinding.ParameterType); + var lazyPropertyEntity = model.FindEntityType(typeof(LazyPropertyEntity)); + var lazyServiceProperty = lazyPropertyEntity.GetServiceProperties().Single(); + Assert.Equal(typeof(ILazyLoader), lazyServiceProperty.ClrType); + }); + } + + public class LazyLoadingContext : ContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(); + } + } + + public class LazyConstructorEntity + { + private readonly ILazyLoader _loader; + + public LazyConstructorEntity(ILazyLoader loader) + { + _loader = loader; + } + + public int Id { get; set; } + + public LazyPropertyEntity LazyPropertyEntity { get; set; } + } + + public class LazyPropertyEntity + { + public ILazyLoader Loader { get; set; } + + public LazyPropertyEntity() + { + } + + public int Id { get; set; } + public int LazyConstructorEntityId { get; set; } + + public LazyConstructorEntity LazyConstructorEntity { get; set; } + } + + [ConditionalFact] + public void Throws_for_query_filter() + { + Test( + new QueryFilterContext(), + new CompiledModelCodeGenerationOptions(), + expectedExceptionMessage: DesignStrings.CompiledModelQueryFilter("QueryFilter")); + } + + public class QueryFilterContext : ContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity("QueryFilter", e => + { + e.Property("Id"); + e.HasKey("Id"); + e.HasQueryFilter((Expression, bool>>)(e => e != null)); + }); + } + } + + [ConditionalFact] + public void Throws_for_defining_query() + { + Test( + new DefiningQueryContext(), + new CompiledModelCodeGenerationOptions(), + expectedExceptionMessage: DesignStrings.CompiledModelDefiningQuery("object")); + } + + public class DefiningQueryContext : ContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(e => + { + e.Property("Id"); + e.HasKey("Id"); + e.Metadata.SetInMemoryQuery((Expression>>) + (() => Set())); + }); + } + } + + [ConditionalFact] + public void Throws_for_value_generator() + { + Test( + new ValueGeneratorContext(), + new CompiledModelCodeGenerationOptions(), + expectedExceptionMessage: DesignStrings.CompiledModelValueGenerator( + "MyEntity", "Id", nameof(PropertyBuilder.HasValueGeneratorFactory))); + } + + public class ValueGeneratorContext : ContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity("MyEntity", e => + { + e.Property("Id").HasValueGenerator((p, e) => null); + e.HasKey("Id"); + }); + } + } + + [ConditionalFact] + public void Throws_for_value_converter() + { + Test( + new ValueConverterContext(), + new CompiledModelCodeGenerationOptions(), + expectedExceptionMessage: DesignStrings.CompiledModelValueConverter( + "MyEntity", "Id", nameof(PropertyBuilder.HasConversion))); + } + + public class ValueConverterContext : ContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity("MyEntity", e => + { + e.Property("Id").HasConversion(i => i, i => i); + e.HasKey("Id"); + }); + } + } + + [ConditionalFact] + public void Throws_for_value_comparer() + { + Test( + new ValueComparerContext(), + new CompiledModelCodeGenerationOptions(), + expectedExceptionMessage: DesignStrings.CompiledModelValueComparer( + "MyEntity", "Id", nameof(PropertyBuilder.HasConversion))); + } + + public class ValueComparerContext : ContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity("MyEntity", e => + { + e.Property("Id").HasConversion(typeof(int), new FakeValueComparer()); + e.HasKey("Id"); + }); + } + } + + private class FakeValueComparer : ValueComparer + { + public FakeValueComparer() + : base(false) + { + } + + public override Type Type { get; } = typeof(int); + + public override bool Equals(object left, object right) => throw new NotImplementedException(); + + public override int GetHashCode(object instance) => throw new NotImplementedException(); + + public override object Snapshot(object instance) => throw new NotImplementedException(); + } + + [ConditionalFact] + public void Throws_for_custom_type_mapping() + { + Test( + new TypeMappingContext(), + new CompiledModelCodeGenerationOptions(), + expectedExceptionMessage: DesignStrings.CompiledModelTypeMapping( + "MyEntity", "Id", "Customize()", "MyEntityEntityType")); + } + + public class TypeMappingContext : ContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity("MyEntity", e => + { + e.Property("Id").Metadata.SetTypeMapping(new InMemoryTypeMapping(typeof(int[]))); + e.HasKey("Id"); + }); + } + } + + [ConditionalFact] + public void Throws_for_custom_function_translation() + { + Test( + new FunctionTranslationContext(), + new CompiledModelCodeGenerationOptions(), + expectedExceptionMessage: RelationalStrings.CompiledModelFunctionTranslation("GetSqlFragmentStatic")); + } + + public class FunctionTranslationContext : SqlServerContextBase + { + public static string GetSqlFragmentStatic() + => throw new NotImplementedException(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.HasDbFunction(typeof(FunctionTranslationContext).GetMethod(nameof(GetSqlFragmentStatic))) + .HasTranslation(args => new SqlFragmentExpression("NULL")); + } + } + + [ConditionalFact] + public void Throws_for_custom_function_type_mapping() + { + Test( + new FunctionTypeMappingContext(), + new CompiledModelCodeGenerationOptions(), + expectedExceptionMessage: RelationalStrings.CompiledModelFunctionTypeMapping( + "GetSqlFragmentStatic", "Customize()", "FunctionTypeMappingContextModel")); + } + + public class FunctionTypeMappingContext : SqlServerContextBase + { + public static string GetSqlFragmentStatic(string param) + => throw new NotImplementedException(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.HasDbFunction(typeof(FunctionTypeMappingContext).GetMethod(nameof(GetSqlFragmentStatic))) + .Metadata.TypeMapping = new StringTypeMapping("varchar"); + } + } + + [ConditionalFact] + public void Throws_for_custom_function_parameter_type_mapping() + { + Test( + new FunctionParameterTypeMappingContext(), + new CompiledModelCodeGenerationOptions(), + expectedExceptionMessage: RelationalStrings.CompiledModelFunctionParameterTypeMapping( + "GetSqlFragmentStatic", "param", "Customize()", "FunctionParameterTypeMappingContextModel")); + } + + public class FunctionParameterTypeMappingContext : SqlServerContextBase + { + public static string GetSqlFragmentStatic(string param) + => throw new NotImplementedException(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.HasDbFunction(typeof(FunctionParameterTypeMappingContext).GetMethod(nameof(GetSqlFragmentStatic))) + .HasParameter("param").Metadata.TypeMapping = new StringTypeMapping("varchar"); + } + } + + [ConditionalFact] + public void BigModel() + { + Test( + new BigContext(), + new CompiledModelCodeGenerationOptions(), + code => + Assert.Collection(code, + c => AssertFileContents("BigContextModel.cs", + @"// +using System; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + [DbContext(typeof(CSharpRuntimeAnnotationCodeGeneratorTest.BigContext))] + partial class BigContextModel : RuntimeModel + { + private static BigContextModel _instance; + public static IModel Instance + { + get + { + if (_instance == null) + { + _instance = new BigContextModel(); + _instance.Initialize(); + } + + return _instance; + } + } + + protected override void Initialize() + { + var dependentBasebyte = DependentBasebyteEntityType.Create(this, null); + var principalBase = PrincipalBaseEntityType.Create(this, null); + var ownedType = OwnedTypeEntityType.Create(this, null); + var ownedType0 = OwnedType0EntityType.Create(this, null); + var principalBasePrincipalDerivedDependentBasebyte = PrincipalBasePrincipalDerivedDependentBasebyteEntityType.Create(this, null); + var dependentDerivedbyte = DependentDerivedbyteEntityType.Create(this, dependentBasebyte); + var principalDerivedDependentBasebyte = PrincipalDerivedDependentBasebyteEntityType.Create(this, principalBase); + + DependentBasebyteEntityType.CreateForeignKey1(dependentBasebyte, principalBase); + DependentBasebyteEntityType.CreateForeignKey2(dependentBasebyte, principalDerivedDependentBasebyte); + OwnedTypeEntityType.CreateForeignKey1(ownedType, principalBase); + OwnedType0EntityType.CreateForeignKey1(ownedType0, principalDerivedDependentBasebyte); + PrincipalBasePrincipalDerivedDependentBasebyteEntityType.CreateForeignKey1(principalBasePrincipalDerivedDependentBasebyte, principalDerivedDependentBasebyte); + PrincipalBasePrincipalDerivedDependentBasebyteEntityType.CreateForeignKey2(principalBasePrincipalDerivedDependentBasebyte, principalBase); + PrincipalDerivedDependentBasebyteEntityType.CreateForeignKey1(principalDerivedDependentBasebyte, principalBase); + + PrincipalBaseEntityType.CreateSkipNavigation1(principalBase, principalDerivedDependentBasebyte, principalBasePrincipalDerivedDependentBasebyte); + PrincipalDerivedDependentBasebyteEntityType.CreateSkipNavigation1(principalDerivedDependentBasebyte, principalBase, principalBasePrincipalDerivedDependentBasebyte); + + DependentBasebyteEntityType.CreateAnnotations(dependentBasebyte); + PrincipalBaseEntityType.CreateAnnotations(principalBase); + OwnedTypeEntityType.CreateAnnotations(ownedType); + OwnedType0EntityType.CreateAnnotations(ownedType0); + PrincipalBasePrincipalDerivedDependentBasebyteEntityType.CreateAnnotations(principalBasePrincipalDerivedDependentBasebyte); + DependentDerivedbyteEntityType.CreateAnnotations(dependentDerivedbyte); + PrincipalDerivedDependentBasebyteEntityType.CreateAnnotations(principalDerivedDependentBasebyte); + + AddAnnotation(""Relational:MaxIdentifierLength"", 128); + AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + + Customize(); + } + + partial void Customize(); + } +} +", c), + c => AssertFileContents("DependentBasebyteEntityType.cs", @"// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; +using Microsoft.EntityFrameworkCore.ValueGeneration; +using NetTopologySuite.Geometries; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class DependentBasebyteEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DependentBase"", + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentBase), + baseEntityType, + discriminatorProperty: ""EnumDiscriminator""); + + var principalId = runtimeEntityType.AddProperty( + ""PrincipalId"", + typeof(long), + afterSaveBehavior: PropertySaveBehavior.Throw); + principalId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var principalAlternateId = runtimeEntityType.AddProperty( + ""PrincipalAlternateId"", + typeof(Point), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw); + principalAlternateId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var enumDiscriminator = runtimeEntityType.AddProperty( + ""EnumDiscriminator"", + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Discriminator), + afterSaveBehavior: PropertySaveBehavior.Throw, + valueGeneratorFactory: new DiscriminatorValueGeneratorFactory().Create); + enumDiscriminator.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var id = runtimeEntityType.AddProperty( + ""Id"", + typeof(byte?), + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentBase).GetProperty(""Id"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + id.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var key = runtimeEntityType.AddKey( + new[] { principalId, principalAlternateId }); + runtimeEntityType.SetPrimaryKey(key); + + var index = runtimeEntityType.AddIndex( + new[] { principalId }, + unique: true); + + return runtimeEntityType; + } + + public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalId"") }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id"") }), + principalEntityType, + deleteBehavior: DeleteBehavior.Cascade, + unique: true, + required: true); + + return runtimeForeignKey; + } + + public static RuntimeForeignKey CreateForeignKey2(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalId""), declaringEntityType.FindProperty(""PrincipalAlternateId"") }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id""), principalEntityType.FindProperty(""AlternateId"") }), + principalEntityType, + deleteBehavior: DeleteBehavior.ClientNoAction, + unique: true, + required: true); + + var principal = declaringEntityType.AddNavigation(""Principal"", + runtimeForeignKey, + onDependent: true, + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalDerived>), + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentBase).GetProperty(""Principal"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + var dependent = principalEntityType.AddNavigation(""Dependent"", + runtimeForeignKey, + onDependent: false, + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentBase), + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalDerived>).GetProperty(""Dependent"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalDerived>).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + eagerLoaded: true); + + return runtimeForeignKey; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""DiscriminatorValue"", CSharpRuntimeAnnotationCodeGeneratorTest.Discriminator.Base); + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); + runtimeEntityType.AddAnnotation(""Relational:Schema"", null); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", ""PrincipalDerived""); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", c), + c => AssertFileContents("PrincipalBaseEntityType.cs", @"// +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetTopologySuite.Geometries; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class PrincipalBaseEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+PrincipalBase"", + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase), + baseEntityType); + + var id = runtimeEntityType.AddProperty( + ""Id"", + typeof(long?), + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase).GetProperty(""Id"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw); + var overrides = new SortedDictionary(); + var idPrincipalDerived = new RuntimeRelationalPropertyOverrides( + id, + true, + ""DerivedId""); + overrides[StoreObjectIdentifier.Table(""PrincipalDerived"", null)] = idPrincipalDerived; + id.AddAnnotation(""Relational:RelationalOverrides"", overrides); + id.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + + var alternateId = runtimeEntityType.AddProperty( + ""AlternateId"", + typeof(Point), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase).GetField(""AlternateId"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyAccessMode: PropertyAccessMode.FieldDuringConstruction, + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw, + valueConverter: new CastingConverter(), + valueComparer: new CSharpRuntimeAnnotationCodeGeneratorTest.CustomValueComparer()); + alternateId.AddAnnotation(""Relational:ColumnType"", ""geometry""); + alternateId.AddAnnotation(""Relational:DefaultValue"", (NetTopologySuite.Geometries.Point)new NetTopologySuite.IO.WKTReader().Read(""SRID=0;POINT Z(0 0 0)"")); + alternateId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var key = runtimeEntityType.AddKey( + new[] { id }); + + var key0 = runtimeEntityType.AddKey( + new[] { id, alternateId }); + runtimeEntityType.SetPrimaryKey(key0); + key0.AddAnnotation(""Relational:Name"", ""PK""); + + var index = runtimeEntityType.AddIndex( + new[] { alternateId, id }); + + var alternateIndex = runtimeEntityType.AddIndex( + new[] { alternateId }, + name: ""AlternateIndex"", + unique: true); + alternateIndex.AddAnnotation(""Relational:Name"", ""AIX""); + + return runtimeEntityType; + } + + public static RuntimeSkipNavigation CreateSkipNavigation1(RuntimeEntityType declaringEntityType, RuntimeEntityType targetEntityType, RuntimeEntityType joinEntityType) + { + var skipNavigation = declaringEntityType.AddSkipNavigation( + ""Deriveds"", + targetEntityType, + joinEntityType.FindForeignKey( + new[] { joinEntityType.FindProperty(""PrincipalsId""), joinEntityType.FindProperty(""PrincipalsAlternateId"") }, + declaringEntityType.FindKey(new[] { declaringEntityType.FindProperty(""Id""), declaringEntityType.FindProperty(""AlternateId"") }), + declaringEntityType), + true, + false, + typeof(ICollection), + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase).GetProperty(""Deriveds"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + var inverse = targetEntityType.FindSkipNavigation(""Principals""); + if (inverse != null) + { + skipNavigation.Inverse = inverse; + inverse.Inverse = skipNavigation; + } + + return skipNavigation; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); + runtimeEntityType.AddAnnotation(""Relational:Schema"", ""mySchema""); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", ""PrincipalBase""); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", c), + c => AssertFileContents("OwnedTypeEntityType.cs", @"// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; +using NetTopologySuite.Geometries; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class OwnedTypeEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+PrincipalBase.Owned#OwnedType"", + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.OwnedType), + baseEntityType, + sharedClrType: true, + changeTrackingStrategy: ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); + + var principalBaseId = runtimeEntityType.AddProperty( + ""PrincipalBaseId"", + typeof(long), + propertyAccessMode: PropertyAccessMode.Field, + afterSaveBehavior: PropertySaveBehavior.Throw); + principalBaseId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var principalBaseAlternateId = runtimeEntityType.AddProperty( + ""PrincipalBaseAlternateId"", + typeof(Point), + propertyAccessMode: PropertyAccessMode.Field, + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw); + principalBaseAlternateId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var context = runtimeEntityType.AddServiceProperty( + ""Context"", + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.OwnedType).GetProperty(""Context"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + var key = runtimeEntityType.AddKey( + new[] { principalBaseId, principalBaseAlternateId }); + runtimeEntityType.SetPrimaryKey(key); + + return runtimeEntityType; + } + + public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalBaseId""), declaringEntityType.FindProperty(""PrincipalBaseAlternateId"") }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id""), principalEntityType.FindProperty(""AlternateId"") }), + principalEntityType, + deleteBehavior: DeleteBehavior.Cascade, + unique: true, + required: true, + requiredDependent: true, + ownership: true); + + var owned = principalEntityType.AddNavigation(""Owned"", + runtimeForeignKey, + onDependent: false, + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.OwnedType), + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase).GetProperty(""Owned"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase).GetField(""_ownedField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyAccessMode: PropertyAccessMode.Field, + eagerLoaded: true); + + return runtimeForeignKey; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); + runtimeEntityType.AddAnnotation(""Relational:Schema"", ""mySchema""); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", ""PrincipalBase""); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", c), + c => AssertFileContents("OwnedType0EntityType.cs", @"// +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; +using NetTopologySuite.Geometries; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class OwnedType0EntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+PrincipalDerived>.ManyOwned#OwnedType"", + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.OwnedType), + baseEntityType, + sharedClrType: true); + + var principalDerivedDependentBasebyteId = runtimeEntityType.AddProperty( + ""PrincipalDerived>Id"", + typeof(long), + afterSaveBehavior: PropertySaveBehavior.Throw); + principalDerivedDependentBasebyteId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var principalDerivedDependentBasebyteAlternateId = runtimeEntityType.AddProperty( + ""PrincipalDerived>AlternateId"", + typeof(Point), + afterSaveBehavior: PropertySaveBehavior.Throw); + principalDerivedDependentBasebyteAlternateId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var id = runtimeEntityType.AddProperty( + ""Id"", + typeof(int), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw); + id.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + + var context = runtimeEntityType.AddServiceProperty( + ""Context"", + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.OwnedType).GetProperty(""Context"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + var key = runtimeEntityType.AddKey( + new[] { principalDerivedDependentBasebyteId, principalDerivedDependentBasebyteAlternateId, id }); + runtimeEntityType.SetPrimaryKey(key); + + return runtimeEntityType; + } + + public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalDerived>Id""), declaringEntityType.FindProperty(""PrincipalDerived>AlternateId"") }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id""), principalEntityType.FindProperty(""AlternateId"") }), + principalEntityType, + deleteBehavior: DeleteBehavior.Cascade, + required: true, + ownership: true); + + var manyOwned = principalEntityType.AddNavigation(""ManyOwned"", + runtimeForeignKey, + onDependent: false, + typeof(ICollection), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalDerived>).GetField(""ManyOwned"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + eagerLoaded: true); + + return runtimeForeignKey; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); + runtimeEntityType.AddAnnotation(""Relational:Schema"", null); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", ""ManyOwned""); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", null); + runtimeEntityType.AddAnnotation(""SqlServer:MemoryOptimized"", true); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", c), + c => AssertFileContents("PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs", @"// +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using NetTopologySuite.Geometries; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class PrincipalBasePrincipalDerivedDependentBasebyteEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + { + var runtimeEntityType = model.AddEntityType( + ""PrincipalBasePrincipalDerived>"", + typeof(Dictionary), + baseEntityType, + sharedClrType: true, + indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), + propertyBag: true); + + var derivedsId = runtimeEntityType.AddProperty( + ""DerivedsId"", + typeof(long), + propertyInfo: runtimeEntityType.FindIndexerPropertyInfo(), + afterSaveBehavior: PropertySaveBehavior.Throw); + derivedsId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var derivedsAlternateId = runtimeEntityType.AddProperty( + ""DerivedsAlternateId"", + typeof(Point), + propertyInfo: runtimeEntityType.FindIndexerPropertyInfo(), + afterSaveBehavior: PropertySaveBehavior.Throw); + derivedsAlternateId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var principalsId = runtimeEntityType.AddProperty( + ""PrincipalsId"", + typeof(long), + propertyInfo: runtimeEntityType.FindIndexerPropertyInfo(), + afterSaveBehavior: PropertySaveBehavior.Throw); + principalsId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var principalsAlternateId = runtimeEntityType.AddProperty( + ""PrincipalsAlternateId"", + typeof(Point), + propertyInfo: runtimeEntityType.FindIndexerPropertyInfo(), + afterSaveBehavior: PropertySaveBehavior.Throw); + principalsAlternateId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var rowid = runtimeEntityType.AddProperty( + ""rowid"", + typeof(byte[]), + propertyInfo: runtimeEntityType.FindIndexerPropertyInfo(), + nullable: true, + concurrencyToken: true, + valueGenerated: ValueGenerated.OnAddOrUpdate, + beforeSaveBehavior: PropertySaveBehavior.Ignore, + afterSaveBehavior: PropertySaveBehavior.Ignore); + rowid.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var key = runtimeEntityType.AddKey( + new[] { derivedsId, derivedsAlternateId, principalsId, principalsAlternateId }); + runtimeEntityType.SetPrimaryKey(key); + + var index = runtimeEntityType.AddIndex( + new[] { principalsId, principalsAlternateId }); + + return runtimeEntityType; + } + + public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""DerivedsId""), declaringEntityType.FindProperty(""DerivedsAlternateId"") }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id""), principalEntityType.FindProperty(""AlternateId"") }), + principalEntityType, + deleteBehavior: DeleteBehavior.Cascade, + required: true); + + return runtimeForeignKey; + } + + public static RuntimeForeignKey CreateForeignKey2(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalsId""), declaringEntityType.FindProperty(""PrincipalsAlternateId"") }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id""), principalEntityType.FindProperty(""AlternateId"") }), + principalEntityType, + deleteBehavior: DeleteBehavior.Cascade, + required: true); + + return runtimeForeignKey; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); + runtimeEntityType.AddAnnotation(""Relational:Schema"", null); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", ""PrincipalBasePrincipalDerived>""); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", c), + c => AssertFileContents("DependentDerivedbyteEntityType.cs", @"// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class DependentDerivedbyteEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DependentDerived"", + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentDerived), + baseEntityType, + discriminatorProperty: ""EnumDiscriminator""); + + var data = runtimeEntityType.AddProperty( + ""Data"", + typeof(string), + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentDerived).GetProperty(""Data"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentDerived).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true, + maxLength: 20, + unicode: false); + data.AddAnnotation(""Relational:IsFixedLength"", true); + data.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var money = runtimeEntityType.AddProperty( + ""Money"", + typeof(decimal), + precision: 9, + scale: 3); + money.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""DiscriminatorValue"", CSharpRuntimeAnnotationCodeGeneratorTest.Discriminator.Derived); + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); + runtimeEntityType.AddAnnotation(""Relational:Schema"", null); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", ""PrincipalDerived""); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", c), + c => AssertFileContents("PrincipalDerivedDependentBasebyteEntityType.cs", @"// +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class PrincipalDerivedDependentBasebyteEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+PrincipalDerived>"", + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalDerived>), + baseEntityType); + + return runtimeEntityType; + } + + public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""Id""), declaringEntityType.FindProperty(""AlternateId"") }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id""), principalEntityType.FindProperty(""AlternateId"") }), + principalEntityType, + deleteBehavior: DeleteBehavior.ClientCascade, + unique: true, + required: true); + + return runtimeForeignKey; + } + + public static RuntimeSkipNavigation CreateSkipNavigation1(RuntimeEntityType declaringEntityType, RuntimeEntityType targetEntityType, RuntimeEntityType joinEntityType) + { + var skipNavigation = declaringEntityType.AddSkipNavigation( + ""Principals"", + targetEntityType, + joinEntityType.FindForeignKey( + new[] { joinEntityType.FindProperty(""DerivedsId""), joinEntityType.FindProperty(""DerivedsAlternateId"") }, + declaringEntityType.FindKey(new[] { declaringEntityType.FindProperty(""Id""), declaringEntityType.FindProperty(""AlternateId"") }), + declaringEntityType), + true, + false, + typeof(ICollection), + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalDerived>).GetProperty(""Principals"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalDerived>).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + eagerLoaded: true); + + var inverse = targetEntityType.FindSkipNavigation(""Deriveds""); + if (inverse != null) + { + skipNavigation.Inverse = inverse; + inverse.Inverse = skipNavigation; + } + + return skipNavigation; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); + runtimeEntityType.AddAnnotation(""Relational:Schema"", null); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", ""PrincipalDerived""); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", c)), + model => + { + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => model.GetCollation()).Message); + Assert.Null(model[RelationalAnnotationNames.Collation]); + Assert.Equal(SqlServerValueGenerationStrategy.IdentityColumn, model.GetValueGenerationStrategy()); + Assert.Null(model[CoreAnnotationNames.PropertyAccessMode]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => model.GetPropertyAccessMode()).Message); + Assert.Null(model[SqlServerAnnotationNames.MaxDatabaseSize]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => model.GetDatabaseMaxSize()).Message); + Assert.Null(model[SqlServerAnnotationNames.PerformanceLevelSql]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => model.GetPerformanceLevelSql()).Message); + Assert.Null(model[SqlServerAnnotationNames.ServiceTierSql]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => model.GetServiceTierSql()).Message); + Assert.Null(model[SqlServerAnnotationNames.IdentitySeed]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => model.GetIdentitySeed()).Message); + Assert.Null(model[SqlServerAnnotationNames.IdentityIncrement]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => model.GetIdentityIncrement()).Message); + + Assert.Null(model.FindEntityType(typeof(AbstractBase))); + var principalBase = model.FindEntityType(typeof(PrincipalBase)); + Assert.Equal(typeof(PrincipalBase).FullName, principalBase.Name); + Assert.False(principalBase.HasSharedClrType); + Assert.False(principalBase.IsPropertyBag); + Assert.False(principalBase.IsOwned()); + Assert.Null(principalBase.BaseType); + Assert.IsType(principalBase.ConstructorBinding); + Assert.Null(principalBase.FindIndexerPropertyInfo()); + Assert.Equal(ChangeTrackingStrategy.Snapshot, principalBase.GetChangeTrackingStrategy()); + Assert.Null(principalBase.GetQueryFilter()); + Assert.Equal("PrincipalBase", principalBase.GetTableName()); + Assert.Equal("mySchema", principalBase.GetSchema()); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => principalBase.GetSeedData()).Message); + + var principalId = principalBase.FindProperty(nameof(PrincipalBase.Id)); + Assert.Equal(typeof(long?), principalId.ClrType); + Assert.Equal(typeof(long?), principalId.PropertyInfo.PropertyType); + Assert.Equal(typeof(long?), principalId.FieldInfo.FieldType); + Assert.False(principalId.IsNullable); + Assert.Equal(ValueGenerated.OnAdd, principalId.ValueGenerated); + Assert.Equal(PropertySaveBehavior.Throw, principalId.GetAfterSaveBehavior()); + Assert.Equal(PropertySaveBehavior.Save, principalId.GetBeforeSaveBehavior()); + Assert.Null(principalId[CoreAnnotationNames.BeforeSaveBehavior]); + Assert.Null(principalId[CoreAnnotationNames.AfterSaveBehavior]); + Assert.Equal("Id", principalId.GetColumnBaseName()); + Assert.Equal("Id", principalId.GetColumnName(StoreObjectIdentifier.Table("PrincipalBase", "mySchema"))); + Assert.Equal("DerivedId", principalId.GetColumnName(StoreObjectIdentifier.Table("PrincipalDerived"))); + Assert.Equal("bigint", principalId.GetColumnType()); + Assert.Null(principalId.GetValueConverter()); + Assert.NotNull(principalId.GetValueComparer()); + Assert.NotNull(principalId.GetKeyValueComparer()); + Assert.Equal(SqlServerValueGenerationStrategy.IdentityColumn, principalId.GetValueGenerationStrategy()); + Assert.Null(principalId[SqlServerAnnotationNames.IdentitySeed]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => principalId.GetIdentitySeed()).Message); + Assert.Null(principalId[SqlServerAnnotationNames.IdentityIncrement]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => principalId.GetIdentityIncrement()).Message); + Assert.Null(principalId[SqlServerAnnotationNames.Sparse]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => principalId.IsSparse()).Message); + + var principalAlternateId = principalBase.FindProperty(nameof(PrincipalBase.AlternateId)); + Assert.Equal(typeof(Point), principalAlternateId.ClrType); + Assert.False(principalAlternateId.IsNullable); + Assert.Equal(ValueGenerated.OnAdd, principalAlternateId.ValueGenerated); + Assert.Equal("AlternateId", principalAlternateId.GetColumnBaseName()); + Assert.Equal("geometry", principalAlternateId.GetColumnType()); + Assert.Equal(0, ((Point)principalAlternateId.GetDefaultValue()).SRID); + Assert.IsType>(principalAlternateId.GetValueConverter()); + Assert.IsType>(principalAlternateId.GetValueComparer()); + Assert.IsType>(principalAlternateId.GetKeyValueComparer()); + Assert.Equal(SqlServerValueGenerationStrategy.None, principalAlternateId.GetValueGenerationStrategy()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, principalAlternateId.GetPropertyAccessMode()); + Assert.Null(principalAlternateId[CoreAnnotationNames.PropertyAccessMode]); + + Assert.Null(principalBase.FindDiscriminatorProperty()); + + Assert.Equal(2, principalBase.GetIndexes().Count()); + + var compositeIndex = principalBase.GetIndexes().First(); + Assert.Equal(new[] { principalAlternateId, principalId }, compositeIndex.Properties); + Assert.False(compositeIndex.IsUnique); + Assert.Null(compositeIndex.Name); + Assert.Equal("IX_PrincipalBase_AlternateId_Id", compositeIndex.GetDatabaseName()); + Assert.Null(compositeIndex[SqlServerAnnotationNames.Clustered]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => compositeIndex.IsClustered()).Message); + Assert.Null(compositeIndex[SqlServerAnnotationNames.CreatedOnline]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => compositeIndex.IsCreatedOnline()).Message); + Assert.Null(compositeIndex[SqlServerAnnotationNames.FillFactor]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => compositeIndex.GetFillFactor()).Message); + Assert.Null(compositeIndex[SqlServerAnnotationNames.Include]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => compositeIndex.GetIncludeProperties()).Message); + + var alternateIndex = principalBase.GetIndexes().Last(); + Assert.Same(principalAlternateId, alternateIndex.Properties.Single()); + Assert.True(alternateIndex.IsUnique); + Assert.Equal("AlternateIndex", alternateIndex.Name); + Assert.Equal("AIX", alternateIndex.GetDatabaseName()); + Assert.Null(alternateIndex[RelationalAnnotationNames.Filter]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => alternateIndex.GetFilter()).Message); + + Assert.Equal(new[] { compositeIndex, alternateIndex }, principalAlternateId.GetContainingIndexes()); + + Assert.Equal(2, principalBase.GetKeys().Count()); + + var principalAlternateKey = principalBase.GetKeys().First(); + Assert.Same(principalId, principalAlternateKey.Properties.Single()); + Assert.False(principalAlternateKey.IsPrimaryKey()); + Assert.Equal("AK_PrincipalBase_Id", principalAlternateKey.GetName()); + + var principalKey = principalBase.GetKeys().Last(); + Assert.Equal(new[] { principalId, principalAlternateId }, principalKey.Properties); + Assert.True(principalKey.IsPrimaryKey()); + Assert.Equal("PK", principalKey.GetName()); + Assert.Null(principalKey[SqlServerAnnotationNames.Clustered]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => principalKey.IsClustered()).Message); + + Assert.Equal(new[] { principalAlternateKey, principalKey }, principalId.GetContainingKeys()); + + var referenceOwnedNavigation = principalBase.GetNavigations().Single(); + Assert.Equal(nameof(PrincipalBase.Owned), referenceOwnedNavigation.Name); + Assert.False(referenceOwnedNavigation.IsCollection); + Assert.True(referenceOwnedNavigation.IsEagerLoaded); + Assert.False(referenceOwnedNavigation.IsOnDependent); + Assert.Equal(typeof(OwnedType), referenceOwnedNavigation.ClrType); + Assert.Equal("_ownedField", referenceOwnedNavigation.FieldInfo.Name); + Assert.Equal(nameof(PrincipalBase.Owned), referenceOwnedNavigation.PropertyInfo.Name); + Assert.Null(referenceOwnedNavigation.Inverse); + Assert.Equal(principalBase, referenceOwnedNavigation.DeclaringEntityType); + Assert.Equal(PropertyAccessMode.Field, referenceOwnedNavigation.GetPropertyAccessMode()); + Assert.Null(referenceOwnedNavigation[CoreAnnotationNames.PropertyAccessMode]); + + var referenceOwnedType = referenceOwnedNavigation.TargetEntityType; + Assert.Equal(typeof(PrincipalBase).FullName + ".Owned#OwnedType", referenceOwnedType.Name); + Assert.Equal(typeof(OwnedType), referenceOwnedType.ClrType); + Assert.True(referenceOwnedType.HasSharedClrType); + Assert.False(referenceOwnedType.IsPropertyBag); + Assert.True(referenceOwnedType.IsOwned()); + Assert.Null(referenceOwnedType.BaseType); + Assert.False(referenceOwnedType.IsMemoryOptimized()); + Assert.IsType(referenceOwnedType.ConstructorBinding); + Assert.Null(referenceOwnedType.FindIndexerPropertyInfo()); + Assert.Equal(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues, referenceOwnedType.GetChangeTrackingStrategy()); + Assert.Null(referenceOwnedType.GetQueryFilter()); + Assert.Null(referenceOwnedType[CoreAnnotationNames.PropertyAccessMode]); + Assert.Null(referenceOwnedType[CoreAnnotationNames.NavigationAccessMode]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => referenceOwnedType.GetPropertyAccessMode()).Message); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => referenceOwnedType.GetNavigationAccessMode()).Message); + + var referenceOwnership = referenceOwnedNavigation.ForeignKey; + Assert.Same(referenceOwnership, referenceOwnedType.FindOwnership()); + Assert.True(referenceOwnership.IsOwnership); + Assert.True(referenceOwnership.IsRequired); + Assert.True(referenceOwnership.IsRequiredDependent); + Assert.True(referenceOwnership.IsUnique); + Assert.Null(referenceOwnership.DependentToPrincipal); + Assert.Same(referenceOwnedNavigation, referenceOwnership.PrincipalToDependent); + Assert.Equal(DeleteBehavior.Cascade, referenceOwnership.DeleteBehavior); + Assert.Equal(2, referenceOwnership.Properties.Count()); + Assert.Same(principalKey, referenceOwnership.PrincipalKey); + + var ownedServiceProperty = referenceOwnedType.GetServiceProperties().Single(); + Assert.Equal(typeof(DbContext), ownedServiceProperty.ClrType); + Assert.Equal(typeof(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(ownedServiceProperty, ownedServicePropertyBinding.ConsumedProperties.Single()); + Assert.Equal(PropertyAccessMode.PreferField, ownedServiceProperty.GetPropertyAccessMode()); + Assert.Null(ownedServiceProperty[CoreAnnotationNames.PropertyAccessMode]); + + var principalDerived = model.FindEntityType(typeof(PrincipalDerived>)); + Assert.Equal(principalBase, principalDerived.BaseType); + Assert.Equal("Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+"+ + "PrincipalDerived>", + principalDerived.Name); + Assert.False(principalDerived.IsOwned()); + Assert.IsType(principalDerived.ConstructorBinding); + Assert.Equal(ChangeTrackingStrategy.Snapshot, principalDerived.GetChangeTrackingStrategy()); + Assert.Null(principalDerived.GetDiscriminatorValue()); + + var tptForeignKey = principalDerived.GetForeignKeys().Single(); + Assert.False(tptForeignKey.IsOwnership); + Assert.True(tptForeignKey.IsRequired); + Assert.False(tptForeignKey.IsRequiredDependent); + Assert.True(tptForeignKey.IsUnique); + Assert.Null(tptForeignKey.DependentToPrincipal); + Assert.Null(tptForeignKey.PrincipalToDependent); + Assert.Equal(DeleteBehavior.ClientCascade, tptForeignKey.DeleteBehavior); + Assert.Equal(principalKey.Properties, tptForeignKey.Properties); + Assert.Same(principalKey, tptForeignKey.PrincipalKey); + + Assert.Equal(2, principalDerived.GetDeclaredNavigations().Count()); + var dependentNavigation = principalDerived.GetDeclaredNavigations().First(); + Assert.Equal("Dependent", dependentNavigation.Name); + Assert.Equal("Dependent", dependentNavigation.PropertyInfo.Name); + Assert.Equal("k__BackingField", dependentNavigation.FieldInfo.Name); + Assert.False(dependentNavigation.IsCollection); + Assert.True(dependentNavigation.IsEagerLoaded); + Assert.False(dependentNavigation.IsOnDependent); + Assert.Equal(principalDerived, dependentNavigation.DeclaringEntityType); + Assert.Equal("Principal", dependentNavigation.Inverse.Name); + + var ownedCollectionNavigation = principalDerived.GetDeclaredNavigations().Last(); + Assert.Equal("ManyOwned", ownedCollectionNavigation.Name); + Assert.Null(ownedCollectionNavigation.PropertyInfo); + Assert.Equal("ManyOwned", ownedCollectionNavigation.FieldInfo.Name); + Assert.Equal(typeof(ICollection), ownedCollectionNavigation.ClrType); + Assert.True(ownedCollectionNavigation.IsCollection); + Assert.True(ownedCollectionNavigation.IsEagerLoaded); + Assert.False(ownedCollectionNavigation.IsOnDependent); + Assert.Null(ownedCollectionNavigation.Inverse); + Assert.Equal(principalDerived, ownedCollectionNavigation.DeclaringEntityType); + + var collectionOwnedType = ownedCollectionNavigation.TargetEntityType; + Assert.Equal(principalDerived.Name + ".ManyOwned#OwnedType", collectionOwnedType.Name); + Assert.Equal(typeof(OwnedType), collectionOwnedType.ClrType); + Assert.True(collectionOwnedType.HasSharedClrType); + Assert.False(collectionOwnedType.IsPropertyBag); + Assert.True(collectionOwnedType.IsOwned()); + Assert.True(collectionOwnedType.IsMemoryOptimized()); + Assert.Null(collectionOwnedType[RelationalAnnotationNames.IsTableExcludedFromMigrations]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => collectionOwnedType.IsTableExcludedFromMigrations()).Message); + Assert.Null(collectionOwnedType.BaseType); + Assert.IsType(collectionOwnedType.ConstructorBinding); + Assert.Equal(ChangeTrackingStrategy.Snapshot, collectionOwnedType.GetChangeTrackingStrategy()); + + var collectionOwnership = ownedCollectionNavigation.ForeignKey; + Assert.Same(collectionOwnership, collectionOwnedType.FindOwnership()); + Assert.True(collectionOwnership.IsOwnership); + Assert.True(collectionOwnership.IsRequired); + Assert.False(collectionOwnership.IsRequiredDependent); + Assert.False(collectionOwnership.IsUnique); + Assert.Null(collectionOwnership.DependentToPrincipal); + Assert.Same(ownedCollectionNavigation, collectionOwnership.PrincipalToDependent); + Assert.Equal(DeleteBehavior.Cascade, collectionOwnership.DeleteBehavior); + Assert.Equal(2, collectionOwnership.Properties.Count()); + + var derivedSkipNavigation = principalDerived.GetDeclaredSkipNavigations().Single(); + Assert.Equal("Principals", derivedSkipNavigation.Name); + Assert.Equal("Principals", derivedSkipNavigation.PropertyInfo.Name); + Assert.Equal("k__BackingField", derivedSkipNavigation.FieldInfo.Name); + Assert.Equal(typeof(ICollection), derivedSkipNavigation.ClrType); + Assert.True(derivedSkipNavigation.IsCollection); + Assert.True(derivedSkipNavigation.IsEagerLoaded); + Assert.False(derivedSkipNavigation.IsOnDependent); + Assert.Equal(principalDerived, derivedSkipNavigation.DeclaringEntityType); + Assert.Equal("Deriveds", derivedSkipNavigation.Inverse.Name); + Assert.Same(principalBase.GetSkipNavigations().Single(), derivedSkipNavigation.Inverse); + + Assert.Same(derivedSkipNavigation, derivedSkipNavigation.ForeignKey.GetReferencingSkipNavigations().Single()); + Assert.Same(derivedSkipNavigation.Inverse, derivedSkipNavigation.Inverse.ForeignKey.GetReferencingSkipNavigations().Single()); + + Assert.Equal(new[] { derivedSkipNavigation.Inverse, derivedSkipNavigation }, principalDerived.GetSkipNavigations()); + + var joinType = derivedSkipNavigation.JoinEntityType; + + Assert.Equal("PrincipalBasePrincipalDerived>", joinType.Name); + Assert.Equal(typeof(Dictionary), joinType.ClrType); + Assert.True(joinType.HasSharedClrType); + Assert.True(joinType.IsPropertyBag); + Assert.False(joinType.IsOwned()); + Assert.Null(joinType.BaseType); + Assert.IsType(joinType.ConstructorBinding); + Assert.Equal("Item", joinType.FindIndexerPropertyInfo().Name); + Assert.Equal(ChangeTrackingStrategy.Snapshot, joinType.GetChangeTrackingStrategy()); + Assert.Null(joinType[RelationalAnnotationNames.Comment]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => joinType.GetComment()).Message); + Assert.Null(joinType.GetQueryFilter()); + + var rowid = joinType.GetProperties().Single(p => !p.IsForeignKey()); + Assert.Equal(typeof(byte[]), rowid.ClrType); + Assert.True(rowid.IsIndexerProperty()); + Assert.Same(joinType.FindIndexerPropertyInfo(), rowid.PropertyInfo); + Assert.Null(rowid.FieldInfo); + Assert.True(rowid.IsNullable); + Assert.False(rowid.IsShadowProperty()); + Assert.True(rowid.IsConcurrencyToken); + Assert.Equal(ValueGenerated.OnAddOrUpdate, rowid.ValueGenerated); + Assert.Equal("rowid", rowid.GetColumnBaseName()); + Assert.Equal("rowversion", rowid.GetColumnType()); + Assert.Null(rowid[RelationalAnnotationNames.Comment]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => rowid.GetComment()).Message); + Assert.Null(rowid[RelationalAnnotationNames.Collation]); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => rowid.GetCollation()).Message); + Assert.Null(rowid.GetValueConverter()); + Assert.NotNull(rowid.GetValueComparer()); + Assert.NotNull(rowid.GetKeyValueComparer()); + Assert.Equal(SqlServerValueGenerationStrategy.None, rowid.GetValueGenerationStrategy()); + + var dependentForeignKey = dependentNavigation.ForeignKey; + Assert.False(dependentForeignKey.IsOwnership); + Assert.True(dependentForeignKey.IsRequired); + Assert.False(dependentForeignKey.IsRequiredDependent); + Assert.True(dependentForeignKey.IsUnique); + Assert.Same(dependentNavigation.Inverse, dependentForeignKey.DependentToPrincipal); + Assert.Same(dependentNavigation, dependentForeignKey.PrincipalToDependent); + Assert.Equal(DeleteBehavior.ClientNoAction, dependentForeignKey.DeleteBehavior); + Assert.Equal(new[] { "PrincipalId", "PrincipalAlternateId" }, dependentForeignKey.Properties.Select(p => p.Name)); + Assert.Same(principalKey, dependentForeignKey.PrincipalKey); + + var dependentBase = dependentNavigation.TargetEntityType; + + Assert.True(dependentBase.GetIsDiscriminatorMappingComplete()); + var principalDiscriminator = dependentBase.FindDiscriminatorProperty(); + Assert.IsType( + principalDiscriminator.GetValueGeneratorFactory()(principalDiscriminator, dependentBase)); + Assert.Equal(Discriminator.Base, dependentBase.GetDiscriminatorValue()); + + var dependentBaseForeignKey = dependentBase.GetForeignKeys().Single(fk => fk != dependentForeignKey); + var dependentForeignKeyProperty = dependentBaseForeignKey.Properties.Single(); + + Assert.Equal(new[] { dependentBaseForeignKey, dependentForeignKey }, dependentForeignKeyProperty.GetContainingForeignKeys()); + + var dependentDerived = dependentBase.GetDerivedTypes().Single(); + Assert.Equal(Discriminator.Derived, dependentDerived.GetDiscriminatorValue()); + + Assert.Equal(2, dependentDerived.GetDeclaredProperties().Count()); + + var dependentData = dependentDerived.GetDeclaredProperties().First(); + Assert.Equal(typeof(string), dependentData.ClrType); + Assert.Equal("Data", dependentData.Name); + Assert.Equal("Data", dependentData.PropertyInfo.Name); + Assert.Equal("k__BackingField", dependentData.FieldInfo.Name); + Assert.True(dependentData.IsNullable); + Assert.False(dependentData.IsShadowProperty()); + Assert.False(dependentData.IsConcurrencyToken); + Assert.Equal(ValueGenerated.Never, dependentData.ValueGenerated); + Assert.Equal("Data", dependentData.GetColumnBaseName()); + Assert.Equal("char(20)", dependentData.GetColumnType()); + Assert.Equal(20, dependentData.GetMaxLength()); + Assert.False(dependentData.IsUnicode()); + Assert.True(dependentData.IsFixedLength()); + Assert.Null(dependentData.GetPrecision()); + Assert.Null(dependentData.GetScale()); + + var dependentMoney = dependentDerived.GetDeclaredProperties().Last(); + Assert.Equal(typeof(decimal), dependentMoney.ClrType); + Assert.Equal("Money", dependentMoney.Name); + Assert.Null(dependentMoney.PropertyInfo); + Assert.Null(dependentMoney.FieldInfo); + Assert.False(dependentMoney.IsNullable); + Assert.True(dependentMoney.IsShadowProperty()); + Assert.False(dependentMoney.IsConcurrencyToken); + Assert.Equal(ValueGenerated.Never, dependentMoney.ValueGenerated); + Assert.Equal("Money", dependentMoney.GetColumnBaseName()); + Assert.Equal("decimal(9,3)", dependentMoney.GetColumnType()); + Assert.Null(dependentMoney.GetMaxLength()); + Assert.Null(dependentMoney.IsUnicode()); + Assert.Null(dependentMoney.IsFixedLength()); + Assert.Equal(9, dependentMoney.GetPrecision()); + Assert.Equal(3, dependentMoney.GetScale()); + + Assert.Equal(new[] + { + derivedSkipNavigation.ForeignKey, + tptForeignKey, + referenceOwnership, + collectionOwnership, + dependentForeignKey, + derivedSkipNavigation.Inverse.ForeignKey + }, + principalKey.GetReferencingForeignKeys()); + + Assert.Equal(new[] + { + dependentBaseForeignKey, + tptForeignKey, + referenceOwnership, + derivedSkipNavigation.Inverse.ForeignKey + }, + principalBase.GetReferencingForeignKeys()); + + Assert.Equal(new[] + { + derivedSkipNavigation.ForeignKey, + collectionOwnership, + dependentForeignKey + }, + principalDerived.GetDeclaredReferencingForeignKeys()); + + Assert.Equal(new[] + { + dependentBase, + dependentDerived, + principalBase, + referenceOwnedType, + principalDerived, + collectionOwnedType, + joinType + }, + model.GetEntityTypes()); + }, + typeof(SqlServerNetTopologySuiteDesignTimeServices)); + } + + public class BigContext : SqlServerContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.HasDatabaseMaxSize("20TB") + .HasPerformanceLevel("High") + .HasServiceTier("AB") + .UseCollation("pi-PI") + .UseIdentityColumns(3, 2); + + modelBuilder.Entity(eb => + { + eb.Property(e => e.Id).UseIdentityColumn(2, 3).IsSparse() + .Metadata.SetColumnName("DerivedId", StoreObjectIdentifier.Table("PrincipalDerived")); + eb.Property(e => e.AlternateId) + .IsRequired() + .UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction) + .HasColumnType("geometry") + .HasDefaultValue(NtsGeometryServices.Instance.CreateGeometryFactory(srid: 0).CreatePoint(new CoordinateZM(0, 0, 0, 0))) + .HasConversion, CustomValueComparer>(); + + eb.HasIndex(e => e.AlternateId, "AlternateIndex") + .IsUnique() + .HasDatabaseName("AIX") + .HasFilter("AlternateId <> NULL") + .IsClustered() + .IsCreatedOnline() + .HasFillFactor(40) + .IncludeProperties(e => e.Id); + + eb.HasIndex(e => new { e.AlternateId, e.Id}); + + eb.HasKey(e => new { e.Id, e.AlternateId }) + .HasName("PK") + .IsClustered(); + + eb.HasAlternateKey(e => e.Id); + + eb.OwnsOne(e => e.Owned, ob => + { + ob.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); + ob.UsePropertyAccessMode(PropertyAccessMode.Field); + }); + + eb.Navigation(e => e.Owned).IsRequired().HasField("_ownedField") + .UsePropertyAccessMode(PropertyAccessMode.Field); + + eb.HasData(new PrincipalBase { Id = 1, AlternateId = new Point(0, 0) }); + + eb.ToTable("PrincipalBase", "mySchema"); + }); + + modelBuilder.Entity>>(eb => + { + eb.HasOne(e => e.Dependent).WithOne(e => e.Principal) + .HasForeignKey>() + .OnDelete(DeleteBehavior.ClientNoAction); + + eb.Navigation(e => e.Dependent).AutoInclude(); + + eb.OwnsMany(typeof(OwnedType).FullName, "ManyOwned", ob => + { + ob.IsMemoryOptimized(); + ob.ToTable("ManyOwned", excludedFromMigrations: true); + }); + + eb.HasMany(e => e.Principals).WithMany(e => (ICollection>>)e.Deriveds) + .UsingEntity(jb => + { + jb.HasComment("Join table"); + jb.Property("rowid") + .IsRowVersion() + .HasComment("RowVersion") + .UseCollation("ri"); + }); + + eb.Navigation(e => e.Principals).AutoInclude(); + + eb.ToTable("PrincipalDerived"); + }); + + modelBuilder.Entity>(eb => + { + eb.Property("Id"); + + eb.HasKey(new[] { "PrincipalId", "PrincipalAlternateId" }); + + eb.HasOne().WithOne() + .HasForeignKey>("PrincipalId") + .HasPrincipalKey(e => e.Id); + + eb.ToTable("PrincipalDerived"); + + eb.HasDiscriminator("EnumDiscriminator") + .HasValue(Discriminator.Base) + .HasValue>(Discriminator.Derived); + }); + + modelBuilder.Entity>(eb => + { + eb.Property("Data") + .HasMaxLength(20) + .IsFixedLength() + .IsUnicode(false); + + eb.Property("Money") + .HasPrecision(9, 3); + }); + } + } + + public class CustomValueComparer : ValueComparer + { + public CustomValueComparer() + : base(false) + { + } + } + + public abstract class AbstractBase + { + public int Id { get; set; } + } + + public class PrincipalBase : AbstractBase + { + public new long? Id { get; set; } + public Point AlternateId; + + private OwnedType _ownedField; + public OwnedType Owned { get => _ownedField; set => _ownedField = value; } + public ICollection Deriveds { get; set; } + } + + public class PrincipalDerived : PrincipalBase + { + public TDependent Dependent { get; set; } + protected ICollection ManyOwned; + public ICollection Principals { get; set; } + } + + public class DependentBase : AbstractBase + { + private new TKey Id { get; set; } + public PrincipalDerived> Principal { get; set; } + } + + public class DependentDerived : DependentBase + { + private string Data { get; set; } + } + + public enum Discriminator + { + Base, + Derived + } + + public class OwnedType : INotifyPropertyChanged, INotifyPropertyChanging + { + private DbContext _context; + + public OwnedType(DbContext context) + { + Context = context; + } + + public DbContext Context + { + get => _context; set + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Context")); + _context = value; + PropertyChanging?.Invoke(this, new PropertyChangingEventArgs("Context")); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangingEventHandler PropertyChanging; + } + + [ConditionalFact] + public void DbFunctions() + { + Test( + new DbFunctionContext(), + new CompiledModelCodeGenerationOptions(), + code => Assert.Collection(code, + c => AssertFileContents("DbFunctionContextModel.cs", + @"// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + [DbContext(typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DbFunctionContext))] + partial class DbFunctionContextModel : RuntimeModel + { + private static DbFunctionContextModel _instance; + public static IModel Instance + { + get + { + if (_instance == null) + { + _instance = new DbFunctionContextModel(); + _instance.Initialize(); + } + + return _instance; + } + } + + protected override void Initialize() + { + var data = DataEntityType.Create(this, null); + var @object = ObjectEntityType.Create(this, null); + + DataEntityType.CreateAnnotations(data); + ObjectEntityType.CreateAnnotations(@object); + + var functions = new SortedDictionary(); + var getBlobs = new RuntimeDbFunction( + ""GetBlobs()"", + this, + typeof(IQueryable), + ""GetBlobs"", + schema: ""dbo""); + + functions[""GetBlobs()""] = getBlobs; + + var getCount = new RuntimeDbFunction( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.GetCount(System.Guid?,string)"", + this, + typeof(int), + ""CustomerOrderCount"", + schema: ""dbf"", + storeType: ""int"", + methodInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DbFunctionContext).GetMethod( + ""GetCount"", + BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly, + null, + new Type[] { typeof(Guid?), typeof(string) }, + null), + scalar: true); + + var id = getCount.AddParameter( + ""id"", + typeof(Guid?), + true, + ""uniqueidentifier""); + id.AddAnnotation(""MyAnnotation"", new[] { 1L }); + + var condition = getCount.AddParameter( + ""condition"", + typeof(string), + false, + ""nvarchar(max)""); + + functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.GetCount(System.Guid?,string)""] = getCount; + + var getData = new RuntimeDbFunction( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.GetData()"", + this, + typeof(IQueryable), + ""GetAllData"", + schema: ""dbo"", + methodInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DbFunctionContext).GetMethod( + ""GetData"", + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, + null, + new Type[] { }, + null)); + + functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.GetData()""] = getData; + + var getData0 = new RuntimeDbFunction( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.GetData(int)"", + this, + typeof(IQueryable), + ""GetData"", + schema: ""dbo"", + methodInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DbFunctionContext).GetMethod( + ""GetData"", + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, + null, + new Type[] { typeof(int) }, + null)); + + var id0 = getData0.AddParameter( + ""id"", + typeof(int), + false, + ""int""); + + functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.GetData(int)""] = getData0; + + var isDateStatic = new RuntimeDbFunction( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.IsDateStatic(string)"", + this, + typeof(bool), + ""IsDate"", + storeType: ""bit"", + methodInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DbFunctionContext).GetMethod( + ""IsDateStatic"", + BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly, + null, + new Type[] { typeof(string) }, + null), + scalar: true, + nullable: true, + builtIn: true); + + var date = isDateStatic.AddParameter( + ""date"", + typeof(string), + false, + ""nvarchar(max)""); + + isDateStatic.AddAnnotation(""MyGuid"", new Guid(""00000000-0000-0000-0000-000000000000"")); + functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.IsDateStatic(string)""] = isDateStatic; + + AddAnnotation(""Relational:DbFunctions"", functions); + AddAnnotation(""Relational:MaxIdentifierLength"", 128); + AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + + Customize(); + } + + partial void Customize(); + } +} +", + c), + c => AssertFileContents("DataEntityType.cs", + @"// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class DataEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+Data"", + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data), + baseEntityType); + + var blob = runtimeEntityType.AddProperty( + ""Blob"", + typeof(byte[]), + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + blob.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.GetData()""); + runtimeEntityType.AddAnnotation(""Relational:Schema"", null); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", + c), + c => AssertFileContents("ObjectEntityType.cs", + @"// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class ObjectEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + { + var runtimeEntityType = model.AddEntityType( + ""object"", + typeof(object), + baseEntityType); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", ""GetBlobs()""); + runtimeEntityType.AddAnnotation(""Relational:Schema"", null); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", + c)), + model => + { + Assert.Equal(5, model.GetDbFunctions().Count()); + + var getCount = model.FindDbFunction(typeof(DbFunctionContext) + .GetMethod("GetCount", BindingFlags.NonPublic | BindingFlags.Instance)); + Assert.Equal("CustomerOrderCount", getCount.Name); + Assert.Same(model, getCount.Model); + Assert.Same(model, ((IReadOnlyDbFunction)getCount).Model); + Assert.Equal(typeof(DbFunctionContext).FullName + ".GetCount(System.Guid?,string)", getCount.ModelName); + Assert.Equal("dbf", getCount.Schema); + Assert.False(getCount.IsNullable); + Assert.True(getCount.IsScalar); + Assert.False(getCount.IsBuiltIn); + Assert.False(getCount.IsAggregate); + Assert.Null(getCount.Translation); + Assert.Equal("int", getCount.TypeMapping?.StoreType); + Assert.Equal(typeof(int), getCount.ReturnType); + Assert.Equal("GetCount", getCount.MethodInfo.Name); + Assert.Empty(getCount.GetAnnotations()); + Assert.Empty(getCount.GetRuntimeAnnotations()); + Assert.Equal("CustomerOrderCount", getCount.StoreFunction.Name); + Assert.False(getCount.StoreFunction.IsShared); + Assert.NotNull(getCount.ToString()); + Assert.Equal(getCount.Parameters, ((IReadOnlyDbFunction)getCount).Parameters); + Assert.Equal(2, getCount.Parameters.Count); + + var getCountParameter1 = getCount.Parameters[0]; + Assert.Same(getCount, getCountParameter1.Function); + Assert.Same(getCount, ((IReadOnlyDbFunctionParameter)getCountParameter1).Function); + Assert.Equal("id", getCountParameter1.Name); + Assert.Equal("uniqueidentifier", getCountParameter1.StoreType); + Assert.Equal("uniqueidentifier", ((IReadOnlyDbFunctionParameter)getCountParameter1).StoreType); + Assert.True(getCountParameter1.PropagatesNullability); + Assert.Equal(typeof(Guid?), getCountParameter1.ClrType); + Assert.Equal("uniqueidentifier", getCountParameter1.TypeMapping.StoreType); + Assert.Single(getCountParameter1.GetAnnotations()); + Assert.Equal(new[] { 1L }, getCountParameter1["MyAnnotation"]); + Assert.Equal("id", getCountParameter1.StoreFunctionParameter.Name); + Assert.Equal("uniqueidentifier", getCountParameter1.StoreFunctionParameter.Type); + Assert.NotNull(getCountParameter1.ToString()); + + var getCountParameter2 = getCount.Parameters[1]; + Assert.Same(getCount, getCountParameter2.Function); + Assert.Equal("condition", getCountParameter2.Name); + Assert.Equal("nvarchar(max)", getCountParameter2.StoreType); + Assert.False(getCountParameter2.PropagatesNullability); + Assert.Equal(typeof(string), getCountParameter2.ClrType); + Assert.Equal("nvarchar(max)", getCountParameter2.TypeMapping.StoreType); + Assert.Equal("condition", getCountParameter2.StoreFunctionParameter.Name); + Assert.Equal("nvarchar(max)", getCountParameter2.StoreFunctionParameter.Type); + Assert.NotNull(getCountParameter2.ToString()); + + var isDate = model.FindDbFunction(typeof(DbFunctionContext).GetMethod("IsDateStatic")); + Assert.Equal("IsDate", isDate.Name); + Assert.Null(isDate.Schema); + Assert.Equal(typeof(DbFunctionContext).FullName + ".IsDateStatic(string)", isDate.ModelName); + Assert.True(isDate.IsNullable); + Assert.True(isDate.IsScalar); + Assert.True(isDate.IsBuiltIn); + Assert.False(isDate.IsAggregate); + Assert.Null(isDate.Translation); + Assert.Equal(typeof(bool), isDate.ReturnType); + Assert.Equal("IsDateStatic", isDate.MethodInfo.Name); + Assert.Single(isDate.GetAnnotations()); + Assert.Equal(new Guid(), isDate["MyGuid"]); + Assert.Empty(isDate.GetRuntimeAnnotations()); + Assert.Equal("bit", isDate.StoreFunction.ReturnType); + Assert.Empty(isDate.StoreFunction.EntityTypeMappings); + Assert.Single(isDate.Parameters); + + var isDateParameter = isDate.Parameters[0]; + Assert.Same(isDate, isDateParameter.Function); + Assert.Equal("date", isDateParameter.Name); + Assert.Equal("nvarchar(max)", isDateParameter.StoreType); + Assert.False(isDateParameter.PropagatesNullability); + Assert.Equal(typeof(string), isDateParameter.ClrType); + Assert.Equal("nvarchar(max)", isDateParameter.TypeMapping.StoreType); + Assert.Equal("date", isDateParameter.StoreFunctionParameter.Name); + Assert.Equal("nvarchar(max)", isDateParameter.StoreFunctionParameter.Type); + + var getData = model.FindDbFunction(typeof(DbFunctionContext) + .GetMethod("GetData", new Type[] { typeof(int) })); + Assert.Equal("GetData", getData.Name); + Assert.Equal("dbo", getData.Schema); + Assert.Equal(typeof(DbFunctionContext).FullName + ".GetData(int)", getData.ModelName); + Assert.False(getData.IsNullable); + Assert.False(getData.IsScalar); + Assert.False(getData.IsBuiltIn); + Assert.False(getData.IsAggregate); + Assert.Null(getData.Translation); + Assert.Equal(typeof(IQueryable), getData.ReturnType); + Assert.Equal("GetData", getData.MethodInfo.Name); + Assert.Empty(getData.GetAnnotations()); + Assert.Empty(getData.GetRuntimeAnnotations()); + Assert.Null(getData.TypeMapping?.StoreType); + Assert.Null(getData.StoreFunction.ReturnType); + Assert.Equal(typeof(Data), getData.StoreFunction.EntityTypeMappings.Single().EntityType.ClrType); + Assert.Single(getData.Parameters); + + var getDataParameter = getData.Parameters[0]; + Assert.Same(getData, getDataParameter.Function); + Assert.Equal("id", getDataParameter.Name); + Assert.Equal("int", getDataParameter.StoreType); + Assert.False(getDataParameter.PropagatesNullability); + Assert.Equal(typeof(int), getDataParameter.ClrType); + Assert.Equal("int", getDataParameter.TypeMapping.StoreType); + Assert.Equal("id", getDataParameter.StoreFunctionParameter.Name); + Assert.Equal("int", getDataParameter.StoreFunctionParameter.Type); + + var getDataParameterless = model.FindDbFunction(typeof(DbFunctionContext) + .GetMethod("GetData", new Type[0])); + Assert.Equal("GetAllData", getDataParameterless.Name); + Assert.Equal("dbo", getDataParameterless.Schema); + Assert.Equal(typeof(DbFunctionContext).FullName + ".GetData()", getDataParameterless.ModelName); + Assert.False(getDataParameterless.IsNullable); + Assert.False(getDataParameterless.IsScalar); + Assert.False(getDataParameterless.IsBuiltIn); + Assert.False(getDataParameterless.IsAggregate); + Assert.Null(getDataParameterless.Translation); + Assert.Equal(typeof(IQueryable), getDataParameterless.ReturnType); + Assert.Equal("GetData", getDataParameterless.MethodInfo.Name); + Assert.Empty(getDataParameterless.GetAnnotations()); + Assert.Empty(getDataParameterless.GetRuntimeAnnotations()); + Assert.False(getDataParameterless.StoreFunction.IsBuiltIn); + Assert.Equal(typeof(Data), getDataParameterless.StoreFunction.EntityTypeMappings.Single().EntityType.ClrType); + Assert.Equal(0, getDataParameterless.Parameters.Count); + + Assert.Equal(2, model.GetEntityTypes().Count()); + var dataEntity = model.FindEntityType(typeof(Data)); + Assert.Null(dataEntity.FindPrimaryKey()); + var dataEntityFunctionMapping = dataEntity.GetFunctionMappings().Single(m => m.IsDefaultFunctionMapping); + Assert.True(dataEntityFunctionMapping.IncludesDerivedTypes); + Assert.True(dataEntityFunctionMapping.IsSharedTablePrincipal); + Assert.True(dataEntityFunctionMapping.IsSplitEntityTypePrincipal); + Assert.Same(getDataParameterless, dataEntityFunctionMapping.DbFunction); + + var getDataStoreFunction = dataEntityFunctionMapping.StoreFunction; + Assert.Same(getDataParameterless, getDataStoreFunction.DbFunctions.Single()); + Assert.False(getDataStoreFunction.IsOptional(dataEntity)); + + var dataEntityOtherFunctionMapping = dataEntity.GetFunctionMappings().Single(m => !m.IsDefaultFunctionMapping); + Assert.True(dataEntityOtherFunctionMapping.IncludesDerivedTypes); + Assert.True(dataEntityOtherFunctionMapping.IsSharedTablePrincipal); + Assert.True(dataEntityOtherFunctionMapping.IsSplitEntityTypePrincipal); + Assert.Same(getData, dataEntityOtherFunctionMapping.DbFunction); + + var getDataOtherStoreFunction = dataEntityOtherFunctionMapping.StoreFunction; + Assert.Same(getData, getDataOtherStoreFunction.DbFunctions.Single()); + Assert.False(getDataOtherStoreFunction.IsOptional(dataEntity)); + + var getBlobs = model.FindDbFunction("GetBlobs()"); + Assert.Equal("dbo", getBlobs.Schema); + Assert.False(getBlobs.IsNullable); + Assert.False(getBlobs.IsScalar); + Assert.False(getBlobs.IsBuiltIn); + Assert.False(getBlobs.IsAggregate); + Assert.Null(getBlobs.Translation); + Assert.Null(getBlobs.TypeMapping); + Assert.Equal(typeof(IQueryable), getBlobs.ReturnType); + Assert.Null(getBlobs.MethodInfo); + Assert.Empty(getBlobs.GetAnnotations()); + Assert.Empty(getBlobs.GetRuntimeAnnotations()); + Assert.Equal("GetBlobs", getBlobs.StoreFunction.Name); + Assert.False(getBlobs.StoreFunction.IsShared); + Assert.NotNull(getBlobs.ToString()); + Assert.Empty(getBlobs.Parameters); + + var objectEntity = model.FindEntityType(typeof(object)); + Assert.Null(objectEntity.FindPrimaryKey()); + var objectEntityFunctionMapping = objectEntity.GetFunctionMappings().Single(m => m.IsDefaultFunctionMapping); + Assert.True(objectEntityFunctionMapping.IncludesDerivedTypes); + Assert.True(objectEntityFunctionMapping.IsSharedTablePrincipal); + Assert.True(objectEntityFunctionMapping.IsSplitEntityTypePrincipal); + Assert.Same(getBlobs, objectEntityFunctionMapping.DbFunction); + }); + } + + public class DbFunctionContext : SqlServerContextBase + { + public static bool IsDateStatic(string date) + => throw new NotImplementedException(); + + private int GetCount(Guid? id, string condition) + => throw new NotImplementedException(); + + public IQueryable GetData(int id) + { + return FromExpression(() => GetData(id)); + } + + public IQueryable GetData() + { + return FromExpression(() => GetData()); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.HasDbFunction(typeof(DbFunctionContext).GetMethod(nameof(GetCount), BindingFlags.NonPublic | BindingFlags.Instance)) + .HasName("CustomerOrderCount").HasSchema("dbf").IsNullable(false) + .HasParameter("id").PropagatesNullability().Metadata.SetAnnotation("MyAnnotation", new[] { 1L }); + + modelBuilder.HasDbFunction(typeof(DbFunctionContext).GetMethod(nameof(IsDateStatic))).HasName("IsDate").IsBuiltIn() + .Metadata.SetAnnotation("MyGuid", new Guid()); + + modelBuilder.HasDbFunction(typeof(DbFunctionContext).GetMethod(nameof(GetData), new Type[] { typeof(int) })); + modelBuilder.HasDbFunction(typeof(DbFunctionContext).GetMethod(nameof(GetData), new Type[0])); + + modelBuilder.Entity().ToFunction(typeof(DbFunctionContext).FullName + ".GetData()", f => f.HasName("GetAllData")) + .HasNoKey(); + + modelBuilder.Entity().ToFunction("GetBlobs()", f => f.HasName("GetBlobs")).HasNoKey(); + } + } + + [ConditionalFact] + public void Sequences() + { + Test( + new SequencesContext(), + new CompiledModelCodeGenerationOptions(), + code => Assert.Collection(code, + c => AssertFileContents("SequencesContextModel.cs", + @"// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + [DbContext(typeof(CSharpRuntimeAnnotationCodeGeneratorTest.SequencesContext))] + partial class SequencesContextModel : RuntimeModel + { + private static SequencesContextModel _instance; + public static IModel Instance + { + get + { + if (_instance == null) + { + _instance = new SequencesContextModel(); + _instance.Initialize(); + } + + return _instance; + } + } + + protected override void Initialize() + { + var data = DataEntityType.Create(this, null); + + DataEntityType.CreateAnnotations(data); + + var sequences = new SortedDictionary<(string, string), ISequence>(); + var hL = new RuntimeSequence( + ""HL"", + this, + typeof(long), + schema: ""S"", + incrementBy: 10); + + sequences[(""HL"", ""S"")] = hL; + + var @long = new RuntimeSequence( + ""Long"", + this, + typeof(long), + startValue: -4L, + incrementBy: 2, + cyclic: true, + minValue: -2L, + maxValue: 2L); + + sequences[(""Long"", null)] = @long; + + AddAnnotation(""Relational:Sequences"", sequences); + AddAnnotation(""Relational:MaxIdentifierLength"", 128); + AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + + Customize(); + } + + partial void Customize(); + } +} +", + c), + c => AssertFileContents("DataEntityType.cs", + @"// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class DataEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+Data"", + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data), + baseEntityType); + + var id = runtimeEntityType.AddProperty( + ""Id"", + typeof(int), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw); + id.AddAnnotation(""SqlServer:HiLoSequenceName"", ""HL""); + id.AddAnnotation(""SqlServer:HiLoSequenceSchema"", ""S""); + id.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.SequenceHiLo); + + var blob = runtimeEntityType.AddProperty( + ""Blob"", + typeof(byte[]), + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + blob.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var key = runtimeEntityType.AddKey( + new[] { id }); + runtimeEntityType.SetPrimaryKey(key); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); + runtimeEntityType.AddAnnotation(""Relational:Schema"", null); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", ""Data""); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", + c)), + model => + { + Assert.Equal(2, model.GetSequences().Count()); + + var longSequence = model.FindSequence("Long"); + Assert.Same(model, longSequence.Model); + Assert.Equal(typeof(long), longSequence.Type); + Assert.True(longSequence.IsCyclic); + Assert.Equal(-4, longSequence.StartValue); + Assert.Equal(-2, longSequence.MinValue); + Assert.Equal(2, longSequence.MaxValue); + Assert.Equal(2, longSequence.IncrementBy); + Assert.NotNull(longSequence.ToString()); + + var hiLo = model.FindSequence("HL", "S"); + Assert.Same(model, ((IReadOnlySequence)hiLo).Model); + Assert.Equal("HL", hiLo.Name); + Assert.Equal("S", hiLo.Schema); + Assert.False(hiLo.IsCyclic); + Assert.Equal(1, hiLo.StartValue); + Assert.Null(hiLo.MinValue); + Assert.Null(hiLo.MaxValue); + Assert.Equal(10, hiLo.IncrementBy); + Assert.NotNull(hiLo.ToString()); + + Assert.Single(model.GetEntityTypes()); + var dataEntity = model.FindEntityType(typeof(Data)); + Assert.Same(hiLo, dataEntity.FindPrimaryKey().Properties.Single().FindHiLoSequence()); + }); + } + + public class SequencesContext : SqlServerContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.HasSequence("Long") + .HasMin(-2) + .HasMax(2) + .IsCyclic() + .IncrementsBy(2) + .StartsAt(-4); + + modelBuilder.Entity(eb => + { + eb.Property("Id").UseHiLo("HL", "S"); + eb.HasKey("Id"); + }); + } + } + + [ConditionalFact] + public void CheckConstraints() + { + Test( + new ConstraintsContext(), + new CompiledModelCodeGenerationOptions(), + code => Assert.Collection(code, + c => AssertFileContents("ConstraintsContextModel.cs", + @"// +using System; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + [DbContext(typeof(CSharpRuntimeAnnotationCodeGeneratorTest.ConstraintsContext))] + partial class ConstraintsContextModel : RuntimeModel + { + private static ConstraintsContextModel _instance; + public static IModel Instance + { + get + { + if (_instance == null) + { + _instance = new ConstraintsContextModel(); + _instance.Initialize(); + } + + return _instance; + } + } + + protected override void Initialize() + { + var data = DataEntityType.Create(this, null); + + DataEntityType.CreateAnnotations(data); + + AddAnnotation(""Relational:MaxIdentifierLength"", 128); + AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + + Customize(); + } + + partial void Customize(); + } +} +", + c), + c => AssertFileContents("DataEntityType.cs", + @"// +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class DataEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+Data"", + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data), + baseEntityType); + + var id = runtimeEntityType.AddProperty( + ""Id"", + typeof(int), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw); + id.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + + var blob = runtimeEntityType.AddProperty( + ""Blob"", + typeof(byte[]), + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + blob.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var key = runtimeEntityType.AddKey( + new[] { id }); + runtimeEntityType.SetPrimaryKey(key); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + var constraints = new SortedDictionary(); + var anotherConstraint = new RuntimeCheckConstraint( + ""anotherConstraint"", + runtimeEntityType, + ""Id <> -1""); + + constraints[""anotherConstraint""] = anotherConstraint; + + var idConstraint = new RuntimeCheckConstraint( + ""idConstraint"", + runtimeEntityType, + ""Id <> 0""); + + constraints[""idConstraint""] = idConstraint; + + runtimeEntityType.AddAnnotation(""Relational:CheckConstraints"", constraints); + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); + runtimeEntityType.AddAnnotation(""Relational:Schema"", null); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", ""Data""); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", + c)), + model => + { + Assert.Single(model.GetEntityTypes()); + var dataEntity = model.FindEntityType(typeof(Data)); + + Assert.Equal(2, dataEntity.GetCheckConstraints().Count()); + var anotherConstraint = dataEntity.GetCheckConstraints().First(); + Assert.Same(dataEntity, anotherConstraint.EntityType); + Assert.Equal("anotherConstraint", anotherConstraint.Name); + Assert.Equal("Id <> -1", anotherConstraint.Sql); + Assert.NotNull(anotherConstraint.ToString()); + + var idConstraint = dataEntity.GetCheckConstraints().Last(); + Assert.Same(dataEntity, ((IReadOnlyCheckConstraint)idConstraint).EntityType); + Assert.Equal("idConstraint", idConstraint.Name); + Assert.Equal("Id <> 0", idConstraint.Sql); + Assert.NotNull(idConstraint.ToString()); + }); + } + + public class ConstraintsContext : SqlServerContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(eb => + { + eb.Property("Id"); + eb.HasKey("Id"); + + eb.HasCheckConstraint("idConstraint", "Id <> 0"); + eb.HasCheckConstraint("anotherConstraint", "Id <> -1"); + }); + } + } + + [ConditionalFact] + public void Sqlite() + { + Test( + new SqliteContext(), + new CompiledModelCodeGenerationOptions(), + code => Assert.Collection(code, + c => AssertFileContents("SqliteContextModel.cs", + @"// +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + [DbContext(typeof(CSharpRuntimeAnnotationCodeGeneratorTest.SqliteContext))] + partial class SqliteContextModel : RuntimeModel + { + private static SqliteContextModel _instance; + public static IModel Instance + { + get + { + if (_instance == null) + { + _instance = new SqliteContextModel(); + _instance.Initialize(); + } + + return _instance; + } + } + + protected override void Initialize() + { + var data = DataEntityType.Create(this, null); + + DataEntityType.CreateAnnotations(data); + + + Customize(); + } + + partial void Customize(); + } +} +", + c), + c => AssertFileContents("DataEntityType.cs", + @"// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; +using NetTopologySuite.Geometries; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class DataEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+Data"", + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data), + baseEntityType); + + var id = runtimeEntityType.AddProperty( + ""Id"", + typeof(int), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw); + + var blob = runtimeEntityType.AddProperty( + ""Blob"", + typeof(byte[]), + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + + var point = runtimeEntityType.AddProperty( + ""Point"", + typeof(Point), + nullable: true); + + var key = runtimeEntityType.AddKey( + new[] { id }); + runtimeEntityType.SetPrimaryKey(key); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); + runtimeEntityType.AddAnnotation(""Relational:Schema"", null); + runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); + runtimeEntityType.AddAnnotation(""Relational:TableName"", ""Data""); + runtimeEntityType.AddAnnotation(""Relational:ViewName"", null); + runtimeEntityType.AddAnnotation(""Relational:ViewSchema"", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", + c)), + model => + { + Assert.Single(model.GetEntityTypes()); + var dataEntity = model.FindEntityType(typeof(Data)); + + Assert.Equal(typeof(Data).FullName, dataEntity.Name); + Assert.False(dataEntity.HasSharedClrType); + Assert.False(dataEntity.IsPropertyBag); + Assert.False(dataEntity.IsOwned()); + Assert.IsType(dataEntity.ConstructorBinding); + Assert.Null(dataEntity.FindIndexerPropertyInfo()); + Assert.Equal(ChangeTrackingStrategy.Snapshot, dataEntity.GetChangeTrackingStrategy()); + Assert.Equal("Data", dataEntity.GetTableName()); + Assert.Null(dataEntity.GetSchema()); + + var point = dataEntity.FindProperty("Point"); + Assert.Equal(typeof(Point), point.ClrType); + Assert.True(point.IsNullable); + Assert.Equal(ValueGenerated.Never, point.ValueGenerated); + Assert.Equal("Point", point.GetColumnBaseName()); + Assert.Equal("POINT", point.GetColumnType()); + Assert.Null(point.GetValueConverter()); + Assert.IsType>(point.GetValueComparer()); + Assert.IsType>(point.GetKeyValueComparer()); + Assert.Null(point.GetSrid()); + }, + typeof(SqliteNetTopologySuiteDesignTimeServices)); + } + + public class SqliteContext : DbContext + { + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options + .EnableServiceProviderCaching(false) + .UseSqlite(o => o.UseNetTopologySuite()); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); + + modelBuilder.Entity(eb => + { + eb.Property("Id"); + eb.HasKey("Id"); + + eb.Property("Point") + .HasSrid(1101); + }); + } + } + + [ConditionalFact] + public void Cosmos() + { + Test( + new CosmosContext(), + new CompiledModelCodeGenerationOptions(), + code => Assert.Collection(code, + c => AssertFileContents("CosmosContextModel.cs", + @"// +using System; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + [DbContext(typeof(CSharpRuntimeAnnotationCodeGeneratorTest.CosmosContext))] + partial class CosmosContextModel : RuntimeModel + { + private static CosmosContextModel _instance; + public static IModel Instance + { + get + { + if (_instance == null) + { + _instance = new CosmosContextModel(); + _instance.Initialize(); + } + + return _instance; + } + } + + protected override void Initialize() + { + var data = DataEntityType.Create(this, null); + + DataEntityType.CreateAnnotations(data); + + AddAnnotation(""Cosmos:ContainerName"", ""Default""); + + Customize(); + } + + partial void Customize(); + } +} +", + c), + c => AssertFileContents("DataEntityType.cs", + @"// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; +using Newtonsoft.Json.Linq; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class DataEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + { + var runtimeEntityType = model.AddEntityType( + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+Data"", + typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data), + baseEntityType); + + var id = runtimeEntityType.AddProperty( + ""Id"", + typeof(int), + afterSaveBehavior: PropertySaveBehavior.Throw); + + var partitionId = runtimeEntityType.AddProperty( + ""PartitionId"", + typeof(long?), + afterSaveBehavior: PropertySaveBehavior.Throw, + providerPropertyType: typeof(string)); + + var blob = runtimeEntityType.AddProperty( + ""Blob"", + typeof(byte[]), + propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + blob.AddAnnotation(""Cosmos:PropertyName"", ""JsonBlob""); + + var __id = runtimeEntityType.AddProperty( + ""__id"", + typeof(string), + afterSaveBehavior: PropertySaveBehavior.Throw, + valueGeneratorFactory: new IdValueGeneratorFactory().Create); + __id.AddAnnotation(""Cosmos:PropertyName"", ""id""); + + var __jObject = runtimeEntityType.AddProperty( + ""__jObject"", + typeof(JObject), + nullable: true, + valueGenerated: ValueGenerated.OnAddOrUpdate, + beforeSaveBehavior: PropertySaveBehavior.Ignore, + afterSaveBehavior: PropertySaveBehavior.Ignore); + __jObject.AddAnnotation(""Cosmos:PropertyName"", """"); + + var _etag = runtimeEntityType.AddProperty( + ""_etag"", + typeof(string), + nullable: true, + concurrencyToken: true, + valueGenerated: ValueGenerated.OnAddOrUpdate, + beforeSaveBehavior: PropertySaveBehavior.Ignore, + afterSaveBehavior: PropertySaveBehavior.Ignore); + + var key = runtimeEntityType.AddKey( + new[] { id, partitionId }); + runtimeEntityType.SetPrimaryKey(key); + + var key0 = runtimeEntityType.AddKey( + new[] { __id, partitionId }); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation(""Cosmos:ContainerName"", ""DataContainer""); + runtimeEntityType.AddAnnotation(""Cosmos:ETagName"", ""_etag""); + runtimeEntityType.AddAnnotation(""Cosmos:PartitionKeyName"", ""PartitionId""); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +", + c)), + model => + { + Assert.Single(model.GetEntityTypes()); + var dataEntity = model.FindEntityType(typeof(Data)); + Assert.Equal(typeof(Data).FullName, dataEntity.Name); + Assert.False(dataEntity.HasSharedClrType); + Assert.False(dataEntity.IsPropertyBag); + Assert.False(dataEntity.IsOwned()); + Assert.IsType(dataEntity.ConstructorBinding); + Assert.Null(dataEntity.FindIndexerPropertyInfo()); + Assert.Equal(ChangeTrackingStrategy.Snapshot, dataEntity.GetChangeTrackingStrategy()); + Assert.Equal("DataContainer", dataEntity.GetContainer()); + Assert.Null(dataEntity.FindDiscriminatorProperty()); + + var id = dataEntity.FindProperty("Id"); + Assert.Equal(typeof(int), id.ClrType); + Assert.Null(id.PropertyInfo); + Assert.Null(id.FieldInfo); + Assert.False(id.IsNullable); + Assert.False(id.IsConcurrencyToken); + Assert.Equal(ValueGenerated.Never, id.ValueGenerated); + Assert.Equal(PropertySaveBehavior.Throw, id.GetAfterSaveBehavior()); + Assert.Equal(PropertySaveBehavior.Save, id.GetBeforeSaveBehavior()); + Assert.Equal("Id", id.GetJsonPropertyName()); + Assert.Null(id.GetValueGeneratorFactory()); + Assert.Null(id.GetValueConverter()); + Assert.NotNull(id.GetValueComparer()); + Assert.NotNull(id.GetKeyValueComparer()); + + var storeId = dataEntity.FindProperty("__id"); + Assert.Equal(typeof(string), storeId.ClrType); + Assert.Null(storeId.PropertyInfo); + Assert.Null(storeId.FieldInfo); + Assert.False(storeId.IsNullable); + Assert.False(storeId.IsConcurrencyToken); + Assert.Equal(ValueGenerated.Never, storeId.ValueGenerated); + Assert.Equal(PropertySaveBehavior.Throw, storeId.GetAfterSaveBehavior()); + Assert.Equal(PropertySaveBehavior.Save, storeId.GetBeforeSaveBehavior()); + Assert.Equal("id", storeId.GetJsonPropertyName()); + Assert.IsType(storeId.GetValueGeneratorFactory()(storeId, dataEntity)); + Assert.Null(storeId.GetValueConverter()); + Assert.NotNull(storeId.GetValueComparer()); + Assert.NotNull(storeId.GetKeyValueComparer()); + + var partitionId = dataEntity.FindProperty("PartitionId"); + Assert.Equal(typeof(long?), partitionId.ClrType); + Assert.Null(partitionId.PropertyInfo); + Assert.Null(partitionId.FieldInfo); + Assert.False(partitionId.IsNullable); + Assert.False(partitionId.IsConcurrencyToken); + Assert.Equal(ValueGenerated.Never, partitionId.ValueGenerated); + Assert.Equal(PropertySaveBehavior.Throw, partitionId.GetAfterSaveBehavior()); + Assert.Equal(PropertySaveBehavior.Save, partitionId.GetBeforeSaveBehavior()); + Assert.Equal("PartitionId", partitionId.GetJsonPropertyName()); + Assert.Null(partitionId.GetValueGeneratorFactory()); + Assert.Null(partitionId.GetValueConverter()); + Assert.IsType>(partitionId.FindTypeMapping().Converter); + Assert.NotNull(partitionId.GetValueComparer()); + Assert.NotNull(partitionId.GetKeyValueComparer()); + + var eTag = dataEntity.FindProperty("_etag"); + Assert.Equal(typeof(string), eTag.ClrType); + Assert.Null(eTag.PropertyInfo); + Assert.Null(eTag.FieldInfo); + Assert.True(eTag.IsNullable); + Assert.True(eTag.IsConcurrencyToken); + Assert.Equal(ValueGenerated.OnAddOrUpdate, eTag.ValueGenerated); + Assert.Equal(PropertySaveBehavior.Ignore, eTag.GetAfterSaveBehavior()); + Assert.Equal(PropertySaveBehavior.Ignore, eTag.GetBeforeSaveBehavior()); + Assert.Equal("_etag", eTag.GetJsonPropertyName()); + Assert.Null(eTag.GetValueGeneratorFactory()); + Assert.Null(eTag.GetValueConverter()); + Assert.NotNull(eTag.GetValueComparer()); + Assert.NotNull(eTag.GetKeyValueComparer()); + Assert.Equal("_etag", dataEntity.GetETagPropertyName()); + Assert.Same(eTag, dataEntity.GetETagProperty()); + + var blob = dataEntity.FindProperty(nameof(Data.Blob)); + Assert.Equal(typeof(byte[]), blob.ClrType); + Assert.Equal(nameof(Data.Blob), blob.PropertyInfo.Name); + Assert.Equal("k__BackingField", blob.FieldInfo.Name); + Assert.True(blob.IsNullable); + Assert.False(blob.IsConcurrencyToken); + Assert.Equal(ValueGenerated.Never, blob.ValueGenerated); + Assert.Equal(PropertySaveBehavior.Save, blob.GetAfterSaveBehavior()); + Assert.Equal(PropertySaveBehavior.Save, blob.GetBeforeSaveBehavior()); + Assert.Equal("JsonBlob", blob.GetJsonPropertyName()); + Assert.Null(blob.GetValueGeneratorFactory()); + Assert.Null(blob.GetValueConverter()); + Assert.NotNull(blob.GetValueComparer()); + Assert.NotNull(blob.GetKeyValueComparer()); + + var jObject = dataEntity.FindProperty("__jObject"); + Assert.Equal(typeof(JObject), jObject.ClrType); + Assert.Null(jObject.PropertyInfo); + Assert.Null(jObject.FieldInfo); + Assert.True(jObject.IsNullable); + Assert.False(jObject.IsConcurrencyToken); + Assert.Equal(ValueGenerated.OnAddOrUpdate, jObject.ValueGenerated); + Assert.Equal(PropertySaveBehavior.Ignore, jObject.GetAfterSaveBehavior()); + Assert.Equal(PropertySaveBehavior.Ignore, jObject.GetBeforeSaveBehavior()); + Assert.Equal("", jObject.GetJsonPropertyName()); + Assert.Null(jObject.GetValueGeneratorFactory()); + Assert.Null(jObject.GetValueConverter()); + Assert.NotNull(jObject.GetValueComparer()); + Assert.NotNull(jObject.GetKeyValueComparer()); + + Assert.Equal(2, dataEntity.GetKeys().Count()); + + Assert.Equal(new[] { id, partitionId, blob, storeId, jObject, eTag }, dataEntity.GetProperties()); + }); + } + + public class CosmosContext : DbContext + { + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options + .EnableServiceProviderCaching(false) + .UseCosmos("localhost", "_", "_"); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); + + modelBuilder.HasDefaultContainer("Default"); + + modelBuilder.Entity(eb => + { + eb.Property("Id"); + eb.Property("PartitionId").HasConversion(); + eb.HasPartitionKey("PartitionId"); + eb.HasKey("Id", "PartitionId"); + eb.ToContainer("DataContainer"); + eb.UseETagConcurrency(); + eb.HasNoDiscriminator(); + eb.Property(d => d.Blob).ToJsonProperty("JsonBlob"); + }); + } + } + + public class Data + { + public byte[] Blob { get; set; } + } + + public abstract class ContextBase : DbContext + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); + } + + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options + .EnableServiceProviderCaching(false) + .UseInMemoryDatabase(nameof(CSharpRuntimeAnnotationCodeGeneratorTest)); + } + + public abstract class SqlServerContextBase : DbContext + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); + } + + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options + .EnableServiceProviderCaching(false) + .UseSqlServer(o => o.UseNetTopologySuite()); + } + + protected void Test( + DbContext context, + CompiledModelCodeGenerationOptions options, + Action> assertScaffold = null, + Action assertModel = null, + Type additionalDesignTimeServices = null, + string expectedExceptionMessage = null) + { + var model = context.GetService().Model; + + var services = new ServiceCollection(); + if (additionalDesignTimeServices != null) + { + ConfigureDesignTimeServices(additionalDesignTimeServices, services); + } + ConfigureProviderServices(context.GetService().Name, services); + services.AddEntityFrameworkDesignTimeServices(); + + options.ModelNamespace ??= "TestNamespace"; + options.ContextType = context.GetType(); + + var generator = services + .BuildServiceProvider() + .GetRequiredService() + .Select(options); + + if (expectedExceptionMessage != null) + { + Assert.Equal(expectedExceptionMessage, + Assert.Throws(() => generator.GenerateModel( + model, + options)).Message); + return; + } + + var scaffoldedFiles = generator.GenerateModel( + model, + options); + if (assertScaffold != null) + { + assertScaffold(scaffoldedFiles); + } + + var build = new BuildSource + { + References = + { + BuildReference.ByName("Microsoft.EntityFrameworkCore"), + BuildReference.ByName("Microsoft.EntityFrameworkCore.Abstractions"), + BuildReference.ByName("Microsoft.EntityFrameworkCore.Cosmos"), + BuildReference.ByName("Microsoft.EntityFrameworkCore.InMemory"), + BuildReference.ByName("Microsoft.EntityFrameworkCore.Relational"), + BuildReference.ByName("Microsoft.EntityFrameworkCore.Sqlite"), + BuildReference.ByName("Microsoft.EntityFrameworkCore.Sqlite.NetTopologySuite"), + BuildReference.ByName("Microsoft.EntityFrameworkCore.SqlServer"), + BuildReference.ByName("Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite"), + BuildReference.ByName("NetTopologySuite"), + BuildReference.ByName("Newtonsoft.Json"), + BuildReference.ByName(typeof(CSharpRuntimeAnnotationCodeGeneratorTest).Assembly.GetName().Name) + }, + Sources = scaffoldedFiles.ToDictionary(f => f.Path, f => f.Code) + }; + + var assembly = build.BuildInMemory(); + + if (assertModel != null) + { + var modelType = assembly.GetType(options.ModelNamespace + "." + options.ContextType.Name + "Model"); + var instancePropertyInfo = modelType.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static); + var compiledModel = (IModel)instancePropertyInfo.GetValue(null); + + var modelRuntimeInitializer = context.GetService(); + assertModel(modelRuntimeInitializer.Initialize(compiledModel, designTime: false)); + } + } + + private void ConfigureProviderServices(string provider, IServiceCollection services) + { + var providerAssembly = Assembly.Load(new AssemblyName(provider)); + + var providerServicesAttribute = providerAssembly.GetCustomAttribute(); + if (providerServicesAttribute == null) + { + throw new InvalidOperationException(DesignStrings.CannotFindDesignTimeProviderAssemblyAttribute(provider)); + } + + var designTimeServicesType = providerAssembly.GetType( + providerServicesAttribute.TypeName, + throwOnError: true, + ignoreCase: false)!; + + ConfigureDesignTimeServices(designTimeServicesType, services); + } + + private static void ConfigureDesignTimeServices( + Type designTimeServicesType, + IServiceCollection services) + { + var designTimeServices = (IDesignTimeServices)Activator.CreateInstance(designTimeServicesType)!; + designTimeServices.ConfigureDesignTimeServices(services); + } + + protected static void AssertFileContents( + string expectedPath, + string expectedCode, + ScaffoldedFile file) + { + Assert.Equal(expectedPath, file.Path); + Assert.Equal(expectedCode, file.Code, ignoreLineEndingDifferences: true); + } + } +} diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpSlimAnnotationCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpSlimAnnotationCodeGeneratorTest.cs deleted file mode 100644 index 912cd4e5c3f..00000000000 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpSlimAnnotationCodeGeneratorTest.cs +++ /dev/null @@ -1,902 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.Design; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal; -using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; -using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.TestUtilities; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal -{ - public class CSharpSlimAnnotationCodeGeneratorTest - { - [ConditionalFact] - public void Empty_model() - { - Test( - new EmptyContext(), - new CompiledModelCodeGenerationOptions(), - code => AssertFileContents( - "EmptyContextModel.cs", - @"// -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Scaffolding.Internal; - -#pragma warning disable 219, 612, 618 -#nullable disable - -namespace TestNamespace -{ - [DbContext(typeof(CSharpSlimAnnotationCodeGeneratorTest.EmptyContext))] - partial class EmptyContextModel : SlimModel - { - private static EmptyContextModel _instance; - public static IModel Instance - { - get - { - if (_instance == null) - { - _instance = new EmptyContextModel(); - _instance.Initialize(); - } - - return _instance; - } - } - - protected override void Initialize() - { - } - } -} -", - code.Single()), - model => { - Assert.Empty(model.GetEntityTypes()); - Assert.Same(model, model.FindRuntimeAnnotationValue("ReadOnlyModel")); - }); - } - - public class EmptyContext : ContextBase - { - } - - [ConditionalFact] - public void Global_namespace_works() - { - Test( - new GlobalNamespaceContext(), - new CompiledModelCodeGenerationOptions - { - ModelNamespace = string.Empty - }, - code => - { - Assert.All(code, f => Assert.DoesNotContain("namespace ", f.Code)); - }, - model => - { - Assert.NotNull(model.FindEntityType("1")); - }); - } - - public class GlobalNamespaceContext : ContextBase - { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity("1", e => - { - e.Property("Id"); - e.HasKey("Id"); - }); - } - } - - [ConditionalFact] - public void Throws_for_constructor_binding() - { - Test( - new LazyLoadingProxiesContext(), - new CompiledModelCodeGenerationOptions(), - expectedExceptionMessage: DesignStrings.CompiledModelConstructorBinding("Lazy")); - } - - public class LazyLoadingProxiesContext : ContextBase - { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity("Lazy", e => - { - e.Property("Id"); - e.HasKey("Id"); - }); - } - - protected override void OnConfiguring(DbContextOptionsBuilder options) - => base.OnConfiguring(options.UseLazyLoadingProxies()); - } - - [ConditionalFact] - public void Manual_lazy_loading() - { - Test( - new LazyLoadingContext(), - new CompiledModelCodeGenerationOptions(), - assertModel: model => - { - var lazyConstructorEntity = model.FindEntityType(typeof(LazyConstructorEntity)); - var lazyParameterBinding = lazyConstructorEntity.ConstructorBinding.ParameterBindings.Single(); - Assert.Equal(typeof(ILazyLoader), lazyParameterBinding.ParameterType); - var lazyPropertyEntity = model.FindEntityType(typeof(LazyPropertyEntity)); - var lazyServiceProperty = lazyPropertyEntity.GetServiceProperties().Single(); - Assert.Equal(typeof(ILazyLoader), lazyServiceProperty.ClrType); - }); - } - - public class LazyLoadingContext : ContextBase - { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity(); - } - } - - public class LazyConstructorEntity - { - private readonly ILazyLoader _loader; - - public LazyConstructorEntity(ILazyLoader loader) - { - _loader = loader; - } - - public int Id { get; set; } - - public LazyPropertyEntity LazyPropertyEntity { get; set; } - } - - public class LazyPropertyEntity - { - public ILazyLoader Loader { get; set; } - - public LazyPropertyEntity() - { - } - - public int Id { get; set; } - public int LazyConstructorEntityId { get; set; } - - public LazyConstructorEntity LazyConstructorEntity { get; set; } - } - - [ConditionalFact] - public void Throws_for_query_filter() - { - Test( - new QueryFilterContext(), - new CompiledModelCodeGenerationOptions(), - expectedExceptionMessage: DesignStrings.CompiledModelQueryFilter("QueryFilter")); - } - - public class QueryFilterContext : ContextBase - { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity("QueryFilter", e => - { - e.Property("Id"); - e.HasKey("Id"); - e.HasQueryFilter((Expression, bool>>)(e => e != null)); - }); - } - } - - [ConditionalFact] - public void Throws_for_defining_query() - { - Test( - new DefiningQueryContext(), - new CompiledModelCodeGenerationOptions(), - expectedExceptionMessage: DesignStrings.CompiledModelDefiningQuery("object")); - } - - public class DefiningQueryContext : ContextBase - { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity(e => - { - e.Property("Id"); - e.HasKey("Id"); - e.Metadata.SetInMemoryQuery((Expression>>) - (() => Set())); - }); - } - } - - [ConditionalFact] - public void Throws_for_value_generator() - { - Test( - new ValueGeneratorContext(), - new CompiledModelCodeGenerationOptions(), - expectedExceptionMessage: DesignStrings.CompiledModelValueGenerator("MyEntity", "Id")); - } - - public class ValueGeneratorContext : ContextBase - { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity("MyEntity", e => - { - e.Property("Id").HasValueGenerator((p, e) => null); - e.HasKey("Id"); - }); - } - } - - [ConditionalFact] - public void Throws_for_value_converter() - { - Test( - new ValueConverterContext(), - new CompiledModelCodeGenerationOptions(), - expectedExceptionMessage: DesignStrings.CompiledModelValueConverter("MyEntity", "Id")); - } - - public class ValueConverterContext : ContextBase - { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity("MyEntity", e => - { - e.Property("Id").HasConversion(i => i, i => i); - e.HasKey("Id"); - }); - } - } - - [ConditionalFact] - public void Throws_for_value_comparer() - { - Test( - new ValueComparerContext(), - new CompiledModelCodeGenerationOptions(), - expectedExceptionMessage: DesignStrings.CompiledModelValueComparer("MyEntity", "Id")); - } - - public class ValueComparerContext : ContextBase - { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity("MyEntity", e => - { - e.Property("Id").HasConversion(typeof(int), new FakeValueComparer()); - e.HasKey("Id"); - }); - } - } - - private class FakeValueComparer : ValueComparer - { - public FakeValueComparer() - : base(false) - { - } - - public override Type Type { get; } = typeof(int); - - public override bool Equals(object left, object right) => throw new NotImplementedException(); - - public override int GetHashCode(object instance) => throw new NotImplementedException(); - - public override object Snapshot(object instance) => throw new NotImplementedException(); - } - - [ConditionalFact] - public void Throws_for_custom_type_mapping() - { - Test( - new TypeMappingContext(), - new CompiledModelCodeGenerationOptions(), - expectedExceptionMessage: DesignStrings.CompiledModelTypeMapping("MyEntity", "Id")); - } - - public class TypeMappingContext : ContextBase - { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity("MyEntity", e => - { - e.Property("Id").Metadata.SetTypeMapping(new InMemoryTypeMapping(typeof(int[]))); - e.HasKey("Id"); - }); - } - } - - [ConditionalFact] - public void Throws_for_custom_function_translation() - { - Test( - new FunctionTranslationContext(), - new CompiledModelCodeGenerationOptions(), - expectedExceptionMessage: RelationalStrings.CompiledModelFunctionTranslation("GetSqlFragmentStatic")); - } - - public class FunctionTranslationContext : SqlServerContextBase - { - public static string GetSqlFragmentStatic() - => throw new NotImplementedException(); - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.HasDbFunction(typeof(FunctionTranslationContext).GetMethod(nameof(GetSqlFragmentStatic))) - .HasTranslation(args => new SqlFragmentExpression("NULL")); - } - } - - [ConditionalFact] - public void Throws_for_custom_function_type_mapping() - { - Test( - new FunctionTypeMappingContext(), - new CompiledModelCodeGenerationOptions(), - expectedExceptionMessage: RelationalStrings.CompiledModelFunctionTypeMapping("GetSqlFragmentStatic")); - } - - public class FunctionTypeMappingContext : SqlServerContextBase - { - public static string GetSqlFragmentStatic(string param) - => throw new NotImplementedException(); - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.HasDbFunction(typeof(FunctionTypeMappingContext).GetMethod(nameof(GetSqlFragmentStatic))) - .Metadata.TypeMapping = new StringTypeMapping("varchar"); - } - } - - [ConditionalFact] - public void Throws_for_custom_function_parameter_type_mapping() - { - Test( - new FunctionParameterTypeMappingContext(), - new CompiledModelCodeGenerationOptions(), - expectedExceptionMessage: RelationalStrings.CompiledModelFunctionParameterTypeMapping("GetSqlFragmentStatic", "param")); - } - - public class FunctionParameterTypeMappingContext : SqlServerContextBase - { - public static string GetSqlFragmentStatic(string param) - => throw new NotImplementedException(); - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.HasDbFunction(typeof(FunctionParameterTypeMappingContext).GetMethod(nameof(GetSqlFragmentStatic))) - .HasParameter("param").Metadata.TypeMapping = new StringTypeMapping("varchar"); - } - } - - [ConditionalFact] - public void DbFunctions() - { - Test( - new DbFunctionContext(), - new CompiledModelCodeGenerationOptions(), - code => Assert.Collection(code, - c => AssertFileContents("DbFunctionContextModel.cs", - @"// -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Scaffolding.Internal; - -#pragma warning disable 219, 612, 618 -#nullable disable - -namespace TestNamespace -{ - [DbContext(typeof(CSharpSlimAnnotationCodeGeneratorTest.DbFunctionContext))] - partial class DbFunctionContextModel : SlimModel - { - private static DbFunctionContextModel _instance; - public static IModel Instance - { - get - { - if (_instance == null) - { - _instance = new DbFunctionContextModel(); - _instance.Initialize(); - } - - return _instance; - } - } - - protected override void Initialize() - { - var data = DataEntityType.Create(this, null); - - DataEntityType.CreateAnnotations(data); - - var functions = new SortedDictionary(); - var getCount = new SlimDbFunction( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpSlimAnnotationCodeGeneratorTest+DbFunctionContext.GetCount(System.Guid?,string)"", - this, - typeof(int), - ""CustomerOrderCount"", - schema: ""dbf"", - storeType: ""int"", - methodInfo: typeof(CSharpSlimAnnotationCodeGeneratorTest.DbFunctionContext).GetMethod( - ""GetCount"", - BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly, - null, - new Type[] { typeof(Guid?), typeof(string) }, - null), - scalar: true); - - var id = getCount.AddParameter( - ""id"", - typeof(Guid?), - true, - ""uniqueidentifier""); - id.AddAnnotation(""MyAnnotation"", new[] { 1L }); - - var condition = getCount.AddParameter( - ""condition"", - typeof(string), - false, - ""nvarchar(max)""); - - functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpSlimAnnotationCodeGeneratorTest+DbFunctionContext.GetCount(System.Guid?,string)""] = getCount; - - var getData = new SlimDbFunction( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpSlimAnnotationCodeGeneratorTest+DbFunctionContext.GetData()"", - this, - typeof(IQueryable), - ""GetAllData"", - schema: ""dbo"", - methodInfo: typeof(CSharpSlimAnnotationCodeGeneratorTest.DbFunctionContext).GetMethod( - ""GetData"", - BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, - null, - new Type[] { }, - null)); - - functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpSlimAnnotationCodeGeneratorTest+DbFunctionContext.GetData()""] = getData; - - var getData0 = new SlimDbFunction( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpSlimAnnotationCodeGeneratorTest+DbFunctionContext.GetData(int)"", - this, - typeof(IQueryable), - ""GetData"", - schema: ""dbo"", - methodInfo: typeof(CSharpSlimAnnotationCodeGeneratorTest.DbFunctionContext).GetMethod( - ""GetData"", - BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, - null, - new Type[] { typeof(int) }, - null)); - - var id0 = getData0.AddParameter( - ""id"", - typeof(int), - false, - ""int""); - - functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpSlimAnnotationCodeGeneratorTest+DbFunctionContext.GetData(int)""] = getData0; - - var isDateStatic = new SlimDbFunction( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpSlimAnnotationCodeGeneratorTest+DbFunctionContext.IsDateStatic(string)"", - this, - typeof(bool), - ""IsDate"", - storeType: ""bit"", - methodInfo: typeof(CSharpSlimAnnotationCodeGeneratorTest.DbFunctionContext).GetMethod( - ""IsDateStatic"", - BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly, - null, - new Type[] { typeof(string) }, - null), - scalar: true, - nullable: true, - builtIn: true); - - var date = isDateStatic.AddParameter( - ""date"", - typeof(string), - false, - ""nvarchar(max)""); - - isDateStatic.AddAnnotation(""MyGuid"", new Guid(""00000000-0000-0000-0000-000000000000"")); - functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpSlimAnnotationCodeGeneratorTest+DbFunctionContext.IsDateStatic(string)""] = isDateStatic; - - this.AddAnnotation(""Relational:DbFunctions"", functions); - this.AddAnnotation(""Relational:MaxIdentifierLength"", 128); - this.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); - } - } -} -", - c), - c => AssertFileContents("DataEntityType.cs", - @"// -using System; -using System.Reflection; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Scaffolding.Internal; - -#pragma warning disable 219, 612, 618 -#nullable disable - -namespace TestNamespace -{ - partial class DataEntityType - { - public static SlimEntityType Create(SlimModel model, SlimEntityType baseEntityType) - { - var slimEntityType = model.AddEntityType( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpSlimAnnotationCodeGeneratorTest+Data"", - typeof(CSharpSlimAnnotationCodeGeneratorTest.Data), - baseEntityType); - - var blob = slimEntityType.AddProperty( - ""Blob"", - typeof(byte[]), - propertyInfo: typeof(CSharpSlimAnnotationCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpSlimAnnotationCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) - , - nullable: true); - - return slimEntityType; - } - - - public static void CreateAnnotations(SlimEntityType slimEntityType) - { - slimEntityType.AddAnnotation(""Relational:FunctionName"", ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpSlimAnnotationCodeGeneratorTest+DbFunctionContext.GetData()""); - slimEntityType.AddAnnotation(""Relational:TableName"", null); - slimEntityType.AddAnnotation(""Relational:Schema"", null); - slimEntityType.AddAnnotation(""Relational:ViewName"", null); - slimEntityType.AddAnnotation(""Relational:ViewSchema"", null); - slimEntityType.AddAnnotation(""Relational:SqlQuery"", null); - } - } -} -", - c)), - model => - { - Assert.Equal(4, model.GetDbFunctions().Count()); - - var getCount = model.FindDbFunction(typeof(DbFunctionContext) - .GetMethod("GetCount", BindingFlags.NonPublic | BindingFlags.Instance)); - Assert.Equal("CustomerOrderCount", getCount.Name); - Assert.Equal(typeof(DbFunctionContext).FullName + ".GetCount(System.Guid?,string)", getCount.ModelName); - Assert.Equal("dbf", getCount.Schema); - Assert.False(getCount.IsNullable); - Assert.True(getCount.IsScalar); - Assert.False(getCount.IsBuiltIn); - Assert.False(getCount.IsAggregate); - Assert.Null(getCount.Translation); - Assert.Equal(typeof(int), getCount.ReturnType); - Assert.Equal("GetCount", getCount.MethodInfo.Name); - Assert.Empty(getCount.GetAnnotations()); - Assert.Empty(getCount.GetRuntimeAnnotations()); - Assert.Equal("CustomerOrderCount", getCount.StoreFunction.Name); - Assert.False(getCount.StoreFunction.IsShared); - Assert.Equal(2, getCount.Parameters.Count); - - var getCountParameter1 = getCount.Parameters[0]; - Assert.Same(getCount, getCountParameter1.Function); - Assert.Equal("id", getCountParameter1.Name); - Assert.Equal("uniqueidentifier", getCountParameter1.StoreType); - Assert.True(getCountParameter1.PropagatesNullability); - Assert.Equal(typeof(Guid?), getCountParameter1.ClrType); - Assert.Equal("uniqueidentifier", getCountParameter1.TypeMapping.StoreType); - Assert.Single(getCountParameter1.GetAnnotations()); - Assert.Equal(new[] { 1L }, getCountParameter1["MyAnnotation"]); - Assert.Equal("id", getCountParameter1.StoreFunctionParameter.Name); - Assert.Equal("uniqueidentifier", getCountParameter1.StoreFunctionParameter.Type); - - var getCountParameter2 = getCount.Parameters[1]; - Assert.Same(getCount, getCountParameter2.Function); - Assert.Equal("condition", getCountParameter2.Name); - Assert.Equal("nvarchar(max)", getCountParameter2.StoreType); - Assert.False(getCountParameter2.PropagatesNullability); - Assert.Equal(typeof(string), getCountParameter2.ClrType); - Assert.Equal("nvarchar(max)", getCountParameter2.TypeMapping.StoreType); - Assert.Equal("condition", getCountParameter2.StoreFunctionParameter.Name); - Assert.Equal("nvarchar(max)", getCountParameter2.StoreFunctionParameter.Type); - - var isDate = model.FindDbFunction(typeof(DbFunctionContext).GetMethod("IsDateStatic")); - Assert.Equal("IsDate", isDate.Name); - Assert.Null(isDate.Schema); - Assert.Equal(typeof(DbFunctionContext).FullName + ".IsDateStatic(string)", isDate.ModelName); - Assert.True(isDate.IsNullable); - Assert.True(isDate.IsScalar); - Assert.True(isDate.IsBuiltIn); - Assert.False(isDate.IsAggregate); - Assert.Null(isDate.Translation); - Assert.Equal(typeof(bool), isDate.ReturnType); - Assert.Equal("IsDateStatic", isDate.MethodInfo.Name); - Assert.Single(isDate.GetAnnotations()); - Assert.Equal(new Guid(), isDate["MyGuid"]); - Assert.Empty(isDate.GetRuntimeAnnotations()); - Assert.Equal("bit", isDate.StoreFunction.ReturnType); - Assert.Empty(isDate.StoreFunction.EntityTypeMappings); - Assert.Single(isDate.Parameters); - - var isDateParameter = isDate.Parameters[0]; - Assert.Same(isDate, isDateParameter.Function); - Assert.Equal("date", isDateParameter.Name); - Assert.Equal("nvarchar(max)", isDateParameter.StoreType); - Assert.False(isDateParameter.PropagatesNullability); - Assert.Equal(typeof(string), isDateParameter.ClrType); - Assert.Equal("nvarchar(max)", isDateParameter.TypeMapping.StoreType); - Assert.Equal("date", isDateParameter.StoreFunctionParameter.Name); - Assert.Equal("nvarchar(max)", isDateParameter.StoreFunctionParameter.Type); - - var getData = model.FindDbFunction(typeof(DbFunctionContext) - .GetMethod("GetData", new Type[] { typeof(int) })); - Assert.Equal("GetData", getData.Name); - Assert.Equal("dbo", getData.Schema); - Assert.Equal(typeof(DbFunctionContext).FullName + ".GetData(int)", getData.ModelName); - Assert.False(getData.IsNullable); - Assert.False(getData.IsScalar); - Assert.False(getData.IsBuiltIn); - Assert.False(getData.IsAggregate); - Assert.Null(getData.Translation); - Assert.Equal(typeof(IQueryable), getData.ReturnType); - Assert.Equal("GetData", getData.MethodInfo.Name); - Assert.Empty(getData.GetAnnotations()); - Assert.Empty(getData.GetRuntimeAnnotations()); - Assert.Null(getData.StoreFunction.ReturnType); - Assert.Equal(typeof(Data), getData.StoreFunction.EntityTypeMappings.Single().EntityType.ClrType); - Assert.Single(getData.Parameters); - - var getDataParameter = getData.Parameters[0]; - Assert.Same(getData, getDataParameter.Function); - Assert.Equal("id", getDataParameter.Name); - Assert.Equal("int", getDataParameter.StoreType); - Assert.False(getDataParameter.PropagatesNullability); - Assert.Equal(typeof(int), getDataParameter.ClrType); - Assert.Equal("int", getDataParameter.TypeMapping.StoreType); - Assert.Equal("id", getDataParameter.StoreFunctionParameter.Name); - Assert.Equal("int", getDataParameter.StoreFunctionParameter.Type); - - var getDataParameterless = model.FindDbFunction(typeof(DbFunctionContext) - .GetMethod("GetData", new Type[0])); - Assert.Equal("GetAllData", getDataParameterless.Name); - Assert.Equal("dbo", getDataParameterless.Schema); - Assert.Equal(typeof(DbFunctionContext).FullName + ".GetData()", getDataParameterless.ModelName); - Assert.False(getDataParameterless.IsNullable); - Assert.False(getDataParameterless.IsScalar); - Assert.False(getDataParameterless.IsBuiltIn); - Assert.False(getDataParameterless.IsAggregate); - Assert.Null(getDataParameterless.Translation); - Assert.Equal(typeof(IQueryable), getDataParameterless.ReturnType); - Assert.Equal("GetData", getDataParameterless.MethodInfo.Name); - Assert.Empty(getDataParameterless.GetAnnotations()); - Assert.Empty(getDataParameterless.GetRuntimeAnnotations()); - Assert.False(getDataParameterless.StoreFunction.IsBuiltIn); - Assert.Equal(typeof(Data), getDataParameterless.StoreFunction.EntityTypeMappings.Single().EntityType.ClrType); - Assert.Equal(0, getDataParameterless.Parameters.Count); - - Assert.Single(model.GetEntityTypes()); - var dataEntity = model.FindEntityType(typeof(Data)); - Assert.Null(dataEntity.FindPrimaryKey()); - var dataEntityFunctionMapping = dataEntity.GetFunctionMappings().Single(m => m.IsDefaultFunctionMapping); - Assert.True(dataEntityFunctionMapping.IncludesDerivedTypes); - Assert.True(dataEntityFunctionMapping.IsSharedTablePrincipal); - Assert.True(dataEntityFunctionMapping.IsSplitEntityTypePrincipal); - Assert.Same(getDataParameterless, dataEntityFunctionMapping.DbFunction); - - var getDataStoreFunction = dataEntityFunctionMapping.StoreFunction; - Assert.Same(getDataParameterless, getDataStoreFunction.DbFunctions.Single()); - Assert.False(getDataStoreFunction.IsOptional(dataEntity)); - - var dataEntityOtherFunctionMapping = dataEntity.GetFunctionMappings().Single(m => !m.IsDefaultFunctionMapping); - Assert.True(dataEntityOtherFunctionMapping.IncludesDerivedTypes); - Assert.True(dataEntityOtherFunctionMapping.IsSharedTablePrincipal); - Assert.True(dataEntityOtherFunctionMapping.IsSplitEntityTypePrincipal); - Assert.Same(getData, dataEntityOtherFunctionMapping.DbFunction); - - var getDataOtherStoreFunction = dataEntityOtherFunctionMapping.StoreFunction; - Assert.Same(getData, getDataOtherStoreFunction.DbFunctions.Single()); - Assert.False(getDataOtherStoreFunction.IsOptional(dataEntity)); - }); - } - - public class DbFunctionContext : SqlServerContextBase - { - public static bool IsDateStatic(string date) - => throw new NotImplementedException(); - - private int GetCount(Guid? id, string condition) - => throw new NotImplementedException(); - - public IQueryable GetData(int id) - { - return FromExpression(() => GetData(id)); - } - - public IQueryable GetData() - { - return FromExpression(() => GetData()); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.HasDbFunction(typeof(DbFunctionContext).GetMethod(nameof(GetCount), BindingFlags.NonPublic | BindingFlags.Instance)) - .HasName("CustomerOrderCount").HasSchema("dbf").IsNullable(false) - .HasParameter("id").PropagatesNullability().Metadata.SetAnnotation("MyAnnotation", new[] { 1L }); - - modelBuilder.HasDbFunction(typeof(DbFunctionContext).GetMethod(nameof(IsDateStatic))).HasName("IsDate").IsBuiltIn() - .Metadata.SetAnnotation("MyGuid", new Guid()); - - modelBuilder.HasDbFunction(typeof(DbFunctionContext).GetMethod(nameof(GetData), new Type[] { typeof(int) })); - modelBuilder.HasDbFunction(typeof(DbFunctionContext).GetMethod(nameof(GetData), new Type[0])); - - modelBuilder.Entity().ToFunction(typeof(DbFunctionContext).FullName + ".GetData()", f => f.HasName("GetAllData")) - .HasNoKey(); - } - } - - public class Data - { - public byte[] Blob { get; set; } - } - - // TODO: Test spatial - // TODO: Test cosmos - // Nullable reference types - - public abstract class ContextBase : DbContext - { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); - } - - protected override void OnConfiguring(DbContextOptionsBuilder options) - => options - .EnableServiceProviderCaching(false) - .UseInMemoryDatabase(nameof(CSharpSlimAnnotationCodeGeneratorTest)); - } - - public abstract class SqlServerContextBase : DbContext - { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); - } - - protected override void OnConfiguring(DbContextOptionsBuilder options) - => options - .EnableServiceProviderCaching(false) - .UseSqlServer(); - } - - protected void Test( - DbContext context, - CompiledModelCodeGenerationOptions options, - Action> assertScaffold = null, - Action assertModel = null, - string expectedExceptionMessage = null) - { - var model = context.GetService().Model; - - var services = new ServiceCollection() - .AddEntityFrameworkDesignTimeServices(); - new SqlServerDesignTimeServices().ConfigureDesignTimeServices(services); - - options.ModelNamespace ??= "TestNamespace"; - options.ContextType = context.GetType(); - - var generator = services - .BuildServiceProvider() - .GetRequiredService() - .Select(options); - - if (expectedExceptionMessage != null) - { - Assert.Equal(expectedExceptionMessage, - Assert.Throws(() => generator.GenerateModel( - model, - options)).Message); - return; - } - - var scaffoldedFiles = generator.GenerateModel( - model, - options); - if (assertScaffold != null) - { - assertScaffold(scaffoldedFiles); - } - - var build = new BuildSource - { - References = - { - BuildReference.ByName("Microsoft.EntityFrameworkCore"), - BuildReference.ByName("Microsoft.EntityFrameworkCore.Abstractions"), - BuildReference.ByName("Microsoft.EntityFrameworkCore.Relational"), - BuildReference.ByName("Microsoft.EntityFrameworkCore.SqlServer"), - BuildReference.ByName(typeof(CSharpSlimAnnotationCodeGeneratorTest).Assembly.GetName().Name) - }, - Sources = scaffoldedFiles.ToDictionary(f => f.Path, f => f.Code) - }; - - var assembly = build.BuildInMemory(); - - if (assertModel != null) - { - var modelType = assembly.GetType(options.ModelNamespace + "." + options.ContextType.Name + "Model"); - var instancePropertyInfo = modelType.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static); - var compiledModel = (IModel)instancePropertyInfo.GetValue(null); - - var modelRuntimeInitializer = context.GetService(); - assertModel(modelRuntimeInitializer.Initialize(compiledModel, designTime: false)); - } - } - - protected static void AssertFileContents( - string expectedPath, - string expectedCode, - ScaffoldedFile file) - { - Assert.Equal(expectedPath, file.Path); - Assert.Equal(expectedCode, file.Code, ignoreLineEndingDifferences: true); - } - } -} diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs index dd13b4f95a4..3f1dc3cec58 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs @@ -6,6 +6,7 @@ using System.Linq; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; @@ -65,7 +66,7 @@ protected void Test( if (assertModel != null) { - var compiledModel = context.Model; + var compiledModel = context.GetService().Model; assertModel(compiledModel); } } diff --git a/test/EFCore.Design.Tests/TestUtilities/BuildSource.cs b/test/EFCore.Design.Tests/TestUtilities/BuildSource.cs index 3600bacec40..c1e5ee4bea3 100644 --- a/test/EFCore.Design.Tests/TestUtilities/BuildSource.cs +++ b/test/EFCore.Design.Tests/TestUtilities/BuildSource.cs @@ -104,10 +104,10 @@ public Assembly BuildInMemory() return assembly; } - private static CSharpCompilationOptions CreateOptions() + private CSharpCompilationOptions CreateOptions() => new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary, - nullableContextOptions: NullableReferenceTypes ? NullableContextOptions.Enable : NullableContextOptions.Disable), + nullableContextOptions: NullableReferenceTypes ? NullableContextOptions.Enable : NullableContextOptions.Disable, reportSuppressedDiagnostics: false, specificDiagnosticOptions: new Dictionary() { diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index c33f8a57f71..3829a84556c 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -908,7 +908,7 @@ public void Can_use_relational_model_with_functions() } private static IRelationalModel Finalize(ModelBuilder modelBuilder) - => RelationalTestHelpers.Instance.Finalize(modelBuilder).GetRelationalModel(); + => RelationalTestHelpers.Instance.Finalize(modelBuilder, designTime: true).GetRelationalModel(); protected virtual ModelBuilder CreateConventionModelBuilder() => RelationalTestHelpers.Instance.CreateConventionBuilder(); diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsCollectionsQueryTestBase.cs index aae825b8c68..80fba8edf0a 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsCollectionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsCollectionsQueryTestBase.cs @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - public class ComplexNavigationsCollectionsQueryTestBase : QueryTestBase + public abstract class ComplexNavigationsCollectionsQueryTestBase : QueryTestBase where TFixture : ComplexNavigationsQueryFixtureBase, new() { protected ComplexNavigationsContext CreateContext() diff --git a/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataBuilderExtensionsTest.cs b/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataBuilderExtensionsTest.cs index 8eabac9a752..3eb9606d40c 100644 --- a/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataBuilderExtensionsTest.cs +++ b/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataBuilderExtensionsTest.cs @@ -252,7 +252,7 @@ public void Can_access_index() Assert.False(indexBuilder.Metadata.IsClustered()); Assert.NotNull(indexBuilder.IsClustered(null, fromDataAnnotation: true)); - Assert.Null(indexBuilder.Metadata.GetIsClusteredConfigurationSource()); + Assert.Equal(ConfigurationSource.DataAnnotation, indexBuilder.Metadata.GetIsClusteredConfigurationSource()); } [ConditionalFact] diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsAnnotationProviderTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsAnnotationProviderTest.cs index ef327a14f0c..b68b0faa6a4 100644 --- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsAnnotationProviderTest.cs +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsAnnotationProviderTest.cs @@ -24,10 +24,10 @@ public void For_property_handles_identity_annotations() var modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Entity().Property("Id").UseIdentityColumn(2, 3); - var model = SqlServerTestHelpers.Instance.Finalize(modelBuilder); + var model = SqlServerTestHelpers.Instance.Finalize(modelBuilder, designTime: true); var property = model.FindEntityType(typeof(Entity)).FindProperty("Id"); - var migrationAnnotations = _annotations.For(property.GetTableColumnMappings().Single().Column).ToList(); + var migrationAnnotations = _annotations.For(property.GetTableColumnMappings().Single().Column, true).ToList(); var identity = Assert.Single(migrationAnnotations, a => a.Name == SqlServerAnnotationNames.Identity); Assert.Equal("2, 3", identity.Value); @@ -39,10 +39,10 @@ public void Resolves_column_names_for_Index_with_included_properties() var modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Entity().Property(e => e.IncludedProp).HasColumnName("IncludedColumn"); modelBuilder.Entity().HasIndex(e => e.IndexedProp).IncludeProperties(e => e.IncludedProp); - var model = SqlServerTestHelpers.Instance.Finalize(modelBuilder); + var model = SqlServerTestHelpers.Instance.Finalize(modelBuilder, designTime: true); Assert.Contains( - _annotations.For(model.FindEntityType(typeof(Entity)).GetIndexes().Single().GetMappedTableIndexes().Single()), + _annotations.For(model.FindEntityType(typeof(Entity)).GetIndexes().Single().GetMappedTableIndexes().Single(), true), a => a.Name == SqlServerAnnotationNames.Include && ((string[])a.Value).Contains("IncludedColumn")); } diff --git a/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs b/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs index ada2276997d..05fade2f3ce 100644 --- a/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs +++ b/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs @@ -23,7 +23,7 @@ public void Does_not_add_Autoincrement_for_OnAdd_integer_property_non_key() FinalizeModel(); Assert.DoesNotContain( - _provider.For(property.GetTableColumnMappings().Single().Column), + _provider.For(property.GetTableColumnMappings().Single().Column, true), a => a.Name == _autoincrement.Name && (bool)a.Value); } @@ -35,7 +35,7 @@ public void Adds_Autoincrement_for_OnAdd_integer_property_primary_key() FinalizeModel(); Assert.Contains( - _provider.For(property.GetTableColumnMappings().Single().Column), + _provider.For(property.GetTableColumnMappings().Single().Column, true), a => a.Name == _autoincrement.Name && (bool)a.Value); } @@ -46,7 +46,7 @@ public void Does_not_add_Autoincrement_for_OnAddOrUpdate_integer_property() FinalizeModel(); Assert.DoesNotContain( - _provider.For(property.GetTableColumnMappings().Single().Column), + _provider.For(property.GetTableColumnMappings().Single().Column, true), a => a.Name == _autoincrement.Name); } @@ -57,7 +57,7 @@ public void Does_not_add_Autoincrement_for_OnUpdate_integer_property() FinalizeModel(); Assert.DoesNotContain( - _provider.For(property.GetTableColumnMappings().Single().Column), + _provider.For(property.GetTableColumnMappings().Single().Column, true), a => a.Name == _autoincrement.Name); } @@ -68,7 +68,7 @@ public void Does_not_add_Autoincrement_for_Never_value_generated_integer_propert FinalizeModel(); Assert.DoesNotContain( - _provider.For(property.GetTableColumnMappings().Single().Column), + _provider.For(property.GetTableColumnMappings().Single().Column, true), a => a.Name == _autoincrement.Name); } @@ -79,7 +79,7 @@ public void Does_not_add_Autoincrement_for_default_integer_property() FinalizeModel(); Assert.DoesNotContain( - _provider.For(property.GetTableColumnMappings().Single().Column), + _provider.For(property.GetTableColumnMappings().Single().Column, true), a => a.Name == _autoincrement.Name); } @@ -90,7 +90,7 @@ public void Does_not_add_Autoincrement_for_non_integer_OnAdd_property() FinalizeModel(); Assert.DoesNotContain( - _provider.For(property.GetTableColumnMappings().Single().Column), + _provider.For(property.GetTableColumnMappings().Single().Column, true), a => a.Name == _autoincrement.Name); } diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs index 21469ce38a9..4ca0bfaaf4a 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs @@ -298,7 +298,7 @@ protected virtual IModel Validate(IMutableModel model, bool sensitiveDataLogging var modelRuntimeInitializer = serviceProvider.GetRequiredService(); var validationLogger = CreateValidationLogger(sensitiveDataLoggingEnabled); - return modelRuntimeInitializer.Initialize(model.FinalizeModel(), designTime: false, validationLogger); + return modelRuntimeInitializer.Initialize(model.FinalizeModel(), designTime: true, validationLogger); } protected DiagnosticsLogger CreateValidationLogger(bool sensitiveDataLoggingEnabled = false) diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs index 8fb9b1da2da..5d3fc191a87 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs @@ -106,7 +106,7 @@ public virtual void HasForeignKey_infers_type_for_shadow_property_when_not_speci modelBuilder.Entity().HasKey(c => c.Key); - var model = (IConventionModel)modelBuilder.FinalizeModel(designTime: true); + var model = (IConventionModel)modelBuilder.FinalizeModel(); var property = model .FindEntityType(typeof(ComplexCaseChild13108))!.GetProperties().Single(p => p.Name == "ParentKey"); diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs index a0fca78500d..a9d2f61cd2e 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs @@ -173,12 +173,12 @@ public abstract TestModelBuilder SharedTypeEntity(string name, Action() where TEntity : class; - public virtual IModel FinalizeModel(bool designTime = false) + public virtual IModel FinalizeModel() { var serviceProvider = TestHelpers.CreateContextServices(); var modelRuntimeInitializer = serviceProvider.GetRequiredService(); - return modelRuntimeInitializer.Initialize(ModelBuilder.FinalizeModel(), designTime, ValidationLogger); + return modelRuntimeInitializer.Initialize(ModelBuilder.FinalizeModel(), designTime: true, ValidationLogger); } public virtual string GetDisplayName(Type entityType) diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index 8a170684110..df77f975a73 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -1546,10 +1546,6 @@ public virtual void Can_add_seed_data_objects() Assert.Equal(-1, data.First()[nameof(Beta.Id)]); Assert.Equal(-2, data.Last()[nameof(Beta.Id)]); - Assert.Equal( - CoreStrings.SlimModelMissingData, - Assert.Throws(() => finalModel.FindEntityType(typeof(Beta)).GetSeedData()).Message); - var _ = finalModel.ToDebugString(); } @@ -1673,7 +1669,7 @@ public virtual void Can_add_seed_data_anonymous_objects_indexed_property_diction b.HasData(new { Id = -1, Required = 2 }); }); - var model = modelBuilder.FinalizeModel(designTime: true); + var model = modelBuilder.FinalizeModel(); var entityType = model.FindEntityType(typeof(IndexedClassByDictionary)); var data = Assert.Single(entityType.GetSeedData()); diff --git a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs index 7ec6da2ae58..d4da1f8997b 100644 --- a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs @@ -195,7 +195,7 @@ public virtual void Can_configure_owned_type_properties() .Ignore(d => d.Id) .Property("foo"); - var model = modelBuilder.FinalizeModel(designTime: true); + var model = modelBuilder.FinalizeModel(); var owner = model.FindEntityType(typeof(Customer)); var owned = owner.FindNavigation(nameof(Customer.Details)).ForeignKey.DeclaringEntityType; @@ -400,7 +400,7 @@ public virtual void Can_configure_owned_type_collection() entityBuilder.WithOwner(o => o.Customer) .HasPrincipalKey(c => c.AlternateKey); - var model = modelBuilder.FinalizeModel(designTime: true); + var model = modelBuilder.FinalizeModel(); var owner = model.FindEntityType(typeof(Customer)); var ownership = owner.FindNavigation(nameof(Customer.Orders)).ForeignKey; @@ -577,7 +577,7 @@ public virtual void Can_configure_owned_type_from_an_owned_type_collection(HasDa } }); - var model = modelBuilder.FinalizeModel(designTime: true); + var model = modelBuilder.FinalizeModel(); var ownership = model.FindEntityType(typeof(Customer)).FindNavigation(nameof(Customer.Orders)).ForeignKey; var owned = ownership.DeclaringEntityType; @@ -617,7 +617,7 @@ public virtual void Can_chain_owned_type_collection_configurations() }); }); - var model = modelBuilder.FinalizeModel(designTime: true); + var model = modelBuilder.FinalizeModel(); var ownership = model.FindEntityType(typeof(Customer)).FindNavigation(nameof(Customer.Orders)).ForeignKey; var owned = ownership.DeclaringEntityType;