From 13e69caf025bd40324a4eebd7b88af998d5e0da1 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 20 Jul 2020 16:58:22 -0700 Subject: [PATCH] Allow mapping entity types to SQL queries Fixes #13358 Part of #17063 --- .../InMemoryEntityTypeBuilderExtensions.cs | 2 +- .../Design/AnnotationCodeGenerator.cs | 5 +- .../RelationalEntityTypeBuilderExtensions.cs | 183 ++++++- .../RelationalEntityTypeExtensions.cs | 83 ++++ .../RelationalPropertyExtensions.cs | 27 +- .../RelationalModelValidator.cs | 70 +-- .../ForeignKeyConstraintExtensions.cs | 4 +- .../Metadata/FunctionColumnExtensions.cs | 2 +- .../Metadata/IFunctionMapping.cs | 2 +- .../Metadata/IRelationalAnnotationProvider.cs | 28 ++ .../Metadata/IRelationalModel.cs | 13 + src/EFCore.Relational/Metadata/ISqlQuery.cs | 39 ++ .../Metadata/ISqlQueryColumn.cs | 23 + .../Metadata/ISqlQueryColumnMapping.cs | 21 + .../Metadata/ISqlQueryMapping.cs | 29 ++ .../Metadata/IStoreFunction.cs | 5 + src/EFCore.Relational/Metadata/ITable.cs | 5 + src/EFCore.Relational/Metadata/ITableBase.cs | 5 + src/EFCore.Relational/Metadata/IView.cs | 5 + .../Metadata/Internal/Column.cs | 54 +- .../Metadata/Internal/ColumnBase.cs | 74 +++ .../Metadata/Internal/ColumnMapping.cs | 29 +- .../Metadata/Internal/ColumnMappingBase.cs | 48 ++ .../Metadata/Internal/FunctionColumn.cs | 45 +- .../Internal/FunctionColumnMapping.cs | 30 +- .../Metadata/Internal/FunctionMapping.cs | 22 +- .../Metadata/Internal/RelationalModel.cs | 462 +++++++++++++----- .../Metadata/Internal/SqlQuery.cs | 73 +++ .../Metadata/Internal/SqlQueryColumn.cs | 49 ++ .../Internal/SqlQueryColumnMapping.cs | 52 ++ .../Metadata/Internal/SqlQueryMapping.cs | 55 +++ .../Metadata/Internal/StoreFunction.cs | 56 +-- .../Metadata/Internal/Table.cs | 64 +-- .../Metadata/Internal/TableBase.cs | 44 +- .../Metadata/Internal/TableMapping.cs | 22 +- .../Metadata/Internal/TableMappingBase.cs | 8 +- .../Metadata/Internal/View.cs | 66 +-- .../Metadata/Internal/ViewColumn.cs | 43 +- .../Metadata/Internal/ViewColumnMapping.cs | 29 +- .../Metadata/Internal/ViewMapping.cs | 22 +- .../Metadata/RelationalAnnotationNames.cs | 25 + .../Metadata/RelationalAnnotationProvider.cs | 12 + .../Metadata/SqlQueryColumnExtensions.cs | 68 +++ .../SqlQueryColumnMappingExtensions.cs | 57 +++ .../Metadata/SqlQueryExtensions.cs | 87 ++++ .../Metadata/SqlQueryMappingExtensions.cs | 62 +++ .../Metadata/StoreObjectIdentifier.cs | 24 + .../Metadata/StoreObjectType.cs | 5 + .../Metadata/TableIndexExtensions.cs | 2 +- .../Metadata/UniqueConstraintExtensions.cs | 2 +- .../Metadata/ViewColumnExtensions.cs | 2 +- .../Internal/MigrationsModelDiffer.cs | 8 +- .../Properties/RelationalStrings.Designer.cs | 42 +- .../Properties/RelationalStrings.resx | 17 +- ...yableMethodTranslatingExpressionVisitor.cs | 19 +- .../Query/SqlExpressionFactory.cs | 3 +- .../Query/SqlExpressions/ColumnExpression.cs | 6 +- .../Query/SqlExpressions/FromSqlExpression.cs | 2 + .../Query/SqlExpressions/SelectExpression.cs | 21 +- .../Diagnostics/CoreLoggerExtensions.cs | 4 +- src/EFCore/Extensions/EntityTypeExtensions.cs | 9 + .../Builders/IConventionEntityTypeBuilder.cs | 2 +- ...nyToManyAssociationEntityTypeConvention.cs | 18 +- src/EFCore/Metadata/Internal/EntityType.cs | 2 +- .../Internal/InternalEntityTypeBuilder.cs | 2 +- .../Internal/InternalForeignKeyBuilder.cs | 4 +- .../Design/CSharpMigrationsGeneratorTest.cs | 4 + .../Design/SnapshotModelProcessorTest.cs | 6 + .../Migrations/ModelSnapshotSqlServerTest.cs | 4 +- .../RelationalModelValidatorTest.cs | 25 +- .../Metadata/RelationalModelTest.cs | 148 ++++++ .../Query/NullSemanticsQuerySqlServerTest.cs | 6 +- 72 files changed, 1914 insertions(+), 682 deletions(-) create mode 100644 src/EFCore.Relational/Metadata/ISqlQuery.cs create mode 100644 src/EFCore.Relational/Metadata/ISqlQueryColumn.cs create mode 100644 src/EFCore.Relational/Metadata/ISqlQueryColumnMapping.cs create mode 100644 src/EFCore.Relational/Metadata/ISqlQueryMapping.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/ColumnBase.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/ColumnMappingBase.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/SqlQuery.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/SqlQueryColumn.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/SqlQueryColumnMapping.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/SqlQueryMapping.cs create mode 100644 src/EFCore.Relational/Metadata/SqlQueryColumnExtensions.cs create mode 100644 src/EFCore.Relational/Metadata/SqlQueryColumnMappingExtensions.cs create mode 100644 src/EFCore.Relational/Metadata/SqlQueryExtensions.cs create mode 100644 src/EFCore.Relational/Metadata/SqlQueryMappingExtensions.cs diff --git a/src/EFCore.InMemory/Extensions/InMemoryEntityTypeBuilderExtensions.cs b/src/EFCore.InMemory/Extensions/InMemoryEntityTypeBuilderExtensions.cs index 86745b72f80..ecfd3cc15f6 100644 --- a/src/EFCore.InMemory/Extensions/InMemoryEntityTypeBuilderExtensions.cs +++ b/src/EFCore.InMemory/Extensions/InMemoryEntityTypeBuilderExtensions.cs @@ -44,7 +44,7 @@ public static EntityTypeBuilder ToQuery( /// /// The same builder instance if the query was set, otherwise. /// - public static IConventionEntityTypeBuilder HasDefiningQuery( + public static IConventionEntityTypeBuilder ToQuery( [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [CanBeNull] LambdaExpression query, bool fromDataAnnotation = false) diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index c921dfeb922..5310643286b 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -33,12 +33,16 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator RelationalAnnotationNames.CheckConstraints, RelationalAnnotationNames.Sequences, RelationalAnnotationNames.DbFunctions, + RelationalAnnotationNames.DefaultMappings, + RelationalAnnotationNames.DefaultColumnMappings, RelationalAnnotationNames.TableMappings, RelationalAnnotationNames.TableColumnMappings, RelationalAnnotationNames.ViewMappings, RelationalAnnotationNames.ViewColumnMappings, RelationalAnnotationNames.FunctionMappings, RelationalAnnotationNames.FunctionColumnMappings, + RelationalAnnotationNames.SqlQueryMappings, + RelationalAnnotationNames.SqlQueryColumnMappings, RelationalAnnotationNames.ForeignKeyMappings, RelationalAnnotationNames.TableIndexMappings, RelationalAnnotationNames.UniqueConstraintMappings, @@ -83,7 +87,6 @@ public virtual void RemoveAnnotationsHandledByConventions( IProperty property, IDictionary annotations) { var columnName = property.GetColumnName(); - if (columnName == property.Name) { annotations.Remove(RelationalAnnotationNames.ColumnName); diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs index 3c2a3b56aec..c9783f481e2 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -282,8 +281,7 @@ public static OwnedNavigationBuilder ToTable The name of the table. /// Indicates whether the configuration was specified using a data annotation. /// - /// The same builder instance if the configuration was applied, - /// otherwise. + /// The same builder instance if the configuration was applied, otherwise. /// public static IConventionEntityTypeBuilder ToTable( [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [CanBeNull] string name, bool fromDataAnnotation = false) @@ -305,8 +303,7 @@ public static IConventionEntityTypeBuilder ToTable( /// The schema of the table. /// Indicates whether the configuration was specified using a data annotation. /// - /// The same builder instance if the configuration was applied, - /// otherwise. + /// The same builder instance if the configuration was applied, otherwise. /// public static IConventionEntityTypeBuilder ToTable( [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, @@ -325,11 +322,11 @@ public static IConventionEntityTypeBuilder ToTable( } /// - /// Returns a value indicating whether the view or table name can be set for this entity type + /// Returns a value indicating whether the table name can be set for this entity type /// using the specified configuration source. /// /// The builder for the entity type being configured. - /// The name of the view or table. + /// The name of the table. /// Indicates whether the configuration was specified using a data annotation. /// if the configuration can be applied. public static bool CanSetTable( @@ -341,14 +338,13 @@ public static bool CanSetTable( } /// - /// Configures the schema of the view or table that the entity type maps to when targeting a relational database. + /// Configures the schema of the table that the entity type maps to when targeting a relational database. /// /// The builder for the entity type being configured. - /// The schema of the view or table. + /// The schema of the table. /// Indicates whether the configuration was specified using a data annotation. /// - /// The same builder instance if the configuration was applied, - /// otherwise. + /// The same builder instance if the configuration was applied, otherwise. /// public static IConventionEntityTypeBuilder ToSchema( [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, @@ -365,11 +361,11 @@ public static IConventionEntityTypeBuilder ToSchema( } /// - /// Returns a value indicating whether the schema of the view or table name can be set for this entity type + /// Returns a value indicating whether the schema of the table name can be set for this entity type /// using the specified configuration source. /// /// The builder for the entity type being configured. - /// The schema of the view or table. + /// The schema of the table. /// Indicates whether the configuration was specified using a data annotation. /// if the configuration can be applied. public static bool CanSetSchema( @@ -481,6 +477,167 @@ public static EntityTypeBuilder ToView( where TEntity : class => (EntityTypeBuilder)ToView((EntityTypeBuilder)entityTypeBuilder, name, schema); + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + public static IConventionEntityTypeBuilder ToView( + [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [CanBeNull] string name, bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanSetView(name, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.SetViewName(name, fromDataAnnotation); + return entityTypeBuilder; + } + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// The schema of the view. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + public static IConventionEntityTypeBuilder ToView( + [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, + [CanBeNull] string name, [CanBeNull] string schema, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanSetView(name, fromDataAnnotation) + || !entityTypeBuilder.CanSetViewSchema(schema, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.SetViewName(name, fromDataAnnotation); + entityTypeBuilder.Metadata.SetViewSchema(schema, fromDataAnnotation); + return entityTypeBuilder; + } + + /// + /// Returns a value indicating whether the view name can be set for this entity type + /// using the specified configuration source. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + public static bool CanSetView( + [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [CanBeNull] string name, bool fromDataAnnotation = false) + { + Check.NullButNotEmpty(name, nameof(name)); + + return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.ViewName, name, fromDataAnnotation); + } + + /// + /// Configures the schema of the view that the entity type maps to when targeting a relational database. + /// + /// The builder for the entity type being configured. + /// The schema of the view. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + public static IConventionEntityTypeBuilder ToViewSchema( + [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, + [CanBeNull] string schema, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanSetSchema(schema, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.SetViewSchema(schema, fromDataAnnotation); + return entityTypeBuilder; + } + + /// + /// Returns a value indicating whether the schema of the view can be set for this entity type + /// using the specified configuration source. + /// + /// The builder for the entity type being configured. + /// The schema of the view. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + public static bool CanSetViewSchema( + [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, + [CanBeNull] string schema, + bool fromDataAnnotation = false) + { + Check.NullButNotEmpty(schema, nameof(schema)); + + return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.ViewSchema, schema, fromDataAnnotation); + } + + /// + /// Configures a SQL string used to provide data for the entity type. + /// + /// The builder for the entity type being configured. + /// The SQL query that will provide the underlying data for the entity type. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToQuerySql( + [NotNull] this EntityTypeBuilder entityTypeBuilder, + [NotNull] string query) + where TEntity : class + { + Check.NotNull(query, nameof(query)); + + entityTypeBuilder.Metadata.SetQuerySql(query); + + return entityTypeBuilder; + } + + /// + /// Configures a SQL string used to provide data for the entity type. + /// + /// The builder for the entity type being configured. + /// The SQL query that will provide the underlying data for the entity type. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + public static IConventionEntityTypeBuilder ToQuerySql( + [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [CanBeNull] string name, bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanSetQuerySql(name, fromDataAnnotation)) + { + return null; + } + + var entityType = entityTypeBuilder.Metadata; + entityType.SetQuerySql(name, fromDataAnnotation); + + return entityTypeBuilder; + } + + /// + /// Returns a value indicating whether the query SQL string can be set for this entity type + /// using the specified configuration source. + /// + /// The builder for the entity type being configured. + /// The SQL query that will provide the underlying data for the entity type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + public static bool CanSetQuerySql( + [NotNull] this IConventionEntityTypeBuilder entityTypeBuilder, [CanBeNull] string name, bool fromDataAnnotation = false) + { + Check.NullButNotEmpty(name, nameof(name)); + + return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.QuerySql, name, fromDataAnnotation); + } + /// /// Configures the function that the entity type maps to when targeting a relational database. /// diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index b5c7d3c3849..a77277ccbaa 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -42,6 +42,7 @@ public static string GetTableName([NotNull] this IEntityType entityType) #pragma warning disable CS0618 // Type or member is obsolete && ((entityType as IConventionEntityType)?.GetDefiningQueryConfigurationSource() == null) #pragma warning restore CS0618 // Type or member is obsolete + && ((entityType as IConventionEntityType)?.GetQuerySqlConfigurationSource() == null) ? GetDefaultTableName(entityType) : null; } @@ -222,6 +223,15 @@ public static string GetSchemaQualifiedViewName([NotNull] this IEntityType entit return (string.IsNullOrEmpty(schema) ? "" : schema + ".") + viewName; } + /// + /// Returns the default mappings that the entity type would use. + /// + /// The entity type to get the table mappings for. + /// The tables to which the entity type is mapped. + public static IEnumerable GetDefaultMappings([NotNull] this IEntityType entityType) => + (IEnumerable)entityType[RelationalAnnotationNames.DefaultMappings] + ?? Array.Empty(); + /// /// Returns the tables to which the entity type is mapped. /// @@ -253,6 +263,7 @@ public static string GetViewName([NotNull] this IEntityType entityType) #pragma warning disable CS0618 // Type or member is obsolete && (entityType as IConventionEntityType)?.GetDefiningQueryConfigurationSource() == null #pragma warning restore CS0618 // Type or member is obsolete + && ((entityType as IConventionEntityType)?.GetQuerySqlConfigurationSource() == null) ? GetDefaultViewName(entityType) : null; } @@ -389,6 +400,78 @@ public static IEnumerable GetViewMappings([NotNull] this IEntityTy (IEnumerable)entityType[RelationalAnnotationNames.ViewMappings] ?? Array.Empty(); + /// + /// Gets the default SQL query name that would be used for this entity type when mapped using + /// . + /// + /// The entity type. + /// Gets the default SQL query name. + public static string GetDefaultSqlQueryName([NotNull] this IEntityType entityType) + => entityType.Name + "." + SqlQueryExtensions.DefaultQueryNameBase; + + /// + /// Returns the SQL string used to provide data for the entity type or if not mapped to a SQL string. + /// + /// The entity type. + /// The SQL string used to provide data for the entity type. + public static string GetQuerySql([NotNull] this IEntityType entityType) + { + var nameAnnotation = (string)entityType[RelationalAnnotationNames.QuerySql]; + if (nameAnnotation != null) + { + return nameAnnotation; + } + + if (entityType.BaseType != null) + { + return entityType.GetRootType().GetQuerySql(); + } + + return null; + } + + /// + /// Sets the SQL string used to provide data for the entity type. + /// + /// The entity type. + /// The SQL string to set. + public static void SetQuerySql([NotNull] this IMutableEntityType entityType, [CanBeNull] string name) + => entityType.SetAnnotation( + RelationalAnnotationNames.QuerySql, + Check.NullButNotEmpty(name, nameof(name))); + + /// + /// Sets the SQL string used to provide data for the entity type. + /// + /// The entity type. + /// The SQL string to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static string SetQuerySql( + [NotNull] this IConventionEntityType entityType, [CanBeNull] string name, bool fromDataAnnotation = false) + => (string)entityType.SetAnnotation( + RelationalAnnotationNames.QuerySql, + Check.NullButNotEmpty(name, nameof(name)), + fromDataAnnotation)?.Value; + + /// + /// Gets the for the query SQL string. + /// + /// The entity type to find configuration source for. + /// The for the query SQL string. + public static ConfigurationSource? GetQuerySqlConfigurationSource([NotNull] this IConventionEntityType entityType) + => entityType.FindAnnotation(RelationalAnnotationNames.QuerySql) + ?.GetConfigurationSource(); + + /// + /// Returns the SQL string mappings. + /// + /// The entity type to get the function mappings for. + /// The functions to which the entity type is mapped. + public static IEnumerable GetSqlQueryMappings([NotNull] this IEntityType entityType) => + (IEnumerable)entityType[RelationalAnnotationNames.SqlQueryMappings] + ?? Array.Empty(); + /// /// Returns the name of the function to which the entity type is mapped or if not mapped to a function. /// diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index 9f6fc8f6511..f7b49d71636 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -304,6 +304,15 @@ public static string SetColumnType( public static ConfigurationSource? GetColumnTypeConfigurationSource([NotNull] this IConventionProperty property) => property.FindAnnotation(RelationalAnnotationNames.ColumnType)?.GetConfigurationSource(); + /// + /// Returns the default columns to which the property would be mapped. + /// + /// The property. + /// The default columns to which the property would be mapped. + public static IEnumerable GetDefaultColumnMappings([NotNull] this IProperty property) => + (IEnumerable)property[RelationalAnnotationNames.DefaultColumnMappings] + ?? Enumerable.Empty(); + /// /// Returns the table columns to which the property is mapped. /// @@ -322,6 +331,15 @@ public static IEnumerable GetViewColumnMappings([NotNull] th (IEnumerable)property[RelationalAnnotationNames.ViewColumnMappings] ?? Enumerable.Empty(); + /// + /// Returns the SQL query columns to which the property is mapped. + /// + /// The property. + /// The SQL query columns to which the property is mapped. + public static IEnumerable GetSqlQueryColumnMappings([NotNull] this IProperty property) => + (IEnumerable)property[RelationalAnnotationNames.SqlQueryColumnMappings] + ?? Enumerable.Empty(); + /// /// Returns the function columns to which the property is mapped. /// @@ -349,6 +367,10 @@ public static IColumnBase FindColumn([NotNull] this IProperty property, StoreObj return property.GetViewColumnMappings() .Where(m => m.ViewMapping.Table.Name == storeObject.Name && m.ViewMapping.Table.Schema == storeObject.Schema) .FirstOrDefault()?.Column; + case StoreObjectType.SqlQuery: + return property.GetSqlQueryColumnMappings() + .Where(m => m.SqlQueryMapping.SqlQuery.Name == storeObject.Name) + .FirstOrDefault()?.Column; case StoreObjectType.Function: return property.GetFunctionColumnMappings() .Where(m => m.FunctionMapping.DbFunction.Name == storeObject.Name) @@ -750,9 +772,8 @@ public static void SetIsFixedLength([NotNull] this IMutableProperty property, bo /// The . /// if the mapped column is nullable; otherwise. public static bool IsColumnNullable([NotNull] this IProperty property) - => !property.IsPrimaryKey() - && (property.IsNullable - || (property.DeclaringEntityType.BaseType != null && property.DeclaringEntityType.GetDiscriminatorProperty() != null)); + => property.IsNullable + || (property.DeclaringEntityType.BaseType != null && property.DeclaringEntityType.GetDiscriminatorProperty() != null); /// /// diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index ae93a6645a0..a19c7f5d98b 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -57,14 +57,42 @@ public override void Validate(IModel model, IDiagnosticsLogger + /// Validates the mapping/configuration of SQL queries in the model. + /// + /// The model to validate. + /// The logger to use. + protected virtual void ValidateSqlQueries( + [NotNull] IModel model, [NotNull] IDiagnosticsLogger logger) + { + foreach (var entityType in model.GetEntityTypes()) + { + var sqlQuery = entityType.GetQuerySql(); + if (sqlQuery == null) + { + continue; + } + + if (entityType.BaseType != null + && (entityType.GetDiscriminatorProperty() == null + || sqlQuery != entityType.BaseType.GetQuerySql())) + { + throw new InvalidOperationException( + RelationalStrings.InvalidMappedSqlQueryDerivedType( + entityType.DisplayName(), entityType.BaseType.DisplayName())); + } + } + } + /// /// Validates the mapping/configuration of functions in the model. /// @@ -980,15 +1008,6 @@ protected virtual void ValidatePropertyOverrides( switch (storeOverride.StoreObjectType) { case StoreObjectType.Table: - if (entityType.GetTableName() == name - && entityType.GetSchema() == schema) - { - throw new InvalidOperationException(RelationalStrings.TableOverrideDeclaredTable( - property.Name, - (schema == null ? "" : schema + ".") + name, - entityType.DisplayName())); - } - if (!entityType.GetDerivedTypes().Any(d => d.GetTableName() == name && d.GetSchema() == schema)) @@ -999,38 +1018,27 @@ protected virtual void ValidatePropertyOverrides( } break; case StoreObjectType.View: - if (entityType.GetViewName() == name - && entityType.GetViewSchema() == schema) - { - throw new InvalidOperationException(RelationalStrings.TableOverrideDeclaredTable( - property.Name, - (schema == null ? "" : schema + ".") + name, - entityType.DisplayName())); - } - if (!entityType.GetDerivedTypes().Any(d => d.GetViewName() == name && d.GetViewSchema() == schema)) { - throw new InvalidOperationException(RelationalStrings.TableOverrideMismatch( + throw new InvalidOperationException(RelationalStrings.ViewOverrideMismatch( entityType.DisplayName() + "." + property.Name, (schema == null ? "" : schema + ".") + name)); } break; - case StoreObjectType.Function: - if (entityType.GetFunctionName() == name) + case StoreObjectType.SqlQuery: + if (!entityType.GetDerivedTypes().Any(d => d.GetDefaultSqlQueryName() == name)) { - throw new InvalidOperationException(RelationalStrings.TableOverrideDeclaredTable( - property.Name, - name, - entityType.DisplayName())); + throw new InvalidOperationException(RelationalStrings.SqlQueryOverrideMismatch( + entityType.DisplayName() + "." + property.Name, name)); } - + break; + case StoreObjectType.Function: if (!entityType.GetDerivedTypes().Any(d => d.GetFunctionName() == name)) { - throw new InvalidOperationException(RelationalStrings.TableOverrideMismatch( - entityType.DisplayName() + "." + property.Name, - (schema == null ? "" : schema + ".") + name)); + throw new InvalidOperationException(RelationalStrings.FunctionOverrideMismatch( + entityType.DisplayName() + "." + property.Name, name)); } break; default: diff --git a/src/EFCore.Relational/Metadata/ForeignKeyConstraintExtensions.cs b/src/EFCore.Relational/Metadata/ForeignKeyConstraintExtensions.cs index f9e9d7cd0ba..d3553989b2c 100644 --- a/src/EFCore.Relational/Metadata/ForeignKeyConstraintExtensions.cs +++ b/src/EFCore.Relational/Metadata/ForeignKeyConstraintExtensions.cs @@ -47,11 +47,11 @@ public static string ToDebugString( .Append(" ") .Append(foreignKey.Table.Name) .Append(" ") - .Append(Column.Format(foreignKey.Columns)) + .Append(ColumnBase.Format(foreignKey.Columns)) .Append(" -> ") .Append(foreignKey.PrincipalTable.Name) .Append(" ") - .Append(Column.Format(foreignKey.PrincipalColumns)); + .Append(ColumnBase.Format(foreignKey.PrincipalColumns)); if (foreignKey.OnDeleteAction != ReferentialAction.NoAction) { diff --git a/src/EFCore.Relational/Metadata/FunctionColumnExtensions.cs b/src/EFCore.Relational/Metadata/FunctionColumnExtensions.cs index 7533b512e41..49e5bb86442 100644 --- a/src/EFCore.Relational/Metadata/FunctionColumnExtensions.cs +++ b/src/EFCore.Relational/Metadata/FunctionColumnExtensions.cs @@ -38,7 +38,7 @@ public static string ToDebugString( var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; if (singleLine) { - builder.Append($"Column: {column.Table.Name}."); + builder.Append($"FunctionColumn: {column.Table.Name}."); } builder.Append(column.Name).Append(" ("); diff --git a/src/EFCore.Relational/Metadata/IFunctionMapping.cs b/src/EFCore.Relational/Metadata/IFunctionMapping.cs index 44d09ee5187..ff67a0ff433 100644 --- a/src/EFCore.Relational/Metadata/IFunctionMapping.cs +++ b/src/EFCore.Relational/Metadata/IFunctionMapping.cs @@ -12,7 +12,7 @@ public interface IFunctionMapping : ITableMappingBase { /// /// Gets the value indicating whether this is the function mapping - /// that should be used first when the entity type is queried. + /// that should be used when the entity type is queried. /// bool IsDefaultFunctionMapping { get; } diff --git a/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs b/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs index 7c876089c23..feb9e1b5f44 100644 --- a/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs +++ b/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs @@ -56,6 +56,34 @@ public interface IRelationalAnnotationProvider /// The annotations. IEnumerable For([NotNull] IViewColumn column); + /// + /// Gets provider-specific annotations for the given . + /// + /// The SQL query. + /// The annotations. + IEnumerable For([NotNull] ISqlQuery sqlQuery); + + /// + /// Gets provider-specific annotations for the given . + /// + /// The column. + /// The annotations. + IEnumerable For([NotNull] ISqlQueryColumn column); + + /// + /// Gets provider-specific annotations for the given . + /// + /// The function. + /// The annotations. + IEnumerable For([NotNull] IStoreFunction function); + + /// + /// Gets provider-specific annotations for the given . + /// + /// The column. + /// The annotations. + IEnumerable For([NotNull] IFunctionColumn column); + /// /// Gets provider-specific annotations for the given . /// diff --git a/src/EFCore.Relational/Metadata/IRelationalModel.cs b/src/EFCore.Relational/Metadata/IRelationalModel.cs index f9771bb5870..82bc3143a90 100644 --- a/src/EFCore.Relational/Metadata/IRelationalModel.cs +++ b/src/EFCore.Relational/Metadata/IRelationalModel.cs @@ -28,6 +28,12 @@ public interface IRelationalModel : IAnnotatable /// All the views mapped in the model. IEnumerable Views { get; } + /// + /// Returns all the SQL queries mapped in the model. + /// + /// All the SQL queries mapped in the model. + IEnumerable Queries { get; } + /// /// Returns all sequences contained in the model. /// @@ -59,6 +65,13 @@ public interface IRelationalModel : IAnnotatable /// The view with a given name or if no view with the given name is defined. IView FindView([NotNull] string name, [CanBeNull] string schema); + /// + /// Gets the SQL query with the given name. Returns if no SQL query with the given name is defined. + /// + /// The name of the SQL query. + /// The SQL query with a given name or if no SQL query with the given name is defined. + ISqlQuery FindQuery([NotNull] string name); + /// /// Finds an with the given name. /// diff --git a/src/EFCore.Relational/Metadata/ISqlQuery.cs b/src/EFCore.Relational/Metadata/ISqlQuery.cs new file mode 100644 index 00000000000..048903291a3 --- /dev/null +++ b/src/EFCore.Relational/Metadata/ISqlQuery.cs @@ -0,0 +1,39 @@ +// 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 JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + /// + /// Represents a SQL query string. + /// + public interface ISqlQuery : ITableBase + { + /// + /// Gets the entity type mappings. + /// + new IEnumerable EntityTypeMappings { get; } + + /// + /// Gets the columns defined for this query. + /// + new IEnumerable Columns { get; } + + /// + /// Gets the column with the given name. Returns if no column with the given name is defined. + /// + new ISqlQueryColumn FindColumn([NotNull] string name); + + /// + /// Gets the column mapped to the given property. Returns if no column is mapped to the given property. + /// + new ISqlQueryColumn FindColumn([NotNull] IProperty property); + + /// + /// Gets the SQL query string. + /// + public string Sql { get; } + } +} diff --git a/src/EFCore.Relational/Metadata/ISqlQueryColumn.cs b/src/EFCore.Relational/Metadata/ISqlQueryColumn.cs new file mode 100644 index 00000000000..bc8575859e9 --- /dev/null +++ b/src/EFCore.Relational/Metadata/ISqlQueryColumn.cs @@ -0,0 +1,23 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + /// + /// Represents a column in a SQL query. + /// + public interface ISqlQueryColumn : IColumnBase + { + /// + /// Gets the containing SQL query. + /// + ISqlQuery SqlQuery { get; } + + /// + /// Gets the property mappings. + /// + new IEnumerable PropertyMappings { get; } + } +} diff --git a/src/EFCore.Relational/Metadata/ISqlQueryColumnMapping.cs b/src/EFCore.Relational/Metadata/ISqlQueryColumnMapping.cs new file mode 100644 index 00000000000..8f18ff4ca94 --- /dev/null +++ b/src/EFCore.Relational/Metadata/ISqlQueryColumnMapping.cs @@ -0,0 +1,21 @@ +// 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. + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + /// + /// Represents property mapping to a SQL query column. + /// + public interface ISqlQueryColumnMapping : IColumnMappingBase + { + /// + /// Gets the target column. + /// + new ISqlQueryColumn Column { get; } + + /// + /// Gets the containing SQL query mapping. + /// + ISqlQueryMapping SqlQueryMapping { get; } + } +} diff --git a/src/EFCore.Relational/Metadata/ISqlQueryMapping.cs b/src/EFCore.Relational/Metadata/ISqlQueryMapping.cs new file mode 100644 index 00000000000..68809bd904c --- /dev/null +++ b/src/EFCore.Relational/Metadata/ISqlQueryMapping.cs @@ -0,0 +1,29 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + /// + /// Represents entity type mapping to a SQL query. + /// + public interface ISqlQueryMapping : ITableMappingBase + { + /// + /// Gets the value indicating whether this is the SQL query mapping + /// that should be used when the entity type is queried. + /// + bool IsDefaultSqlQueryMapping { get; set; } + + /// + /// Gets the target SQL query. + /// + ISqlQuery SqlQuery { get; } + + /// + /// Gets the properties mapped to columns on the target SQL query. + /// + new IEnumerable ColumnMappings { get; } + } +} diff --git a/src/EFCore.Relational/Metadata/IStoreFunction.cs b/src/EFCore.Relational/Metadata/IStoreFunction.cs index 68998e4ba61..b6d37e9ec88 100644 --- a/src/EFCore.Relational/Metadata/IStoreFunction.cs +++ b/src/EFCore.Relational/Metadata/IStoreFunction.cs @@ -46,5 +46,10 @@ public interface IStoreFunction : ITableBase /// if no column with the given name is defined for the returned row set. /// new IFunctionColumn FindColumn([NotNull] string name); + + /// + /// Gets the column mapped to the given property. Returns if no column is mapped to the given property. + /// + new IFunctionColumn FindColumn([NotNull] IProperty property); } } diff --git a/src/EFCore.Relational/Metadata/ITable.cs b/src/EFCore.Relational/Metadata/ITable.cs index 3cb497ac2a8..b65851825f9 100644 --- a/src/EFCore.Relational/Metadata/ITable.cs +++ b/src/EFCore.Relational/Metadata/ITable.cs @@ -66,5 +66,10 @@ public virtual string Comment /// Gets the column with a given name. Returns if no column with the given name is defined. /// new IColumn FindColumn([NotNull] string name); + + /// + /// Gets the column mapped to the given property. Returns if no column is mapped to the given property. + /// + new IColumn FindColumn([NotNull] IProperty property); } } diff --git a/src/EFCore.Relational/Metadata/ITableBase.cs b/src/EFCore.Relational/Metadata/ITableBase.cs index f1421749dc6..61ac2f53b3d 100644 --- a/src/EFCore.Relational/Metadata/ITableBase.cs +++ b/src/EFCore.Relational/Metadata/ITableBase.cs @@ -47,6 +47,11 @@ public interface ITableBase : IAnnotatable /// IColumnBase FindColumn([NotNull] string name); + /// + /// Gets the column mapped to the given property. Returns if no column is mapped to the given property. + /// + IColumnBase FindColumn([NotNull] IProperty property); + /// /// Gets the foreign keys for the given entity type that point to other entity types sharing this table. /// diff --git a/src/EFCore.Relational/Metadata/IView.cs b/src/EFCore.Relational/Metadata/IView.cs index b6a1cbc3e19..884231487b1 100644 --- a/src/EFCore.Relational/Metadata/IView.cs +++ b/src/EFCore.Relational/Metadata/IView.cs @@ -26,6 +26,11 @@ public interface IView : ITableBase /// new IViewColumn FindColumn([NotNull] string name); + /// + /// Gets the column mapped to the given property. Returns if no column is mapped to the given property. + /// + new IViewColumn FindColumn([NotNull] IProperty property); + /// /// Gets the view definition or if this view is not managed by migrations. /// diff --git a/src/EFCore.Relational/Metadata/Internal/Column.cs b/src/EFCore.Relational/Metadata/Internal/Column.cs index 94be55428ed..c8d6a53f1c3 100644 --- a/src/EFCore.Relational/Metadata/Internal/Column.cs +++ b/src/EFCore.Relational/Metadata/Internal/Column.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 Column : Annotatable, IColumn + public class Column : ColumnBase, IColumn { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -24,31 +24,12 @@ public class Column : Annotatable, IColumn /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public Column([NotNull] string name, [CanBeNull] string type, [NotNull] Table table) + : base(name, type, table) { - Name = name; - StoreType = type; - Table = table; } /// - public virtual string Name { get; } - - /// - public virtual ITable Table { get; } - - /// - public virtual string StoreType { get; } - - /// - public virtual bool IsNullable { get; set; } - - /// - /// 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 SortedSet PropertyMappings { get; } = new SortedSet(ColumnMappingBaseComparer.Instance); + public new virtual ITable Table => (ITable)base.Table; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -58,38 +39,11 @@ public Column([NotNull] string name, [CanBeNull] string type, [NotNull] Table ta /// public override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); - /// - /// 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 static string Format([NotNull] IEnumerable columns) - => "{" - + string.Join( - ", ", - columns.Select(p => "'" + p.Name + "'")) - + "}"; - /// IEnumerable IColumn.PropertyMappings { [DebuggerStepThrough] - get => PropertyMappings; - } - - /// - IEnumerable IColumnBase.PropertyMappings - { - [DebuggerStepThrough] - get => PropertyMappings; - } - - /// - ITableBase IColumnBase.Table - { - [DebuggerStepThrough] - get => Table; + get => PropertyMappings.Cast(); } } } diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnBase.cs b/src/EFCore.Relational/Metadata/Internal/ColumnBase.cs new file mode 100644 index 00000000000..8a008f74afb --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/ColumnBase.cs @@ -0,0 +1,74 @@ +// 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 System.Diagnostics; +using System.Linq; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Metadata.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 ColumnBase : Annotatable, IColumnBase + { + /// + /// 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 ColumnBase([NotNull] string name, [NotNull] string type, [NotNull] TableBase table) + { + Name = name; + StoreType = type; + Table = table; + } + + /// + public virtual string Name { get; } + + /// + public virtual ITableBase Table { get; } + + /// + public virtual string StoreType { get; } + + /// + public virtual bool IsNullable { get; set; } + + /// + /// 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 SortedSet PropertyMappings { get; } + = new SortedSet(ColumnMappingBaseComparer.Instance); + + /// + /// 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 static string Format([NotNull] IEnumerable columns) + => "{" + + string.Join( + ", ", + columns.Select(p => "'" + p.Name + "'")) + + "}"; + + /// + IEnumerable IColumnBase.PropertyMappings + { + [DebuggerStepThrough] + get => PropertyMappings; + } + } +} diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnMapping.cs b/src/EFCore.Relational/Metadata/Internal/ColumnMapping.cs index 2756ffe86a6..85903e13406 100644 --- a/src/EFCore.Relational/Metadata/Internal/ColumnMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/ColumnMapping.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 ColumnMapping : Annotatable, IColumnMapping + public class ColumnMapping : ColumnMappingBase, IColumnMapping { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -27,24 +27,12 @@ public ColumnMapping( [NotNull] Column column, [CanBeNull] RelationalTypeMapping typeMapping, [NotNull] TableMapping tableMapping) + : base(property, column, typeMapping, tableMapping) { - Property = property; - Column = column; - TypeMapping = typeMapping; - TableMapping = tableMapping; } /// - public virtual IProperty Property { get; } - - /// - public virtual IColumn Column { get; } - - /// - public virtual RelationalTypeMapping TypeMapping { get; } - - /// - public virtual ITableMapping TableMapping { get; } + public new virtual ITableMapping TableMapping => (ITableMapping)base.TableMapping; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -55,17 +43,10 @@ public ColumnMapping( public override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); /// - IColumnBase IColumnMappingBase.Column - { - [DebuggerStepThrough] - get => Column; - } - - /// - ITableMappingBase IColumnMappingBase.TableMapping + IColumn IColumnMapping.Column { [DebuggerStepThrough] - get => TableMapping; + get => (IColumn)Column; } } } diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnMappingBase.cs b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBase.cs new file mode 100644 index 00000000000..efdec9f4782 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBase.cs @@ -0,0 +1,48 @@ +// 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 JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Microsoft.EntityFrameworkCore.Metadata.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 ColumnMappingBase : Annotatable, IColumnMappingBase + { + /// + /// 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 ColumnMappingBase( + [NotNull] IProperty property, + [NotNull] ColumnBase column, + [NotNull] RelationalTypeMapping typeMapping, + [NotNull] TableMappingBase tableMapping) + { + Property = property; + Column = column; + TypeMapping = typeMapping; + TableMapping = tableMapping; + } + + /// + public virtual IProperty Property { get; } + + /// + public virtual IColumnBase Column { get; } + + /// + public virtual RelationalTypeMapping TypeMapping { get; } + + /// + public virtual ITableMappingBase TableMapping { get; } + } +} diff --git a/src/EFCore.Relational/Metadata/Internal/FunctionColumn.cs b/src/EFCore.Relational/Metadata/Internal/FunctionColumn.cs index dafbabfe792..ce3f94b78ac 100644 --- a/src/EFCore.Relational/Metadata/Internal/FunctionColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/FunctionColumn.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -14,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 FunctionColumn : Annotatable, IFunctionColumn + public class FunctionColumn : ColumnBase, IFunctionColumn { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -22,33 +23,13 @@ public class FunctionColumn : Annotatable, IFunctionColumn /// 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 FunctionColumn([NotNull] string name, [NotNull] string type, [NotNull] IStoreFunction function) + public FunctionColumn([NotNull] string name, [NotNull] string type, [NotNull] StoreFunction function) + : base(name, type, function) { - Name = name; - StoreType = type; - Function = function; } /// - public virtual string Name { get; } - - /// - public virtual IStoreFunction Function { get; } - - /// - public virtual string StoreType { get; } - - /// - public virtual bool IsNullable { get; set; } - - /// - /// 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 SortedSet PropertyMappings { get; } - = new SortedSet(ColumnMappingBaseComparer.Instance); + public virtual IStoreFunction Function => (IStoreFunction)Table; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -62,21 +43,7 @@ public FunctionColumn([NotNull] string name, [NotNull] string type, [NotNull] IS IEnumerable IFunctionColumn.PropertyMappings { [DebuggerStepThrough] - get => PropertyMappings; - } - - /// - IEnumerable IColumnBase.PropertyMappings - { - [DebuggerStepThrough] - get => PropertyMappings; - } - - /// - ITableBase IColumnBase.Table - { - [DebuggerStepThrough] - get => Function; + get => PropertyMappings.Cast(); } } } diff --git a/src/EFCore.Relational/Metadata/Internal/FunctionColumnMapping.cs b/src/EFCore.Relational/Metadata/Internal/FunctionColumnMapping.cs index a2edf45f417..0e74ea6f8e9 100644 --- a/src/EFCore.Relational/Metadata/Internal/FunctionColumnMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/FunctionColumnMapping.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 FunctionColumnMapping : Annotatable, IFunctionColumnMapping + public class FunctionColumnMapping : ColumnMappingBase, IFunctionColumnMapping { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -27,24 +27,12 @@ public FunctionColumnMapping( [NotNull] FunctionColumn column, [NotNull] RelationalTypeMapping typeMapping, [NotNull] FunctionMapping viewMapping) + : base(property, column, typeMapping, viewMapping) { - Property = property; - Column = column; - TypeMapping = typeMapping; - FunctionMapping = viewMapping; } /// - public virtual IProperty Property { get; } - - /// - public virtual IFunctionColumn Column { get; } - - /// - public virtual RelationalTypeMapping TypeMapping { get; } - - /// - public virtual IFunctionMapping FunctionMapping { get; } + public virtual IFunctionMapping FunctionMapping => (IFunctionMapping)TableMapping; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -54,18 +42,10 @@ public FunctionColumnMapping( /// public override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); - /// - IColumnBase IColumnMappingBase.Column - { - [DebuggerStepThrough] - get => Column; - } - - /// - ITableMappingBase IColumnMappingBase.TableMapping + IFunctionColumn IFunctionColumnMapping.Column { [DebuggerStepThrough] - get => FunctionMapping; + get => (IFunctionColumn)Column; } } } diff --git a/src/EFCore.Relational/Metadata/Internal/FunctionMapping.cs b/src/EFCore.Relational/Metadata/Internal/FunctionMapping.cs index 640d3b4b94e..c0bc8950483 100644 --- a/src/EFCore.Relational/Metadata/Internal/FunctionMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/FunctionMapping.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -41,18 +42,6 @@ public FunctionMapping( /// public virtual IDbFunction DbFunction { get; } - /// - /// 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 SortedSet ColumnMappings { get; } - = new SortedSet(ColumnMappingBaseComparer.Instance); - - /// - protected override IEnumerable ProtectedColumnMappings => ColumnMappings; - /// /// 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 @@ -65,14 +54,7 @@ public FunctionMapping( IEnumerable IFunctionMapping.ColumnMappings { [DebuggerStepThrough] - get => ColumnMappings; - } - - /// - IEnumerable ITableMappingBase.ColumnMappings - { - [DebuggerStepThrough] - get => ColumnMappings; + get => ColumnMappings.Cast(); } } } diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index ff783ab10bf..76afd038dbd 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -34,6 +34,15 @@ public RelationalModel([NotNull] IModel model) /// public virtual IModel Model { get; } + /// + /// 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 SortedDictionary DefaultTables { get; } + = new SortedDictionary(); + /// /// 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 @@ -52,6 +61,15 @@ public RelationalModel([NotNull] IModel model) public virtual SortedDictionary<(string, string), View> Views { get; } = new SortedDictionary<(string, string), View>(); + /// + /// 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 SortedDictionary Queries { get; } + = new SortedDictionary(); + /// /// 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 @@ -73,6 +91,12 @@ public virtual IView FindView(string name, string schema) ? view : null; + /// + public virtual ISqlQuery FindQuery(string name) + => Queries.TryGetValue(name, out var query) + ? query + : null; + /// public virtual IStoreFunction FindFunction(string name, string schema, IReadOnlyList parameters) => Functions.TryGetValue((name, schema, parameters), out var function) @@ -98,10 +122,14 @@ public static IModel Add( foreach (var entityType in model.GetEntityTypes()) { + AddDefaultMappings(databaseModel, entityType); + AddTables(databaseModel, entityType); AddViews(databaseModel, entityType); + AddSqlQueries(databaseModel, entityType); + AddMappedFunctions(databaseModel, entityType); } @@ -114,7 +142,7 @@ public static IModel Add( if (relationalAnnotationProvider != null) { - foreach (var column in table.Columns.Values) + foreach (Column column in table.Columns.Values) { column.AddAnnotations(relationalAnnotationProvider.For(column)); } @@ -149,7 +177,7 @@ public static IModel Add( if (relationalAnnotationProvider != null) { - foreach (var viewColumn in view.Columns.Values) + foreach (ViewColumn viewColumn in view.Columns.Values) { viewColumn.AddAnnotations(relationalAnnotationProvider.For(viewColumn)); } @@ -158,6 +186,32 @@ public static IModel Add( } } + foreach (var query in databaseModel.Queries.Values) + { + if (relationalAnnotationProvider != null) + { + foreach (SqlQueryColumn queryColumn in query.Columns.Values) + { + queryColumn.AddAnnotations(relationalAnnotationProvider.For(queryColumn)); + } + + query.AddAnnotations(relationalAnnotationProvider.For(query)); + } + } + + foreach (var function in databaseModel.Functions.Values) + { + if (relationalAnnotationProvider != null) + { + foreach (FunctionColumn functionColumn in function.Columns.Values) + { + functionColumn.AddAnnotations(relationalAnnotationProvider.For(functionColumn)); + } + + function.AddAnnotations(relationalAnnotationProvider.For(function)); + } + } + if (relationalAnnotationProvider != null) { foreach (Sequence sequence in ((IRelationalModel)databaseModel).Sequences) @@ -171,6 +225,73 @@ public static IModel Add( return model; } + private static void AddDefaultMappings(RelationalModel databaseModel, IConventionEntityType entityType) + { + var name = entityType.GetRootType().FullName(); + if (!databaseModel.DefaultTables.TryGetValue(name, out var defaultTable)) + { + defaultTable = new TableBase(name, null, databaseModel); + databaseModel.DefaultTables.Add(name, defaultTable); + } + + var tableMapping = new TableMappingBase(entityType, defaultTable, includesDerivedTypes: true) + { + IsSharedTablePrincipal = true, + IsSplitEntityTypePrincipal = true + }; + + foreach (var property in entityType.GetProperties()) + { + var columnName = property.GetColumnName(); + if (columnName == null) + { + continue; + } + + var column = (ColumnBase)defaultTable.FindColumn(columnName); + if (column == null) + { + column = new ColumnBase(columnName, property.GetColumnType(), defaultTable); + column.IsNullable = property.IsColumnNullable(); + defaultTable.Columns.Add(columnName, column); + } + else if (!property.IsNullable) + { + column.IsNullable = false; + } + + var columnMapping = new ColumnMappingBase( + property, column, property.FindRelationalTypeMapping(), tableMapping); + tableMapping.ColumnMappings.Add(columnMapping); + column.PropertyMappings.Add(columnMapping); + + var columnMappings = property[RelationalAnnotationNames.DefaultColumnMappings] as SortedSet; + if (columnMappings == null) + { + columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); + property.SetAnnotation(RelationalAnnotationNames.DefaultColumnMappings, columnMappings); + } + + columnMappings.Add(columnMapping); + } + + var tableMappings = entityType[RelationalAnnotationNames.DefaultMappings] as List; + if (tableMappings == null) + { + tableMappings = new List(); + entityType.SetAnnotation(RelationalAnnotationNames.DefaultMappings, tableMappings); + } + + if (tableMapping.ColumnMappings.Count != 0 + || tableMappings.Count == 0) + { + tableMappings.Add(tableMapping); + defaultTable.EntityTypeMappings.Add(tableMapping); + } + + tableMappings.Reverse(); + } + private static void AddTables(RelationalModel databaseModel, IConventionEntityType entityType) { var tableName = entityType.GetTableName(); @@ -268,138 +389,243 @@ private static void AddTables(RelationalModel databaseModel, IConventionEntityTy } } - private static string AddViews(RelationalModel databaseModel, IConventionEntityType entityType) + private static void AddViews(RelationalModel databaseModel, IConventionEntityType entityType) { var viewName = entityType.GetViewName(); - if (viewName != null) + if (viewName == null) { - var schema = entityType.GetViewSchema(); - List viewMappings = null; - var mappedType = entityType; - while (mappedType != null) + return; + } + + var schema = entityType.GetViewSchema(); + List viewMappings = null; + var mappedType = entityType; + while (mappedType != null) + { + var mappedViewName = mappedType.GetViewName(); + var mappedSchema = mappedType.GetViewSchema(); + + if (mappedViewName == null + || (mappedViewName == viewName + && mappedSchema == schema + && mappedType != entityType)) { - var mappedViewName = mappedType.GetViewName(); - var mappedSchema = mappedType.GetViewSchema(); + break; + } - if (mappedViewName == null - || (mappedViewName == viewName - && mappedSchema == schema - && mappedType != entityType)) + if (!databaseModel.Views.TryGetValue((mappedViewName, mappedSchema), out var view)) + { + view = new View(mappedViewName, mappedSchema, databaseModel); + databaseModel.Views.Add((mappedViewName, mappedSchema), view); + } + + var mappedView = StoreObjectIdentifier.View(mappedViewName, mappedSchema); + var viewMapping = new ViewMapping(entityType, view, includesDerivedTypes: mappedType == entityType) + { + IsSplitEntityTypePrincipal = true + }; + foreach (var property in mappedType.GetProperties()) + { + var columnName = property.GetColumnName(mappedView); + if (columnName == null) { - break; + continue; } - if (!databaseModel.Views.TryGetValue((mappedViewName, mappedSchema), out var view)) + var column = (ViewColumn)view.FindColumn(columnName); + if (column == null) { - view = new View(mappedViewName, mappedSchema, databaseModel); - databaseModel.Views.Add((mappedViewName, mappedSchema), view); + column = new ViewColumn(columnName, property.GetColumnType(mappedView), view); + column.IsNullable = property.IsColumnNullable(mappedView); + view.Columns.Add(columnName, column); } - - var mappedView = StoreObjectIdentifier.View(mappedViewName, mappedSchema); - var viewMapping = new ViewMapping(entityType, view, includesDerivedTypes: mappedType == entityType) + else if (!property.IsColumnNullable(mappedView)) { - IsSplitEntityTypePrincipal = true - }; - foreach (var property in mappedType.GetProperties()) + column.IsNullable = false; + } + + var columnMapping = new ViewColumnMapping( + property, column, property.FindRelationalTypeMapping(mappedView), viewMapping); + viewMapping.ColumnMappings.Add(columnMapping); + column.PropertyMappings.Add(columnMapping); + + var columnMappings = property[RelationalAnnotationNames.ViewColumnMappings] as SortedSet; + if (columnMappings == null) { - var columnName = property.GetColumnName(mappedView); - if (columnName == null) - { - continue; - } + columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); + property.SetAnnotation(RelationalAnnotationNames.ViewColumnMappings, columnMappings); + } - var column = (ViewColumn)view.FindColumn(columnName); - if (column == null) - { - column = new ViewColumn(columnName, property.GetColumnType(mappedView), view); - column.IsNullable = property.IsColumnNullable(mappedView); - view.Columns.Add(columnName, column); - } - else if (!property.IsColumnNullable(mappedView)) - { - column.IsNullable = false; - } + columnMappings.Add(columnMapping); + } - var columnMapping = new ViewColumnMapping( - property, column, property.FindRelationalTypeMapping(mappedView), viewMapping); - viewMapping.ColumnMappings.Add(columnMapping); - column.PropertyMappings.Add(columnMapping); + mappedType = mappedType.BaseType; - var columnMappings = property[RelationalAnnotationNames.ViewColumnMappings] as SortedSet; - if (columnMappings == null) - { - columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); - property.SetAnnotation(RelationalAnnotationNames.ViewColumnMappings, columnMappings); - } + viewMappings = entityType[RelationalAnnotationNames.ViewMappings] as List; + if (viewMappings == null) + { + viewMappings = new List(); + entityType.SetAnnotation(RelationalAnnotationNames.ViewMappings, viewMappings); + } - columnMappings.Add(columnMapping); - } + if (viewMapping.ColumnMappings.Count != 0 + || viewMappings.Count == 0) + { + viewMappings.Add(viewMapping); + view.EntityTypeMappings.Add(viewMapping); + } + } - mappedType = mappedType.BaseType; + viewMappings.Reverse(); + } + + private static void AddSqlQueries(RelationalModel databaseModel, IConventionEntityType entityType) + { + var querySql = entityType.GetQuerySql(); + if (querySql == null) + { + return; + } + + List queryMappings = null; + var definingType = entityType; + while (definingType != null) + { + var mappedSql = definingType.GetQuerySql(); + if (mappedSql == null + || definingType.BaseType == null + || (mappedSql == querySql + && definingType != entityType)) + { + break; + } + + definingType = definingType.BaseType; + } + + var mappedType = entityType; + while (mappedType != null) + { + var mappedSql = mappedType.GetQuerySql(); + if (mappedSql == null + || (mappedSql == querySql + && mappedType != entityType)) + { + break; + } + + var mappedQuery = StoreObjectIdentifier.SqlQuery(definingType); + if (!databaseModel.Queries.TryGetValue(mappedQuery.Name, out var sqlQuery)) + { + sqlQuery = new SqlQuery(mappedQuery.Name, databaseModel); + sqlQuery.Sql = mappedSql; + databaseModel.Queries.Add(mappedQuery.Name, sqlQuery); + } - viewMappings = entityType[RelationalAnnotationNames.ViewMappings] as List; - if (viewMappings == null) + var queryMapping = new SqlQueryMapping(entityType, sqlQuery, includesDerivedTypes: true) + { + IsDefaultSqlQueryMapping = true, + IsSharedTablePrincipal = true, + IsSplitEntityTypePrincipal = true + }; + + foreach (var property in mappedType.GetProperties()) + { + var columnName = property.GetColumnName(mappedQuery); + if (columnName == null) { - viewMappings = new List(); - entityType.SetAnnotation(RelationalAnnotationNames.ViewMappings, viewMappings); + continue; } - if (viewMapping.ColumnMappings.Count != 0 - || viewMappings.Count == 0) + var column = (SqlQueryColumn)sqlQuery.FindColumn(columnName); + if (column == null) { - viewMappings.Add(viewMapping); - view.EntityTypeMappings.Add(viewMapping); + column = new SqlQueryColumn(columnName, property.GetColumnType(mappedQuery), sqlQuery); + column.IsNullable = property.IsColumnNullable(mappedQuery); + sqlQuery.Columns.Add(columnName, column); } + else if (!property.IsColumnNullable(mappedQuery)) + { + column.IsNullable = false; + } + + var columnMapping = new SqlQueryColumnMapping( + property, column, property.FindRelationalTypeMapping(mappedQuery), queryMapping); + queryMapping.ColumnMappings.Add(columnMapping); + column.PropertyMappings.Add(columnMapping); + + var columnMappings = property[RelationalAnnotationNames.SqlQueryColumnMappings] as SortedSet; + if (columnMappings == null) + { + columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); + property.SetAnnotation(RelationalAnnotationNames.SqlQueryColumnMappings, columnMappings); + } + + columnMappings.Add(columnMapping); + } + + mappedType = mappedType.BaseType; + + queryMappings = entityType[RelationalAnnotationNames.SqlQueryMappings] as List; + if (queryMappings == null) + { + queryMappings = new List(); + entityType.SetAnnotation(RelationalAnnotationNames.SqlQueryMappings, queryMappings); } - viewMappings.Reverse(); + if (queryMapping.ColumnMappings.Count != 0 + || queryMappings.Count == 0) + { + queryMappings.Add(queryMapping); + sqlQuery.EntityTypeMappings.Add(queryMapping); + } } - return viewName; + queryMappings.Reverse(); } - private static string AddMappedFunctions(RelationalModel databaseModel, IConventionEntityType entityType) + private static void AddMappedFunctions(RelationalModel databaseModel, IConventionEntityType entityType) { var model = databaseModel.Model; var functionName = entityType.GetFunctionName(); - if (functionName != null) + if (functionName == null) { - List functionMappings = null; - var mappedType = entityType; - while (mappedType != null) - { - var mappedFunctionName = mappedType.GetFunctionName(); - if (mappedFunctionName == null - || (mappedFunctionName == functionName - && mappedType != entityType)) - { - break; - } + return; + } - var dbFunction = (DbFunction)model.FindDbFunction(mappedFunctionName); - var functionMapping = CreateFunctionMapping(entityType, mappedType, dbFunction, databaseModel, @default: true); + List functionMappings = null; + var mappedType = entityType; + while (mappedType != null) + { + var mappedFunctionName = mappedType.GetFunctionName(); + if (mappedFunctionName == null + || (mappedFunctionName == functionName + && mappedType != entityType)) + { + break; + } - mappedType = mappedType.BaseType; + var dbFunction = (DbFunction)model.FindDbFunction(mappedFunctionName); + var functionMapping = CreateFunctionMapping(entityType, mappedType, dbFunction, databaseModel, @default: true); - functionMappings = entityType[RelationalAnnotationNames.FunctionMappings] as List; - if (functionMappings == null) - { - functionMappings = new List(); - entityType.SetAnnotation(RelationalAnnotationNames.FunctionMappings, functionMappings); - } + mappedType = mappedType.BaseType; - if (functionMapping.ColumnMappings.Count != 0 - || functionMappings.Count == 0) - { - functionMappings.Add(functionMapping); - ((StoreFunction)functionMapping.StoreFunction).EntityTypeMappings.Add(functionMapping); - } + functionMappings = entityType[RelationalAnnotationNames.FunctionMappings] as List; + if (functionMappings == null) + { + functionMappings = new List(); + entityType.SetAnnotation(RelationalAnnotationNames.FunctionMappings, functionMappings); } - functionMappings.Reverse(); + if (functionMapping.ColumnMappings.Count != 0 + || functionMappings.Count == 0) + { + functionMappings.Add(functionMapping); + ((StoreFunction)functionMapping.StoreFunction).EntityTypeMappings.Add(functionMapping); + } } - return functionName; + functionMappings.Reverse(); } private static void AddTVFs(RelationalModel relationalModel) @@ -558,16 +784,7 @@ private static void PopulateConstraints(Table table) var principalColumns = new Column[foreignKey.Properties.Count]; for (var i = 0; i < principalColumns.Length; i++) { - var property = foreignKey.PrincipalKey.Properties[i]; - foreach (var columnMapping in property.GetTableColumnMappings()) - { - if (columnMapping.TableMapping.Table == principalTable) - { - principalColumns[i] = (Column)columnMapping.Column; - break; - } - } - + principalColumns[i] = (Column)principalTable.FindColumn(foreignKey.PrincipalKey.Properties[i]); if (principalColumns[i] == null) { principalColumns = null; @@ -583,16 +800,7 @@ private static void PopulateConstraints(Table table) var columns = new Column[foreignKey.Properties.Count]; for (var i = 0; i < columns.Length; i++) { - var property = foreignKey.Properties[i]; - foreach (var columnMapping in property.GetTableColumnMappings()) - { - if (columnMapping.TableMapping.Table == table) - { - columns[i] = (Column)columnMapping.Column; - break; - } - } - + columns[i] = (Column)table.FindColumn(foreignKey.Properties[i]); if (columns[i] == null) { columns = null; @@ -636,16 +844,7 @@ private static void PopulateConstraints(Table table) var columns = new Column[key.Properties.Count]; for (var i = 0; i < columns.Length; i++) { - var property = key.Properties[i]; - foreach (var columnMapping in property.GetTableColumnMappings()) - { - if (columnMapping.TableMapping.Table == table) - { - columns[i] = (Column)columnMapping.Column; - break; - } - } - + columns[i] = (Column)table.FindColumn(key.Properties[i]); if (columns[i] == null) { columns = null; @@ -685,16 +884,7 @@ private static void PopulateConstraints(Table table) var columns = new Column[index.Properties.Count]; for (var i = 0; i < columns.Length; i++) { - var property = index.Properties[i]; - foreach (var columnMapping in property.GetTableColumnMappings()) - { - if (columnMapping.TableMapping.Table == table) - { - columns[i] = (Column)columnMapping.Column; - break; - } - } - + columns[i] = (Column)table.FindColumn(index.Properties[i]); if (columns[i] == null) { columns = null; @@ -872,5 +1062,11 @@ IEnumerable IRelationalModel.Functions [DebuggerStepThrough] get => Functions.Values; } + + IEnumerable IRelationalModel.Queries + { + [DebuggerStepThrough] + get => Queries.Values; + } } } diff --git a/src/EFCore.Relational/Metadata/Internal/SqlQuery.cs b/src/EFCore.Relational/Metadata/Internal/SqlQuery.cs new file mode 100644 index 00000000000..cdd78dc94f3 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/SqlQuery.cs @@ -0,0 +1,73 @@ +// 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.Diagnostics; +using System.Linq; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Metadata.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 SqlQuery : TableBase, ISqlQuery + { + /// + /// 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 SqlQuery([NotNull] string name, [NotNull] RelationalModel model) + : base(name, null, model) + { + } + + /// + public virtual string Sql { get; [param: NotNull] set; } + + /// + public override IColumnBase FindColumn(IProperty property) + => property.GetSqlQueryColumnMappings() + .FirstOrDefault(cm => cm.TableMapping.Table == this) + ?.Column; + + /// + /// 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 override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + IEnumerable ISqlQuery.EntityTypeMappings + { + [DebuggerStepThrough] + get => EntityTypeMappings.Cast(); + } + + /// + IEnumerable ISqlQuery.Columns + { + [DebuggerStepThrough] + get => Columns.Values.Cast(); + } + + /// + [DebuggerStepThrough] + ISqlQueryColumn ISqlQuery.FindColumn(string name) + => (ISqlQueryColumn)base.FindColumn(name); + + /// + [DebuggerStepThrough] + ISqlQueryColumn ISqlQuery.FindColumn(IProperty property) + => (ISqlQueryColumn)FindColumn(property); + } +} diff --git a/src/EFCore.Relational/Metadata/Internal/SqlQueryColumn.cs b/src/EFCore.Relational/Metadata/Internal/SqlQueryColumn.cs new file mode 100644 index 00000000000..2ae65f66441 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/SqlQueryColumn.cs @@ -0,0 +1,49 @@ +// 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 System.Diagnostics; +using System.Linq; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Metadata.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 SqlQueryColumn : ColumnBase, ISqlQueryColumn + { + /// + /// 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 SqlQueryColumn([NotNull] string name, [NotNull] string type, [NotNull] SqlQuery sqlQuery) + : base(name, type, sqlQuery) + { + } + + /// + public virtual ISqlQuery SqlQuery => (ISqlQuery)Table; + + /// + /// 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 override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + IEnumerable ISqlQueryColumn.PropertyMappings + { + [DebuggerStepThrough] + get => PropertyMappings.Cast(); + } + } +} diff --git a/src/EFCore.Relational/Metadata/Internal/SqlQueryColumnMapping.cs b/src/EFCore.Relational/Metadata/Internal/SqlQueryColumnMapping.cs new file mode 100644 index 00000000000..36b12a66efe --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/SqlQueryColumnMapping.cs @@ -0,0 +1,52 @@ +// 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.Diagnostics; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Microsoft.EntityFrameworkCore.Metadata.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 SqlQueryColumnMapping : ColumnMappingBase, ISqlQueryColumnMapping + { + /// + /// 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 SqlQueryColumnMapping( + [NotNull] IProperty property, + [NotNull] SqlQueryColumn column, + [NotNull] RelationalTypeMapping typeMapping, + [NotNull] SqlQueryMapping sqlQueryMapping) + : base(property, column, typeMapping, sqlQueryMapping) + { + } + + /// + public virtual ISqlQueryMapping SqlQueryMapping => (ISqlQueryMapping)TableMapping; + + /// + /// 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 override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + ISqlQueryColumn ISqlQueryColumnMapping.Column + { + [DebuggerStepThrough] + get => (ISqlQueryColumn)Column; + } + } +} diff --git a/src/EFCore.Relational/Metadata/Internal/SqlQueryMapping.cs b/src/EFCore.Relational/Metadata/Internal/SqlQueryMapping.cs new file mode 100644 index 00000000000..df8c528e860 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/SqlQueryMapping.cs @@ -0,0 +1,55 @@ +// 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 System.Diagnostics; +using System.Linq; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Metadata.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 SqlQueryMapping : TableMappingBase, ISqlQueryMapping + { + /// + /// 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 SqlQueryMapping( + [NotNull] IEntityType entityType, + [NotNull] SqlQuery sqlQuery, + bool includesDerivedTypes) + : base(entityType, sqlQuery, includesDerivedTypes) + { + } + + /// + public virtual bool IsDefaultSqlQueryMapping { get; set; } + + /// + public virtual ISqlQuery SqlQuery => (ISqlQuery)base.Table; + + /// + /// 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 override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + IEnumerable ISqlQueryMapping.ColumnMappings + { + [DebuggerStepThrough] + get => ColumnMappings.Cast(); + } + } +} diff --git a/src/EFCore.Relational/Metadata/Internal/StoreFunction.cs b/src/EFCore.Relational/Metadata/Internal/StoreFunction.cs index bf46846261c..e905df86fd4 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreFunction.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreFunction.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -52,24 +53,6 @@ public StoreFunction([NotNull] DbFunction dbFunction, [NotNull] RelationalModel /// public virtual string ReturnType { get; } - /// - /// 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 SortedSet EntityTypeMappings { get; } - = new SortedSet(TableMappingBaseComparer.Instance); - - /// - /// 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 SortedDictionary Columns { get; } - = new SortedDictionary(StringComparer.Ordinal); - /// /// 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 @@ -79,10 +62,10 @@ public StoreFunction([NotNull] DbFunction dbFunction, [NotNull] RelationalModel public virtual StoreFunctionParameter[] Parameters { get; } /// - public virtual IFunctionColumn FindColumn(string name) - => Columns.TryGetValue(name, out var column) - ? column - : null; + public override IColumnBase FindColumn(IProperty property) + => property.GetFunctionColumnMappings() + .FirstOrDefault(cm => cm.TableMapping.Table == this) + ?.Column; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -96,28 +79,14 @@ public virtual IFunctionColumn FindColumn(string name) IEnumerable IStoreFunction.EntityTypeMappings { [DebuggerStepThrough] - get => EntityTypeMappings; - } - - /// - IEnumerable ITableBase.EntityTypeMappings - { - [DebuggerStepThrough] - get => EntityTypeMappings; + get => EntityTypeMappings.Cast(); } /// IEnumerable IStoreFunction.Columns { [DebuggerStepThrough] - get => Columns.Values; - } - - /// - IEnumerable ITableBase.Columns - { - [DebuggerStepThrough] - get => Columns.Values; + get => Columns.Values.Cast(); } /// @@ -134,9 +103,14 @@ IEnumerable IStoreFunction.DbFunctions get => DbFunctions.Values; } - /// + /// + [DebuggerStepThrough] + IFunctionColumn IStoreFunction.FindColumn(string name) + => (IFunctionColumn)base.FindColumn(name); + + /// [DebuggerStepThrough] - IColumnBase ITableBase.FindColumn(string name) - => FindColumn(name); + IFunctionColumn IStoreFunction.FindColumn(IProperty property) + => (IFunctionColumn)FindColumn(property); } } diff --git a/src/EFCore.Relational/Metadata/Internal/Table.cs b/src/EFCore.Relational/Metadata/Internal/Table.cs index 8eb8d867f51..14127b4ae9e 100644 --- a/src/EFCore.Relational/Metadata/Internal/Table.cs +++ b/src/EFCore.Relational/Metadata/Internal/Table.cs @@ -29,15 +29,6 @@ public Table([NotNull] string name, [CanBeNull] string schema, [NotNull] Relatio { } - /// - /// 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 SortedSet EntityTypeMappings { get; } = new SortedSet( - TableMappingBaseComparer.Instance); - /// /// 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 @@ -89,19 +80,11 @@ public virtual UniqueConstraint FindUniqueConstraint([NotNull] string name) /// public virtual bool IsExcludedFromMigrations { get; set; } - /// - /// 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 SortedDictionary Columns { get; } = new SortedDictionary(StringComparer.Ordinal); - /// - public virtual IColumn FindColumn(string name) - => Columns.TryGetValue(name, out var column) - ? column - : null; + public override IColumnBase FindColumn(IProperty property) + => property.GetTableColumnMappings() + .FirstOrDefault(cm => cm.TableMapping.Table == this) + ?.Column; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -111,32 +94,18 @@ public virtual IColumn FindColumn(string name) /// public override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); - /// - IEnumerable ITableBase.Columns - { - [DebuggerStepThrough] - get => Columns.Values; - } - - /// - IEnumerable ITable.Columns - { - [DebuggerStepThrough] - get => Columns.Values; - } - /// IEnumerable ITable.EntityTypeMappings { [DebuggerStepThrough] - get => EntityTypeMappings; + get => base.EntityTypeMappings.Cast(); } /// - IEnumerable ITableBase.EntityTypeMappings + IEnumerable ITable.Columns { [DebuggerStepThrough] - get => EntityTypeMappings; + get => base.Columns.Values.Cast(); } /// @@ -168,20 +137,13 @@ IEnumerable ITable.Indexes } /// - IColumnBase ITableBase.FindColumn(string name) => FindColumn(name); - - /// - IEnumerable ITableBase.GetRowInternalForeignKeys(IEntityType entityType) - => RowInternalForeignKeys != null - && RowInternalForeignKeys.TryGetValue(entityType, out var foreignKeys) - ? foreignKeys - : Enumerable.Empty(); + [DebuggerStepThrough] + IColumn ITable.FindColumn(string name) + => (IColumn)base.FindColumn(name); /// - IEnumerable ITableBase.GetReferencingRowInternalForeignKeys(IEntityType entityType) - => ReferencingRowInternalForeignKeys != null - && ReferencingRowInternalForeignKeys.TryGetValue(entityType, out var foreignKeys) - ? foreignKeys - : Enumerable.Empty(); + [DebuggerStepThrough] + IColumn ITable.FindColumn(IProperty property) + => (IColumn)FindColumn(property); } } diff --git a/src/EFCore.Relational/Metadata/Internal/TableBase.cs b/src/EFCore.Relational/Metadata/Internal/TableBase.cs index f63ab6fb78d..62c90a45f44 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableBase.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableBase.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -14,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 abstract class TableBase : Annotatable, ITableBase + public class TableBase : Annotatable, ITableBase { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -52,7 +53,8 @@ public TableBase([NotNull] string name, [CanBeNull] string schema, [NotNull] Rel /// 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 SortedDictionary> RowInternalForeignKeys { get; [param: NotNull] set; } + public virtual SortedSet EntityTypeMappings { get; } + = new SortedSet(TableMappingBaseComparer.Instance); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -60,32 +62,58 @@ public TableBase([NotNull] string name, [CanBeNull] string schema, [NotNull] Rel /// 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 SortedDictionary> ReferencingRowInternalForeignKeys { get; [param: NotNull] set; } + public virtual SortedDictionary Columns { get; } + = new SortedDictionary(StringComparer.Ordinal); /// - IEnumerable ITableBase.EntityTypeMappings => throw new NotImplementedException(); + public virtual IColumnBase FindColumn(string name) + => Columns.TryGetValue(name, out var column) + ? column + : null; /// - IEnumerable ITableBase.Columns => throw new NotImplementedException(); + public virtual IColumnBase FindColumn(IProperty property) + => property.GetDefaultColumnMappings() + .FirstOrDefault(cm => cm.TableMapping.Table == this) + ?.Column; + + /// + /// 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 SortedDictionary> RowInternalForeignKeys { get; [param: NotNull] set; } + + /// + /// 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 SortedDictionary> ReferencingRowInternalForeignKeys { get; [param: NotNull] set; } /// IRelationalModel ITableBase.Model => Model; /// - IColumnBase ITableBase.FindColumn(string name) => throw new NotImplementedException(); + IEnumerable ITableBase.EntityTypeMappings => EntityTypeMappings; + + /// + IEnumerable ITableBase.Columns => Columns.Values; /// IEnumerable ITableBase.GetRowInternalForeignKeys(IEntityType entityType) => RowInternalForeignKeys != null && RowInternalForeignKeys.TryGetValue(entityType, out var foreignKeys) ? foreignKeys - : null; + : Enumerable.Empty(); /// IEnumerable ITableBase.GetReferencingRowInternalForeignKeys(IEntityType entityType) => ReferencingRowInternalForeignKeys != null && ReferencingRowInternalForeignKeys.TryGetValue(entityType, out var foreignKeys) ? foreignKeys - : null; + : Enumerable.Empty(); } } diff --git a/src/EFCore.Relational/Metadata/Internal/TableMapping.cs b/src/EFCore.Relational/Metadata/Internal/TableMapping.cs index edd1f3a5942..a20360fedda 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableMapping.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -33,18 +34,6 @@ public TableMapping( /// public new virtual ITable Table => (ITable)base.Table; - /// - /// 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 SortedSet ColumnMappings { get; } - = new SortedSet(ColumnMappingBaseComparer.Instance); - - /// - protected override IEnumerable ProtectedColumnMappings => ColumnMappings; - /// /// 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 @@ -64,14 +53,7 @@ ITableBase ITableMappingBase.Table IEnumerable ITableMapping.ColumnMappings { [DebuggerStepThrough] - get => ColumnMappings; - } - - /// - IEnumerable ITableMappingBase.ColumnMappings - { - [DebuggerStepThrough] - get => ColumnMappings; + get => ColumnMappings.Cast(); } } } diff --git a/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs b/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs index 88734c7811d..b440ac9874d 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableMappingBase.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 abstract class TableMappingBase : Annotatable, ITableMappingBase + public class TableMappingBase : Annotatable, ITableMappingBase { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -44,7 +44,8 @@ public TableMappingBase( /// 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. /// - protected abstract IEnumerable ProtectedColumnMappings { get; } + public virtual SortedSet ColumnMappings { get; } + = new SortedSet(ColumnMappingBaseComparer.Instance); /// public virtual bool IncludesDerivedTypes { get; } @@ -55,11 +56,10 @@ public TableMappingBase( /// public virtual bool IsSplitEntityTypePrincipal { get; set; } - /// IEnumerable ITableMappingBase.ColumnMappings { [DebuggerStepThrough] - get => ProtectedColumnMappings; + get => ColumnMappings; } } } diff --git a/src/EFCore.Relational/Metadata/Internal/View.cs b/src/EFCore.Relational/Metadata/Internal/View.cs index 8a15229707c..ddae4c3fc5e 100644 --- a/src/EFCore.Relational/Metadata/Internal/View.cs +++ b/src/EFCore.Relational/Metadata/Internal/View.cs @@ -29,33 +29,15 @@ public View([NotNull] string name, [CanBeNull] string schema, [NotNull] Relation { } - /// - /// 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 SortedSet EntityTypeMappings { get; } = new SortedSet( - TableMappingBaseComparer.Instance); - - /// - /// 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 SortedDictionary Columns { get; } - = new SortedDictionary(StringComparer.Ordinal); - /// public virtual string ViewDefinitionSql => (string)EntityTypeMappings.Select(m => m.EntityType[RelationalAnnotationNames.ViewDefinitionSql]).FirstOrDefault(d => d != null); /// - public virtual IViewColumn FindColumn(string name) - => Columns.TryGetValue(name, out var column) - ? column - : null; + public override IColumnBase FindColumn(IProperty property) + => property.GetViewColumnMappings() + .FirstOrDefault(cm => cm.TableMapping.Table == this) + ?.Column; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -65,50 +47,28 @@ public virtual IViewColumn FindColumn(string name) /// public override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); - /// - [DebuggerStepThrough] - IColumnBase ITableBase.FindColumn(string name) => FindColumn(name); - - /// - IEnumerable ITableBase.Columns - { - [DebuggerStepThrough] - get => Columns.Values; - } - - /// - IEnumerable IView.Columns - { - [DebuggerStepThrough] - get => Columns.Values; - } - /// IEnumerable IView.EntityTypeMappings { [DebuggerStepThrough] - get => EntityTypeMappings; + get => EntityTypeMappings.Cast(); } /// - IEnumerable ITableBase.EntityTypeMappings + IEnumerable IView.Columns { [DebuggerStepThrough] - get => EntityTypeMappings; + get => Columns.Values.Cast(); } /// - IEnumerable ITableBase.GetRowInternalForeignKeys(IEntityType entityType) - => RowInternalForeignKeys != null - && RowInternalForeignKeys.TryGetValue(entityType, out var foreignKeys) - ? foreignKeys - : Enumerable.Empty(); + [DebuggerStepThrough] + IViewColumn IView.FindColumn(string name) + => (IViewColumn)base.FindColumn(name); /// - IEnumerable ITableBase.GetReferencingRowInternalForeignKeys(IEntityType entityType) - => ReferencingRowInternalForeignKeys != null - && ReferencingRowInternalForeignKeys.TryGetValue(entityType, out var foreignKeys) - ? foreignKeys - : Enumerable.Empty(); + [DebuggerStepThrough] + IViewColumn IView.FindColumn(IProperty property) + => (IViewColumn)FindColumn(property); } } diff --git a/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs b/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs index b4b80478d88..64c3103d727 100644 --- a/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -14,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 ViewColumn : Annotatable, IViewColumn + public class ViewColumn : ColumnBase, IViewColumn { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -23,32 +24,12 @@ public class ViewColumn : Annotatable, IViewColumn /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public ViewColumn([NotNull] string name, [NotNull] string type, [NotNull] View view) + : base(name, type, view) { - Name = name; - StoreType = type; - View = view; } /// - public virtual string Name { get; } - - /// - public virtual IView View { get; } - - /// - public virtual string StoreType { get; } - - /// - public virtual bool IsNullable { get; set; } - - /// - /// 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 SortedSet PropertyMappings { get; } - = new SortedSet(ColumnMappingBaseComparer.Instance); + public virtual IView View => (IView)Table; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -62,21 +43,7 @@ public ViewColumn([NotNull] string name, [NotNull] string type, [NotNull] View v IEnumerable IViewColumn.PropertyMappings { [DebuggerStepThrough] - get => PropertyMappings; - } - - /// - IEnumerable IColumnBase.PropertyMappings - { - [DebuggerStepThrough] - get => PropertyMappings; - } - - /// - ITableBase IColumnBase.Table - { - [DebuggerStepThrough] - get => View; + get => PropertyMappings.Cast(); } } } diff --git a/src/EFCore.Relational/Metadata/Internal/ViewColumnMapping.cs b/src/EFCore.Relational/Metadata/Internal/ViewColumnMapping.cs index 9591214327f..69c5c8e1cf8 100644 --- a/src/EFCore.Relational/Metadata/Internal/ViewColumnMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/ViewColumnMapping.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 ViewColumnMapping : Annotatable, IViewColumnMapping + public class ViewColumnMapping : ColumnMappingBase, IViewColumnMapping { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -27,24 +27,12 @@ public ViewColumnMapping( [NotNull] ViewColumn column, [NotNull] RelationalTypeMapping typeMapping, [NotNull] ViewMapping viewMapping) + : base(property, column, typeMapping, viewMapping) { - Property = property; - Column = column; - TypeMapping = typeMapping; - ViewMapping = viewMapping; } /// - public virtual IProperty Property { get; } - - /// - public virtual IViewColumn Column { get; } - - /// - public virtual RelationalTypeMapping TypeMapping { get; } - - /// - public virtual IViewMapping ViewMapping { get; } + public virtual IViewMapping ViewMapping => (IViewMapping)TableMapping; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -55,17 +43,10 @@ public ViewColumnMapping( public override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); /// - IColumnBase IColumnMappingBase.Column - { - [DebuggerStepThrough] - get => Column; - } - - /// - ITableMappingBase IColumnMappingBase.TableMapping + IViewColumn IViewColumnMapping.Column { [DebuggerStepThrough] - get => ViewMapping; + get => (IViewColumn)Column; } } } diff --git a/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs b/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs index 9819bab2bab..dc80725a543 100644 --- a/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -33,18 +34,6 @@ public ViewMapping( /// public virtual IView View => (IView)base.Table; - /// - /// 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 SortedSet ColumnMappings { get; } - = new SortedSet(ColumnMappingBaseComparer.Instance); - - /// - protected override IEnumerable ProtectedColumnMappings => ColumnMappings; - /// /// 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 @@ -57,14 +46,7 @@ public ViewMapping( IEnumerable IViewMapping.ColumnMappings { [DebuggerStepThrough] - get => ColumnMappings; - } - - /// - IEnumerable ITableMappingBase.ColumnMappings - { - [DebuggerStepThrough] - get => ColumnMappings; + get => ColumnMappings.Cast(); } } } diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs index f0fddbaad7f..c75a9274149 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs @@ -71,6 +71,11 @@ public static class RelationalAnnotationNames /// public const string FunctionName = Prefix + "FunctionName"; + /// + /// The name for mapped query sql annotations. + /// + public const string QuerySql = Prefix + "QuerySql"; + /// /// The name for comment annotations. /// @@ -148,6 +153,16 @@ public static class RelationalAnnotationNames /// public const string RelationalModel = Prefix + "RelationalModel"; + /// + /// The name for view mappings annotations. + /// + public const string DefaultMappings = Prefix + "DefaultMappings"; + + /// + /// The name for view column mappings annotations. + /// + public const string DefaultColumnMappings = Prefix + "DefaultColumnMappings"; + /// /// The name for table mappings annotations. /// @@ -178,6 +193,16 @@ public static class RelationalAnnotationNames /// public const string FunctionColumnMappings = Prefix + "FunctionColumnMappings"; + /// + /// The name for view mappings annotations. + /// + public const string SqlQueryMappings = Prefix + "SqlQueryMappings"; + + /// + /// The name for view column mappings annotations. + /// + public const string SqlQueryColumnMappings = Prefix + "SqlQueryColumnMappings"; + /// /// The name for foreign key mappings annotations. /// diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs index 3a23e8fbf24..e7b46520904 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs @@ -47,6 +47,18 @@ public RelationalAnnotationProvider([NotNull] RelationalAnnotationProviderDepend /// public virtual IEnumerable For(IViewColumn column) => Enumerable.Empty(); + /// + public virtual IEnumerable For(ISqlQuery sqlQuery) => Enumerable.Empty(); + + /// + public virtual IEnumerable For(ISqlQueryColumn column) => Enumerable.Empty(); + + /// + public virtual IEnumerable For(IStoreFunction function) => Enumerable.Empty(); + + /// + public virtual IEnumerable For(IFunctionColumn column) => Enumerable.Empty(); + /// public virtual IEnumerable For(IForeignKeyConstraint foreignKey) => Enumerable.Empty(); diff --git a/src/EFCore.Relational/Metadata/SqlQueryColumnExtensions.cs b/src/EFCore.Relational/Metadata/SqlQueryColumnExtensions.cs new file mode 100644 index 00000000000..aa2dbb63f11 --- /dev/null +++ b/src/EFCore.Relational/Metadata/SqlQueryColumnExtensions.cs @@ -0,0 +1,68 @@ +// 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.Text; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + /// + /// Extension methods for . + /// + public static class SqlQueryColumnExtensions + { + /// + /// + /// Creates a human-readable representation of the given metadata. + /// + /// + /// Warning: Do not rely on the format of the returned string. + /// It is designed for debugging only and may change arbitrarily between releases. + /// + /// + /// The metadata item. + /// Options for generating the string. + /// The number of indent spaces to use before each new line. + /// A human-readable representation. + public static string ToDebugString( + [NotNull] this ISqlQueryColumn column, + MetadataDebugStringOptions options, + int indent = 0) + { + var builder = new StringBuilder(); + var indentString = new string(' ', indent); + + builder.Append(indentString); + + var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; + if (singleLine) + { + builder.Append($"SqlQueryColumn: {column.Table.Name}."); + } + + builder.Append(column.Name).Append(" ("); + + builder.Append(column.StoreType).Append(")"); + + if (column.IsNullable) + { + builder.Append(" Nullable"); + } + else + { + builder.Append(" NonNullable"); + } + + builder.Append(")"); + + if (!singleLine && + (options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(column.AnnotationsToDebugString(indent + 2)); + } + + return builder.ToString(); + } + } +} diff --git a/src/EFCore.Relational/Metadata/SqlQueryColumnMappingExtensions.cs b/src/EFCore.Relational/Metadata/SqlQueryColumnMappingExtensions.cs new file mode 100644 index 00000000000..08765e0776b --- /dev/null +++ b/src/EFCore.Relational/Metadata/SqlQueryColumnMappingExtensions.cs @@ -0,0 +1,57 @@ +// 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.Text; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + /// + /// Extension methods for . + /// + public static class SqlQueryColumnMappingExtensions + { + /// + /// + /// Creates a human-readable representation of the given metadata. + /// + /// + /// Warning: Do not rely on the format of the returned string. + /// It is designed for debugging only and may change arbitrarily between releases. + /// + /// + /// The metadata item. + /// Options for generating the string. + /// The number of indent spaces to use before each new line. + /// A human-readable representation. + public static string ToDebugString( + [NotNull] this ISqlQueryColumnMapping columnMapping, + MetadataDebugStringOptions options, + int indent = 0) + { + var builder = new StringBuilder(); + var indentString = new string(' ', indent); + + builder.Append(indentString); + + var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; + if (singleLine) + { + builder.Append($"SqlQueryColumnMapping: "); + } + + builder.Append(columnMapping.Property.Name).Append(" - "); + + builder.Append(columnMapping.Column.Name); + + if (!singleLine && + (options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(columnMapping.AnnotationsToDebugString(indent + 2)); + } + + return builder.ToString(); + } + } +} diff --git a/src/EFCore.Relational/Metadata/SqlQueryExtensions.cs b/src/EFCore.Relational/Metadata/SqlQueryExtensions.cs new file mode 100644 index 00000000000..9bc22cf03b5 --- /dev/null +++ b/src/EFCore.Relational/Metadata/SqlQueryExtensions.cs @@ -0,0 +1,87 @@ +// 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.Linq; +using System.Text; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + /// + /// Extension methods for . + /// + public static class SqlQueryExtensions + { + /// + /// Gets the name used for the mapped using + /// . + /// + public static readonly string DefaultQueryNameBase = "MappedSqlQuery"; + + /// + /// + /// Creates a human-readable representation of the given metadata. + /// + /// + /// Warning: Do not rely on the format of the returned string. + /// It is designed for debugging only and may change arbitrarily between releases. + /// + /// + /// The metadata item. + /// Options for generating the string. + /// The number of indent spaces to use before each new line. + /// A human-readable representation. + public static string ToDebugString( + [NotNull] this ISqlQuery sqlQuery, + MetadataDebugStringOptions options, + int indent = 0) + { + var builder = new StringBuilder(); + var indentString = new string(' ', indent); + + builder + .Append(indentString) + .Append("SqlQuery: "); + + if (sqlQuery.Schema != null) + { + builder + .Append(sqlQuery.Schema) + .Append("."); + } + + builder.Append(sqlQuery.Name); + + if ((options & MetadataDebugStringOptions.SingleLine) == 0) + { + var mappings = sqlQuery.EntityTypeMappings.ToList(); + if (mappings.Count != 0) + { + builder.AppendLine().Append(indentString).Append(" EntityTypeMappings: "); + foreach (var mapping in mappings) + { + builder.AppendLine().Append(mapping.ToDebugString(options, indent + 4)); + } + } + + var columns = sqlQuery.Columns.ToList(); + if (columns.Count != 0) + { + builder.AppendLine().Append(indentString).Append(" Properties: "); + foreach (var column in columns) + { + builder.AppendLine().Append(column.ToDebugString(options, indent + 4)); + } + } + + if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(sqlQuery.AnnotationsToDebugString(indent + 2)); + } + } + + return builder.ToString(); + } + } +} diff --git a/src/EFCore.Relational/Metadata/SqlQueryMappingExtensions.cs b/src/EFCore.Relational/Metadata/SqlQueryMappingExtensions.cs new file mode 100644 index 00000000000..04c48656892 --- /dev/null +++ b/src/EFCore.Relational/Metadata/SqlQueryMappingExtensions.cs @@ -0,0 +1,62 @@ +// 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.Text; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + /// + /// Extension methods for . + /// + public static class SqlQueryMappingExtensions + { + /// + /// + /// Creates a human-readable representation of the given metadata. + /// + /// + /// Warning: Do not rely on the format of the returned string. + /// It is designed for debugging only and may change arbitrarily between releases. + /// + /// + /// The metadata item. + /// Options for generating the string. + /// The number of indent spaces to use before each new line. + /// A human-readable representation. + public static string ToDebugString( + [NotNull] this ISqlQueryMapping sqlQueryMapping, + MetadataDebugStringOptions options, + int indent = 0) + { + var builder = new StringBuilder(); + var indentString = new string(' ', indent); + + builder.Append(indentString); + + var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; + if (singleLine) + { + builder.Append($"SqlQueryMapping: "); + } + + builder.Append(sqlQueryMapping.EntityType.Name).Append(" - "); + + builder.Append(sqlQueryMapping.Table.Name); + + if (sqlQueryMapping.IncludesDerivedTypes) + { + builder.Append($" IncludesDerivedTypes"); + } + + if (!singleLine && + (options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(sqlQueryMapping.AnnotationsToDebugString(indent + 2)); + } + + return builder.ToString(); + } + } +} diff --git a/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs b/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs index a7deb16b565..ebdb032ce6d 100644 --- a/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs +++ b/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs @@ -39,6 +39,30 @@ public static StoreObjectIdentifier View([NotNull] string name, [CanBeNull] stri return new StoreObjectIdentifier { StoreObjectType = StoreObjectType.View, Name = name, Schema = schema }; } + /// + /// Creates an id for the SQL query mapped using . + /// + /// The entity type. + /// The SQL query id. + public static StoreObjectIdentifier SqlQuery([NotNull] IEntityType entityType) + { + Check.NotNull(entityType, nameof(entityType)); + + return new StoreObjectIdentifier { StoreObjectType = StoreObjectType.SqlQuery, Name = entityType.GetDefaultSqlQueryName() }; + } + + /// + /// Creates a SQL query id. + /// + /// The SQL query name. + /// The SQL query id. + public static StoreObjectIdentifier SqlQuery([NotNull] string name) + { + Check.NotNull(name, nameof(name)); + + return new StoreObjectIdentifier { StoreObjectType = StoreObjectType.SqlQuery, Name = name }; + } + /// /// Creates a function id. /// diff --git a/src/EFCore.Relational/Metadata/StoreObjectType.cs b/src/EFCore.Relational/Metadata/StoreObjectType.cs index 92546534e8c..4fd72c71bb3 100644 --- a/src/EFCore.Relational/Metadata/StoreObjectType.cs +++ b/src/EFCore.Relational/Metadata/StoreObjectType.cs @@ -18,6 +18,11 @@ public enum StoreObjectType /// View, + /// + /// A SQL query. + /// + SqlQuery, + /// /// A table-valued function. /// diff --git a/src/EFCore.Relational/Metadata/TableIndexExtensions.cs b/src/EFCore.Relational/Metadata/TableIndexExtensions.cs index 91714966529..a95eeae1678 100644 --- a/src/EFCore.Relational/Metadata/TableIndexExtensions.cs +++ b/src/EFCore.Relational/Metadata/TableIndexExtensions.cs @@ -44,7 +44,7 @@ public static string ToDebugString( builder .Append(index.Name) .Append(" ") - .Append(Column.Format(index.Columns)); + .Append(ColumnBase.Format(index.Columns)); if (index.IsUnique) { diff --git a/src/EFCore.Relational/Metadata/UniqueConstraintExtensions.cs b/src/EFCore.Relational/Metadata/UniqueConstraintExtensions.cs index 8731c182805..eba53370902 100644 --- a/src/EFCore.Relational/Metadata/UniqueConstraintExtensions.cs +++ b/src/EFCore.Relational/Metadata/UniqueConstraintExtensions.cs @@ -52,7 +52,7 @@ public static string ToDebugString( builder .Append(uniqueConstraint.Name) .Append(" ") - .Append(Column.Format(uniqueConstraint.Columns)); + .Append(ColumnBase.Format(uniqueConstraint.Columns)); if (uniqueConstraint.GetIsPrimaryKey()) { diff --git a/src/EFCore.Relational/Metadata/ViewColumnExtensions.cs b/src/EFCore.Relational/Metadata/ViewColumnExtensions.cs index bbb52971274..9a902af7bd4 100644 --- a/src/EFCore.Relational/Metadata/ViewColumnExtensions.cs +++ b/src/EFCore.Relational/Metadata/ViewColumnExtensions.cs @@ -38,7 +38,7 @@ public static string ToDebugString( var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; if (singleLine) { - builder.Append($"Column: {column.Table.Name}."); + builder.Append($"ViewColumn: {column.Table.Name}."); } builder.Append(column.Name).Append(" ("); diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 3dad0821c41..db4baef4dad 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -704,7 +704,7 @@ private static IEnumerable GetSortedColumns(ITable table) var sortedColumns = new List(columns.Count); foreach (var property in GetSortedProperties(GetMainType(table).GetRootType(), table)) { - var column = property.GetTableColumnMappings().FirstOrDefault(m => m.TableMapping.Table == table)?.Column; + var column = table.FindColumn(property); if (columns.Remove(column)) { sortedColumns.Add(column); @@ -1692,8 +1692,7 @@ protected virtual Dictionary> DiffData( var keyPropertiesMap = new List<(IProperty, ValueConverter, ValueConverter)>(); foreach (var keyProperty in targetKey.Properties) { - var targetColumnMapping = keyProperty.GetTableColumnMappings().First(m => m.TableMapping.Table == targetTable); - var targetColumn = targetColumnMapping.Column; + var targetColumn = targetTable.FindColumn(keyProperty); var sourceColumn = diffContext.FindSource(targetColumn); if (sourceColumn == null) { @@ -1919,8 +1918,7 @@ protected virtual Dictionary> DiffData( continue; } - var targetColumn = targetProperty.GetTableColumnMappings() - .FirstOrDefault(m => m.TableMapping.EntityType == entry.EntityType && m.TableMapping.Table == targetTable)?.Column; + var targetColumn = targetTable.FindColumn(targetProperty); var sourceColumn = diffContext.FindSource(targetColumn); if (sourceColumn == null) { diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 0f84089e005..6bb0eb38f85 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -811,15 +811,7 @@ public static string NonTPHViewClash([CanBeNull] object entityType, [CanBeNull] entityType, otherEntityType, view); /// - /// The property '{property}' has configuration specific to the table or view '{table}', however that's the main table or view to which '{entityType}' is mapped. Remove the specific configuration or use the configuration that does not specify a table or view. - /// - public static string TableOverrideDeclaredTable([CanBeNull] object property, [CanBeNull] object table, [CanBeNull] object entityType) - => string.Format( - GetString("TableOverrideDeclaredTable", nameof(property), nameof(table), nameof(entityType)), - property, table, entityType); - - /// - /// The property '{propertySpecification}' has specific configuration for the table or view '{table}', however it isn't mapped to a column on that table. Remove the specific configuration or map an entity type that contains this property to '{table}'. + /// The property '{propertySpecification}' has specific configuration for the table '{table}', however it isn't mapped to a column on that table. Remove the specific configuration or map an entity type that contains this property to '{table}'. /// public static string TableOverrideMismatch([CanBeNull] object propertySpecification, [CanBeNull] object table) => string.Format( @@ -944,6 +936,38 @@ public static string DefaultValueSqlUnspecified([CanBeNull] object column, [CanB GetString("DefaultValueSqlUnspecified", nameof(column), nameof(table)), column, table); + /// + /// The entity type '{entityType}' is mapped to a SQL query and it's derived from '{baseEntityType}'. Derived entity types cannot be mapped to a different SQL query. + /// + public static string InvalidMappedSqlQueryDerivedType([CanBeNull] object entityType, [CanBeNull] object baseEntityType) + => string.Format( + GetString("InvalidMappedSqlQueryDerivedType", nameof(entityType), nameof(baseEntityType)), + entityType, baseEntityType); + + /// + /// The property '{propertySpecification}' has specific configuration for the function '{function}', however it isn't mapped to a column on that function return. Remove the specific configuration or map an entity type that contains this property to '{function}'. + /// + public static string FunctionOverrideMismatch([CanBeNull] object propertySpecification, [CanBeNull] object function) + => string.Format( + GetString("FunctionOverrideMismatch", nameof(propertySpecification), nameof(function)), + propertySpecification, function); + + /// + /// The property '{propertySpecification}' has specific configuration for the SQL query '{query}', however it isn't mapped to a column on that query. Remove the specific configuration or map an entity type that contains this property to '{query}'. + /// + public static string SqlQueryOverrideMismatch([CanBeNull] object propertySpecification, [CanBeNull] object query) + => string.Format( + GetString("SqlQueryOverrideMismatch", nameof(propertySpecification), nameof(query)), + propertySpecification, query); + + /// + /// The property '{propertySpecification}' has specific configuration for the view '{table}', however it isn't mapped to a column on that view. Remove the specific configuration or map an entity type that contains this property to '{table}'. + /// + public static string ViewOverrideMismatch([CanBeNull] object propertySpecification, [CanBeNull] object table) + => string.Format( + GetString("ViewOverrideMismatch", nameof(propertySpecification), nameof(table)), + propertySpecification, table); + /// /// Sequence contains no elements. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 5dd170626dd..6389362bc0f 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -609,11 +609,8 @@ Both '{entityType}' and '{otherEntityType}' are mapped to the view '{view}'. All the entity types in a hierarchy that don't have a discriminator must be mapped to different views. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. - - The property '{property}' has configuration specific to the table or view '{table}', however that's the main table or view to which '{entityType}' is mapped. Remove the specific configuration or use the configuration that does not specify a table or view. - - The property '{propertySpecification}' has specific configuration for the table or view '{table}', however it isn't mapped to a column on that table. Remove the specific configuration or map an entity type that contains this property to '{table}'. + The property '{propertySpecification}' has specific configuration for the table '{table}', however it isn't mapped to a column on that table. Remove the specific configuration or map an entity type that contains this property to '{table}'. '{entityType}' is mapped to the table '{table}' while '{otherEntityType}' is mapped to the table '{otherTable}'. Map all the entity types in the hierarchy to the same table or remove the discriminator and map all of them to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. @@ -688,6 +685,18 @@ The column '{column}' on table {table} has unspecified default value SQL. Specify the SQL before using EF Core to create the database schema. + + The entity type '{entityType}' is mapped to a SQL query and it's derived from '{baseEntityType}'. Derived entity types cannot be mapped to a different SQL query. + + + The property '{propertySpecification}' has specific configuration for the function '{function}', however it isn't mapped to a column on that function return. Remove the specific configuration or map an entity type that contains this property to '{function}'. + + + The property '{propertySpecification}' has specific configuration for the SQL query '{query}', however it isn't mapped to a column on that query. Remove the specific configuration or map an entity type that contains this property to '{query}'. + + + The property '{propertySpecification}' has specific configuration for the view '{table}', however it isn't mapped to a column on that view. Remove the specific configuration or map an entity type that contains this property to '{table}'. + Sequence contains no elements. diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index c7c437979f7..cec2642ebed 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -90,8 +90,7 @@ protected override Expression VisitExtension(Expression extensionExpression) _sqlExpressionFactory.Select( fromSqlQueryRootExpression.EntityType, new FromSqlExpression( - (fromSqlQueryRootExpression.EntityType.GetViewOrTableMappings().SingleOrDefault()?.Table.Name - ?? fromSqlQueryRootExpression.EntityType.ShortName()).Substring(0, 1).ToLower(), + fromSqlQueryRootExpression.EntityType.GetDefaultMappings().SingleOrDefault().Table.Name.Substring(0, 1).ToLower(), fromSqlQueryRootExpression.Sql, fromSqlQueryRootExpression.Argument))); @@ -1450,14 +1449,8 @@ private static IDictionary GetPropertyExpressionsFr foreach (var property in entityType .GetAllBaseTypes().Concat(entityType.GetDerivedTypesInclusive()).SelectMany(EntityTypeExtensions.GetDeclaredProperties)) { - var column = table is ITable - ? (IColumnBase)property.GetTableColumnMappings().Where(cm => cm.TableMapping.Table == table - && cm.TableMapping.EntityType == property.DeclaringEntityType).Single().Column - : property.GetViewColumnMappings().Where(cm => cm.TableMapping.Table == table - && cm.TableMapping.EntityType == property.DeclaringEntityType).Single().Column; - propertyExpressions[property] = new ColumnExpression( - property, column, tableExpression, nullable || !property.IsPrimaryKey()); + property, table.FindColumn(property), tableExpression, nullable || !property.IsPrimaryKey()); } return propertyExpressions; @@ -1470,13 +1463,7 @@ private static IDictionary GetPropertyExpressionsFr foreach (var property in entityType .GetAllBaseTypes().Concat(entityType.GetDerivedTypesInclusive()).SelectMany(EntityTypeExtensions.GetDeclaredProperties)) { - var column = table is ITable - ? (IColumnBase)property.GetTableColumnMappings().Where(cm => cm.TableMapping.Table == table - && cm.TableMapping.EntityType == property.DeclaringEntityType).Single().Column - : property.GetViewColumnMappings().Where(cm => cm.TableMapping.Table == table - && cm.TableMapping.EntityType == property.DeclaringEntityType).Single().Column; - - propertyExpressions[property] = new ColumnExpression(property, column, tableExpression, nullable: true); + propertyExpressions[property] = new ColumnExpression(property, table.FindColumn(property), tableExpression, nullable: true); } return propertyExpressions; diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index 23adc434eaf..034cd028708 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -783,8 +783,7 @@ public virtual SelectExpression Select(IEntityType entityType, string sql, Expre Check.NotNull(sql, nameof(sql)); var tableExpression = new FromSqlExpression( - (entityType.GetViewOrTableMappings().SingleOrDefault()?.Table.Name ?? entityType.ShortName()).Substring(0, 1).ToLower(), - sql, sqlArguments); + entityType.GetDefaultMappings().SingleOrDefault().Table.Name.Substring(0, 1).ToLower(), sql, sqlArguments); var selectExpression = new SelectExpression(entityType, tableExpression); AddConditions(selectExpression, entityType); diff --git a/src/EFCore.Relational/Query/SqlExpressions/ColumnExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/ColumnExpression.cs index e5540749cfc..7191e6244f3 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/ColumnExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/ColumnExpression.cs @@ -26,11 +26,11 @@ public sealed class ColumnExpression : SqlExpression { internal ColumnExpression(IProperty property, IColumnBase column, TableExpressionBase table, bool nullable) : this( - column?.Name ?? property.GetColumnName(), + column.Name, table, property.ClrType, - property.GetRelationalTypeMapping(), - nullable || (column?.IsNullable ?? property.IsColumnNullable())) + column.PropertyMappings.First(m => m.Property == property).TypeMapping, + nullable || column.IsNullable) { } diff --git a/src/EFCore.Relational/Query/SqlExpressions/FromSqlExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/FromSqlExpression.cs index 8a231e5d520..fb70e953f8f 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/FromSqlExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/FromSqlExpression.cs @@ -35,6 +35,7 @@ public FromSqlExpression([NotNull] string sql, [NotNull] Expression arguments, [ Sql = sql; Arguments = arguments; } + /// /// Creates a new instance of the class. /// @@ -55,6 +56,7 @@ public FromSqlExpression([NotNull] string alias, [NotNull] string sql, [NotNull] /// The user-provided custom SQL for the table source. /// public virtual string Sql { get; } + /// /// The user-provided parameters passed to the custom SQL. /// diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 32136c75215..594302f3b16 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -242,21 +242,8 @@ internal SelectExpression(IEntityType entityType, ISqlExpressionFactory sqlExpre } static ColumnExpression GetColumn( - IProperty property, IEntityType currentEntityType, ITableBase table, TableExpressionBase tableExpression, bool nullable) - { - var columnMappings = table switch - { - IStoreFunction _ => property.GetFunctionColumnMappings().Cast(), - IView _ => property.GetViewColumnMappings().Cast(), - _ => property.GetTableColumnMappings().Cast() - }; - - var column = columnMappings - .Single(cm => cm.TableMapping.Table == table && cm.TableMapping.EntityType == currentEntityType) - .Column; - - return new ColumnExpression(property, column, tableExpression, nullable); - } + IProperty property, IEntityType currentEntityType, ITableBase table, TableExpressionBase tableExpression, bool nullable) + => new ColumnExpression(property, table.FindColumn(property), tableExpression, nullable); } internal SelectExpression(IEntityType entityType, TableExpressionBase tableExpression) @@ -265,9 +252,11 @@ internal SelectExpression(IEntityType entityType, TableExpressionBase tableExpre _tables.Add(tableExpression); var propertyExpressions = new Dictionary(); + var defaultTable = entityType.GetDefaultMappings().Single().Table; foreach (var property in GetAllPropertiesInHierarchy(entityType)) { - propertyExpressions[property] = new ColumnExpression(property, null, tableExpression, nullable: false); + propertyExpressions[property] = new ColumnExpression( + property, defaultTable.FindColumn(property), tableExpression, nullable: false); } var entityProjection = new EntityProjectionExpression(entityType, propertyExpressions); diff --git a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs index 1bd17c3b16c..678782921fd 100644 --- a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs +++ b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs @@ -495,7 +495,7 @@ public static void PossibleUnintendedCollectionNavigationNullComparisonWarning( { definition.Log( diagnostics, - $"{navigation.DeclaringEntityType.Name}.{navigation.TargetEntityType.Name}"); + $"{navigation.DeclaringEntityType.DisplayName()}.{navigation.Name}"); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -513,7 +513,7 @@ private static string PossibleUnintendedCollectionNavigationNullComparisonWarnin { var d = (EventDefinition)definition; var p = (NavigationEventData)payload; - return d.GenerateMessage($"{p.Navigation.DeclaringEntityType.Name}.{p.Navigation.TargetEntityType.Name}"); + return d.GenerateMessage($"{p.Navigation.DeclaringEntityType.DisplayName()}.{p.Navigation.Name}"); } /// diff --git a/src/EFCore/Extensions/EntityTypeExtensions.cs b/src/EFCore/Extensions/EntityTypeExtensions.cs index f9030e1d49d..959828a3fc4 100644 --- a/src/EFCore/Extensions/EntityTypeExtensions.cs +++ b/src/EFCore/Extensions/EntityTypeExtensions.cs @@ -331,6 +331,15 @@ private static string DisplayNameDefault(this ITypeBase type) /// The display name. [DebuggerStepThrough] public static string DisplayName([NotNull] this ITypeBase type) + => type.FullName(); + + /// + /// Gets the unique name for the given . + /// + /// The entity type. + /// The display name. + [DebuggerStepThrough] + public static string FullName([NotNull] this ITypeBase type) { if (!(type is IEntityType entityType) || !entityType.HasDefiningNavigation()) diff --git a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs index 5b35718dee1..adc26c15962 100644 --- a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs @@ -660,7 +660,7 @@ IConventionSkipNavigationBuilder HasSkipNavigation( /// /// The same builder instance if the query was set, otherwise. /// - [Obsolete("Use InMemoryEntityTypeBuilderExtensions.HasDefiningQuery")] + [Obsolete("Use InMemoryEntityTypeBuilderExtensions.ToQuery")] IConventionEntityTypeBuilder HasDefiningQuery([CanBeNull] LambdaExpression query, bool fromDataAnnotation = false); /// diff --git a/src/EFCore/Metadata/Conventions/ManyToManyAssociationEntityTypeConvention.cs b/src/EFCore/Metadata/Conventions/ManyToManyAssociationEntityTypeConvention.cs index 6e766f8dd67..8cbf10370a7 100644 --- a/src/EFCore/Metadata/Conventions/ManyToManyAssociationEntityTypeConvention.cs +++ b/src/EFCore/Metadata/Conventions/ManyToManyAssociationEntityTypeConvention.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -98,14 +98,18 @@ private void CreateAssociationEntityType( var model = declaringEntityType.Model; // create the association entity type - var otherIdentifiers = model.GetEntityTypes().ToDictionary(et => et.Name, et => 0); - var associationEntityTypeName = Uniquifier.Uniquify( - string.Format( + var associationEntityTypeName = string.Format( AssociationEntityTypeNameTemplate, declaringEntityType.ShortName(), - inverseEntityType.ShortName()), - otherIdentifiers, - int.MaxValue); + inverseEntityType.ShortName()); + if (model.FindEntityType(associationEntityTypeName) != null) + { + var otherIdentifiers = model.GetEntityTypes().ToDictionary(et => et.Name, et => 0); + associationEntityTypeName = Uniquifier.Uniquify( + associationEntityTypeName, + otherIdentifiers, + int.MaxValue); + } var associationEntityTypeBuilder = model.Builder.SharedEntity( associationEntityTypeName, Model.DefaultPropertyBagType, ConfigurationSource.Convention); diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index 4d43ea5c78c..b36437363e5 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -1177,7 +1177,7 @@ public virtual ForeignKey FindDeclaredForeignKey( foreach (var fk in FindDeclaredForeignKeys(properties)) { if (PropertyListComparer.Instance.Equals(fk.PrincipalKey.Properties, principalKey.Properties) - && StringComparer.Ordinal.Equals(fk.PrincipalEntityType.Name, principalEntityType.Name)) + && fk.PrincipalEntityType == principalEntityType) { return fk; } diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index c4894254bca..da071771768 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -3160,7 +3160,7 @@ private InternalForeignKeyBuilder HasOwnership( return null; } - var newOtherOwnership = otherOwnership.Builder.IsWeakTypeDefinition(configurationSource); + var newOtherOwnership = otherOwnership.Builder.AddToDeclaringTypeDefinition(configurationSource); if (newOtherOwnership == null) { return null; diff --git a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs index b5494533e44..6e368d54030 100644 --- a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs @@ -1049,7 +1049,7 @@ public virtual InternalForeignKeyBuilder IsOwnership(bool? ownership, Configurat var fk = newRelationshipBuilder.Metadata; fk.DeclaringEntityType.Builder.HasNoRelationship(fk, fk.GetConfigurationSource()); - if (otherOwnership.Builder.IsWeakTypeDefinition(configurationSource) == null) + if (otherOwnership.Builder.AddToDeclaringTypeDefinition(configurationSource) == null) { return null; } @@ -1117,7 +1117,7 @@ public virtual bool CanSetIsOwnership(bool? ownership, ConfigurationSource? conf /// 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 InternalForeignKeyBuilder IsWeakTypeDefinition(ConfigurationSource configurationSource) + public virtual InternalForeignKeyBuilder AddToDeclaringTypeDefinition(ConfigurationSource configurationSource) { if (Metadata.DeclaringEntityType.HasDefiningNavigation()) { diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index b0bf9799d82..e443c1f19a5 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -65,6 +65,8 @@ public void Test_new_annotations_handled_for_entity_types() RelationalAnnotationNames.ColumnType, RelationalAnnotationNames.TableColumnMappings, RelationalAnnotationNames.ViewColumnMappings, + RelationalAnnotationNames.SqlQueryColumnMappings, + RelationalAnnotationNames.FunctionColumnMappings, RelationalAnnotationNames.RelationalOverrides, RelationalAnnotationNames.DefaultValueSql, RelationalAnnotationNames.ComputedColumnSql, @@ -156,8 +158,10 @@ public void Test_new_annotations_handled_for_properties() RelationalAnnotationNames.Schema, RelationalAnnotationNames.ViewSchema, RelationalAnnotationNames.DefaultSchema, + RelationalAnnotationNames.DefaultMappings, RelationalAnnotationNames.TableMappings, RelationalAnnotationNames.ViewMappings, + RelationalAnnotationNames.SqlQueryMappings, RelationalAnnotationNames.Name, RelationalAnnotationNames.Sequences, RelationalAnnotationNames.CheckConstraints, diff --git a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs index 83211a893e7..055d1a03d00 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs @@ -232,10 +232,16 @@ private void AssertAnnotations(IMutableAnnotatable element) foreach (var annotationName in GetAnnotationNames() .Where(a => a != RelationalAnnotationNames.MaxIdentifierLength && a != RelationalAnnotationNames.RelationalModel + && a != RelationalAnnotationNames.DefaultMappings + && a != RelationalAnnotationNames.DefaultColumnMappings && a != RelationalAnnotationNames.TableMappings && a != RelationalAnnotationNames.TableColumnMappings && a != RelationalAnnotationNames.ViewMappings && a != RelationalAnnotationNames.ViewColumnMappings + && a != RelationalAnnotationNames.SqlQueryMappings + && a != RelationalAnnotationNames.SqlQueryColumnMappings + && a != RelationalAnnotationNames.FunctionMappings + && a != RelationalAnnotationNames.FunctionColumnMappings && a != RelationalAnnotationNames.ForeignKeyMappings && a != RelationalAnnotationNames.TableIndexMappings && a != RelationalAnnotationNames.UniqueConstraintMappings diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 9595aef8b4c..4a3f3248f45 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -704,7 +704,7 @@ public virtual void EntityType_annotations_are_stored_in_snapshot() });"), o => { - Assert.Equal(5, o.GetEntityTypes().First().GetAnnotations().Count()); + Assert.Equal(6, o.GetEntityTypes().First().GetAnnotations().Count()); Assert.Equal("AnnotationValue", o.GetEntityTypes().First()["AnnotationName"]); }); } @@ -2753,7 +2753,7 @@ public virtual void Property_multiple_annotations_are_stored_in_snapshot() o => { var property = o.GetEntityTypes().First().FindProperty("AlternateId"); - Assert.Equal(5, property.GetAnnotations().Count()); + Assert.Equal(6, property.GetAnnotations().Count()); Assert.Equal("AnnotationValue", property["AnnotationName"]); Assert.Equal("CName", property["Relational:ColumnName"]); Assert.Equal("int", property["Relational:ColumnType"]); diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index f88d41bdce5..cb473a53d3f 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -8,7 +8,6 @@ using System.Reflection; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics.Internal; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -1297,7 +1296,29 @@ public virtual void Detects_invalid_view_overrides() var property = modelBuilder.Entity().Property(a => a.Name).GetInfrastructure(); property.HasColumnName("DogName", StoreObjectIdentifier.View("Dog", null)); - VerifyError(RelationalStrings.TableOverrideMismatch("Animal.Name", "Dog"), + VerifyError(RelationalStrings.ViewOverrideMismatch("Animal.Name", "Dog"), + modelBuilder.Model); + } + + [ConditionalFact] + public virtual void Detects_invalid_sql_query_overrides() + { + var modelBuilder = CreateConventionalModelBuilder(); + var property = modelBuilder.Entity().Property(a => a.Name).GetInfrastructure(); + property.HasColumnName("DogName", StoreObjectIdentifier.SqlQuery("Dog")); + + VerifyError(RelationalStrings.SqlQueryOverrideMismatch("Animal.Name", "Dog"), + modelBuilder.Model); + } + + [ConditionalFact] + public virtual void Detects_invalid_function_overrides() + { + var modelBuilder = CreateConventionalModelBuilder(); + var property = modelBuilder.Entity().Property(a => a.Name).GetInfrastructure(); + property.HasColumnName("DogName", StoreObjectIdentifier.DbFunction("Dog")); + + VerifyError(RelationalStrings.FunctionOverrideMismatch("Animal.Name", "Dog"), modelBuilder.Model); } diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index f9ec339a82a..0ee408ff6e8 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -29,6 +29,7 @@ public void Can_use_relational_model_with_tables(bool useExplicitMapping, Mappin Assert.Empty(model.Views); Assert.True(model.Model.GetEntityTypes().All(et => !et.GetViewMappings().Any())); + AssertDefaultMappings(model); AssertTables(model, useExplicitMapping ? mapping : Mapping.TPH); } @@ -44,6 +45,7 @@ public void Can_use_relational_model_with_views(Mapping mapping) Assert.Empty(model.Tables); Assert.True(model.Model.GetEntityTypes().All(et => !et.GetTableMappings().Any())); + AssertDefaultMappings(model); AssertViews(model, mapping); } @@ -58,10 +60,81 @@ public void Can_use_relational_model_with_views_and_tables(Mapping mapping) Assert.Equal(mapping == Mapping.TPH ? 3 : 4, model.Tables.Count()); Assert.Equal(mapping == Mapping.TPH ? 3 : 4, model.Views.Count()); + AssertDefaultMappings(model); AssertTables(model, mapping); AssertViews(model, mapping); } + private static void AssertDefaultMappings(IRelationalModel model) + { + var orderType = model.Model.FindEntityType(typeof(Order)); + var orderMapping = orderType.GetDefaultMappings().Single(); + Assert.True(orderMapping.IncludesDerivedTypes); + Assert.Equal( + new[] { nameof(Order.AlternateId), nameof(Order.CustomerId), nameof(Order.OrderDate), nameof(Order.OrderId) }, + orderMapping.ColumnMappings.Select(m => m.Property.Name)); + + var ordersTable = orderMapping.Table; + Assert.Equal(new[] { nameof(Order) }, ordersTable.EntityTypeMappings.Select(m => m.EntityType.DisplayName())); + Assert.Equal(new[] { + nameof(Order.AlternateId), + nameof(Order.CustomerId), + "OrderDate", + nameof(Order.OrderId) + }, + ordersTable.Columns.Select(m => m.Name)); + Assert.Equal("Order", ordersTable.Name); + Assert.Null(ordersTable.Schema); + + var orderDate = orderType.FindProperty(nameof(Order.OrderDate)); + + var orderDateMapping = orderDate.GetDefaultColumnMappings().Single(); + Assert.NotNull(orderDateMapping.TypeMapping); + Assert.Equal("default_datetime_mapping", orderDateMapping.TypeMapping.StoreType); + Assert.Same(orderMapping, orderDateMapping.TableMapping); + + var orderDetailsOwnership = orderType.FindNavigation(nameof(Order.Details)).ForeignKey; + var orderDetailsType = orderDetailsOwnership.DeclaringEntityType; + var orderDetailsTable = orderDetailsType.GetDefaultMappings().Single().Table; + Assert.NotEqual(ordersTable, orderDetailsTable); + Assert.Empty(ordersTable.GetReferencingRowInternalForeignKeys(orderType)); + + var orderDetailsDate = orderDetailsType.FindProperty(nameof(OrderDetails.OrderDate)); + Assert.Equal(new[] { orderDetailsDate }, orderDetailsTable.FindColumn("OrderDate").PropertyMappings.Select(m => m.Property)); + + var orderDateColumn = orderDateMapping.Column; + Assert.Same(orderDateColumn, ordersTable.FindColumn("OrderDate")); + Assert.Same(orderDateColumn, ordersTable.FindColumn(orderDate)); + Assert.Equal(new[] { orderDate }, orderDateColumn.PropertyMappings.Select(m => m.Property)); + Assert.Equal("OrderDate", orderDateColumn.Name); + Assert.Equal("default_datetime_mapping", orderDateColumn.StoreType); + Assert.False(orderDateColumn.IsNullable); + Assert.Same(ordersTable, orderDateColumn.Table); + + var customerType = model.Model.FindEntityType(typeof(Customer)); + var customerTable = customerType.GetDefaultMappings().Single().Table; + Assert.Equal("Customer", customerTable.Name); + Assert.Null(customerTable.Schema); + + var specialCustomerType = model.Model.FindEntityType(typeof(SpecialCustomer)); + var customerPk = specialCustomerType.FindPrimaryKey(); + + var specialCustomerDefaultMapping = specialCustomerType.GetDefaultMappings().Single(); + Assert.True(specialCustomerDefaultMapping.IsSplitEntityTypePrincipal); + Assert.True(specialCustomerDefaultMapping.IncludesDerivedTypes); + + var specialCustomerTable = specialCustomerDefaultMapping.Table; + Assert.Equal(customerTable, specialCustomerTable); + + Assert.Equal(2, specialCustomerTable.EntityTypeMappings.Count()); + Assert.True(specialCustomerTable.EntityTypeMappings.First().IsSharedTablePrincipal); + + Assert.Equal(specialCustomerType.GetDiscriminatorProperty() == null ? 7 : 8, specialCustomerTable.Columns.Count()); + + var specialityColumn = specialCustomerTable.Columns.Single(c => c.Name == nameof(SpecialCustomer.Speciality)); + Assert.Equal(specialCustomerType.GetDiscriminatorProperty() != null, specialityColumn.IsNullable); + } + private static void AssertViews(IRelationalModel model, Mapping mapping) { var orderType = model.Model.FindEntityType(typeof(Order)); @@ -109,6 +182,7 @@ private static void AssertViews(IRelationalModel model, Mapping mapping) var orderDateColumn = orderDateMapping.Column; Assert.Same(orderDateColumn, ordersView.FindColumn("OrderDate")); Assert.Same(orderDateColumn, orderDate.FindColumn(StoreObjectIdentifier.View(ordersView.Name, ordersView.Schema))); + Assert.Same(orderDateColumn, ordersView.FindColumn(orderDate)); Assert.Equal(new[] { orderDate, orderDetailsDate }, orderDateColumn.PropertyMappings.Select(m => m.Property)); Assert.Equal("OrderDate", orderDateColumn.Name); Assert.Equal("default_datetime_mapping", orderDateColumn.StoreType); @@ -203,6 +277,7 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) var orderDateColumn = orderDateMapping.Column; Assert.Same(orderDateColumn, ordersTable.FindColumn("OrderDate")); Assert.Same(orderDateColumn, orderDate.FindColumn(StoreObjectIdentifier.Table(ordersTable.Name, ordersTable.Schema))); + Assert.Same(orderDateColumn, ordersTable.FindColumn(orderDate)); Assert.Equal("OrderDate", orderDateColumn.Name); Assert.Equal("default_datetime_mapping", orderDateColumn.StoreType); Assert.False(orderDateColumn.IsNullable); @@ -537,6 +612,77 @@ public void Can_use_relational_model_with_keyless_TPH() Assert.True(specialityColumn.IsNullable); } + [ConditionalFact] + public void Can_use_relational_model_with_SQL_queries() + { + var modelBuilder = CreateConventionModelBuilder(); + modelBuilder.Entity(cb => + { + cb.ToQuerySql("GetOrders()"); + cb.Ignore(c => c.Customer); + cb.Ignore(c => c.Details); + cb.Ignore(c => c.DateDetails); + + cb.Property(c => c.AlternateId).HasColumnName("SomeName"); + cb.HasNoKey(); + }); + + var model = modelBuilder.FinalizeModel().GetRelationalModel(); + + Assert.Single(model.Model.GetEntityTypes()); + Assert.Single(model.Queries); + Assert.Empty(model.Views); + Assert.Empty(model.Tables); + Assert.Empty(model.Functions); + + var orderType = model.Model.FindEntityType(typeof(Order)); + Assert.Null(orderType.FindPrimaryKey()); + + var orderMapping = orderType.GetSqlQueryMappings().Single(); + Assert.True(orderMapping.IsSharedTablePrincipal); + Assert.True(orderMapping.IsSplitEntityTypePrincipal); + + Assert.True(orderMapping.IncludesDerivedTypes); + Assert.Equal( + new[] { nameof(Order.AlternateId), nameof(Order.CustomerId), nameof(Order.OrderDate), nameof(Order.OrderId) }, + orderMapping.ColumnMappings.Select(m => m.Property.Name)); + + var ordersQuery = orderMapping.SqlQuery; + Assert.Equal( + new[] { orderType }, + ordersQuery.EntityTypeMappings.Select(m => m.EntityType)); + Assert.Equal(new[] { + nameof(Order.CustomerId), + nameof(Order.OrderDate), + nameof(Order.OrderId), + "SomeName" + }, + ordersQuery.Columns.Select(m => m.Name)); + Assert.Equal("Microsoft.EntityFrameworkCore.Metadata.RelationalModelTest+Order.MappedSqlQuery", ordersQuery.Name); + Assert.Null(ordersQuery.Schema); + Assert.Equal("GetOrders()", ordersQuery.Sql); + Assert.False(ordersQuery.IsShared); + + var orderDate = orderType.FindProperty(nameof(Order.OrderDate)); + Assert.Single(orderDate.GetSqlQueryColumnMappings()); + var orderDateMapping = orderMapping.ColumnMappings.Single(m => m.Property == orderDate); + Assert.NotNull(orderDateMapping.TypeMapping); + Assert.Equal("default_datetime_mapping", orderDateMapping.TypeMapping.StoreType); + Assert.Same(orderMapping, orderDateMapping.SqlQueryMapping); + + var orderDateColumn = orderDateMapping.Column; + Assert.Same(orderDateColumn, ordersQuery.FindColumn(nameof(Order.OrderDate))); + Assert.Same(orderDateColumn, orderDate.FindColumn(StoreObjectIdentifier.SqlQuery(orderType))); + Assert.Same(orderDateColumn, ordersQuery.FindColumn(orderDate)); + Assert.Equal(new[] { orderDate }, orderDateColumn.PropertyMappings.Select(m => m.Property)); + Assert.Equal(nameof(Order.OrderDate), orderDateColumn.Name); + Assert.Equal("default_datetime_mapping", orderDateColumn.StoreType); + Assert.False(orderDateColumn.IsNullable); + Assert.Same(ordersQuery, orderDateColumn.SqlQuery); + + Assert.Same(orderMapping, ordersQuery.EntityTypeMappings.Single()); + } + private static IQueryable GetOrdersForCustomer(int id) => throw new NotImplementedException(); @@ -611,6 +757,8 @@ public void Can_use_relational_model_with_functions() var orderDateColumn = orderDateMapping.Column; Assert.Same(orderDateColumn, ordersFunction.FindColumn(nameof(Order.OrderDate))); + Assert.Same(orderDateColumn, orderDate.FindColumn(StoreObjectIdentifier.DbFunction(ordersFunction.Name))); + Assert.Same(orderDateColumn, ordersFunction.FindColumn(orderDate)); Assert.Equal(new[] { orderDate }, orderDateColumn.PropertyMappings.Select(m => m.Property)); Assert.Equal(nameof(Order.OrderDate), orderDateColumn.Name); Assert.Equal("default_datetime_mapping", orderDateColumn.StoreType); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index 35062fe17f2..5a3d2d1e1d5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -1216,11 +1216,11 @@ public override void From_sql_composed_with_relational_null_comparison() base.From_sql_composed_with_relational_null_comparison(); AssertSql( - @"SELECT [e].[Id], [e].[BoolA], [e].[BoolB], [e].[BoolC], [e].[IntA], [e].[IntB], [e].[IntC], [e].[NullableBoolA], [e].[NullableBoolB], [e].[NullableBoolC], [e].[NullableIntA], [e].[NullableIntB], [e].[NullableIntC], [e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC], [e].[StringA], [e].[StringB], [e].[StringC] + @"SELECT [n].[Id], [n].[BoolA], [n].[BoolB], [n].[BoolC], [n].[IntA], [n].[IntB], [n].[IntC], [n].[NullableBoolA], [n].[NullableBoolB], [n].[NullableBoolC], [n].[NullableIntA], [n].[NullableIntB], [n].[NullableIntC], [n].[NullableStringA], [n].[NullableStringB], [n].[NullableStringC], [n].[StringA], [n].[StringB], [n].[StringC] FROM ( SELECT * FROM ""Entities1"" -) AS [e] -WHERE [e].[StringA] = [e].[StringB]"); +) AS [n] +WHERE [n].[StringA] = [n].[StringB]"); } public override async Task Projecting_nullable_bool_with_coalesce(bool async)