diff --git a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs index aed8bea7b3f..78dafa7d9ad 100644 --- a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs +++ b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations.Design; using Microsoft.EntityFrameworkCore.Migrations.Internal; @@ -107,6 +108,7 @@ public static class DesignTimeServiceCollectionExtensions .AddTransient(_ => context.GetService()) .AddTransient(_ => context.GetService()) .AddTransient(_ => context.GetService()) - .AddTransient(_ => context.GetService()); + .AddTransient(_ => context.GetService()) + .AddTransient(_ => context.GetService()); } } diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 8b6b0440df8..b9a7fd86cac 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -60,11 +60,10 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui ChangeDetector.SkipDetectChangesAnnotation, CoreAnnotationNames.ChangeTrackingStrategy, CoreAnnotationNames.OwnedTypes, + RelationalAnnotationNames.RelationalModel, RelationalAnnotationNames.CheckConstraints, RelationalAnnotationNames.Sequences, - RelationalAnnotationNames.DbFunctions, - RelationalAnnotationNames.Tables, - RelationalAnnotationNames.Views); + RelationalAnnotationNames.DbFunctions); if (annotations.Count > 0) { diff --git a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs index 6c0473a2a30..bc89715a9a7 100644 --- a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs @@ -241,13 +241,12 @@ private IEnumerable GetAnnotationNamespaces(IEnumerable it CoreAnnotationNames.ValueGeneratorFactory, CoreAnnotationNames.DefiningQuery, CoreAnnotationNames.QueryFilter, + RelationalAnnotationNames.RelationalModel, RelationalAnnotationNames.CheckConstraints, RelationalAnnotationNames.Sequences, RelationalAnnotationNames.DbFunctions, - RelationalAnnotationNames.Tables, RelationalAnnotationNames.TableMappings, RelationalAnnotationNames.TableColumnMappings, - RelationalAnnotationNames.Views, RelationalAnnotationNames.ViewMappings, RelationalAnnotationNames.ViewColumnMappings, RelationalAnnotationNames.ForeignKeyMappings, diff --git a/src/EFCore.Design/Migrations/Design/MigrationsScaffolder.cs b/src/EFCore.Design/Migrations/Design/MigrationsScaffolder.cs index 2b08ea6afd4..fe762243212 100644 --- a/src/EFCore.Design/Migrations/Design/MigrationsScaffolder.cs +++ b/src/EFCore.Design/Migrations/Design/MigrationsScaffolder.cs @@ -129,10 +129,11 @@ public MigrationsScaffolder([NotNull] MigrationsScaffolderDependencies dependenc } var modelSnapshot = Dependencies.MigrationsAssembly.ModelSnapshot; - var lastModel = Dependencies.SnapshotModelProcessor.Process(modelSnapshot?.Model); - var upOperations = Dependencies.MigrationsModelDiffer.GetDifferences(lastModel, Dependencies.Model); + var lastModel = Dependencies.SnapshotModelProcessor.Process(modelSnapshot?.Model)?.GetRelationalModel(); + var upOperations = Dependencies.MigrationsModelDiffer + .GetDifferences(lastModel, Dependencies.Model.GetRelationalModel()); var downOperations = upOperations.Count > 0 - ? Dependencies.MigrationsModelDiffer.GetDifferences(Dependencies.Model, lastModel) + ? Dependencies.MigrationsModelDiffer.GetDifferences(Dependencies.Model.GetRelationalModel(), lastModel) : new List(); var migrationId = Dependencies.MigrationsIdGenerator.GenerateId(migrationName); var modelSnapshotNamespace = GetNamespace(modelSnapshot?.GetType(), migrationNamespace); @@ -245,7 +246,7 @@ public virtual MigrationFiles RemoveMigration([NotNull] string projectDir, [NotN model = migration.TargetModel; if (!Dependencies.MigrationsModelDiffer.HasDifferences( - model, Dependencies.SnapshotModelProcessor.Process(modelSnapshot.Model))) + model.GetRelationalModel(), Dependencies.SnapshotModelProcessor.Process(modelSnapshot.Model).GetRelationalModel())) { var applied = false; try diff --git a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs index 7aea5fd4d2e..2d4f04cd2a8 100644 --- a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs +++ b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs @@ -11,6 +11,7 @@ using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Migrations.Internal @@ -25,6 +26,7 @@ public class SnapshotModelProcessor : ISnapshotModelProcessor { private readonly IOperationReporter _operationReporter; private readonly HashSet _relationalNames; + private readonly IConventionSetBuilder _conventionSetBuilder; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -33,7 +35,8 @@ public class SnapshotModelProcessor : ISnapshotModelProcessor /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public SnapshotModelProcessor( - [NotNull] IOperationReporter operationReporter) + [NotNull] IOperationReporter operationReporter, + [NotNull] IConventionSetBuilder conventionSetBuilder) { _operationReporter = operationReporter; _relationalNames = new HashSet( @@ -41,6 +44,7 @@ public class SnapshotModelProcessor : ISnapshotModelProcessor .GetRuntimeFields() .Where(p => p.Name != nameof(RelationalAnnotationNames.Prefix)) .Select(p => ((string)p.GetValue(null)).Substring(RelationalAnnotationNames.Prefix.Length - 1))); + _conventionSetBuilder = conventionSetBuilder; } /// @@ -80,7 +84,19 @@ public virtual IModel Process(IModel model) if (model is IConventionModel conventionModel) { - model = new RelationalModelConvention().ProcessModelFinalized(conventionModel); + var conventionSet = _conventionSetBuilder.CreateConventionSet(); + + var typeMappingConvention = conventionSet.ModelFinalizingConventions.OfType().FirstOrDefault(); + if (typeMappingConvention != null) + { + typeMappingConvention.ProcessModelFinalizing(conventionModel.Builder, null); + } + + var relationalModelConvention = conventionSet.ModelFinalizedConventions.OfType().FirstOrDefault(); + if (relationalModelConvention != null) + { + model = relationalModelConvention.ProcessModelFinalized(conventionModel); + } } return model is IMutableModel mutableModel diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index a9a23d66cf6..12341702c57 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -263,10 +263,9 @@ private void GenerateEntityTypeErrors(IModel model) RemoveAnnotation(ref annotations, CoreAnnotationNames.OwnedTypes); RemoveAnnotation(ref annotations, ChangeDetector.SkipDetectChangesAnnotation); RemoveAnnotation(ref annotations, RelationalAnnotationNames.MaxIdentifierLength); + RemoveAnnotation(ref annotations, RelationalAnnotationNames.RelationalModel); RemoveAnnotation(ref annotations, RelationalAnnotationNames.CheckConstraints); RemoveAnnotation(ref annotations, RelationalAnnotationNames.Sequences); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.Tables); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.Views); RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.DatabaseName); RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.EntityTypeErrors); diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index a01c7635a4f..f5471a0dcf9 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -521,9 +521,10 @@ public static void SetComment([NotNull] this IMutableEntityType entityType, [Can /// A value indicating whether the entity type is ignored by Migrations. public static bool IsIgnoredByMigrations([NotNull] this IEntityType entityType) { - if (entityType.BaseType != null) + if (entityType.BaseType != null + && entityType.BaseType.IsIgnoredByMigrations()) { - return entityType.BaseType.IsIgnoredByMigrations(); + return true; } if (entityType.GetTableName() != null) @@ -532,7 +533,9 @@ public static bool IsIgnoredByMigrations([NotNull] this IEntityType entityType) } if (entityType.FindAnnotation(RelationalAnnotationNames.QueryableFunctionResultType) != null) + { return true; + } var viewDefinition = entityType.FindAnnotation(RelationalAnnotationNames.ViewDefinition); if (viewDefinition?.Value != null) diff --git a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs index 58bdf216721..7bfbf1ca688 100644 --- a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs @@ -1,10 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; @@ -60,53 +62,19 @@ public static void SetDefaultSchema([NotNull] this IMutableModel model, [CanBeNu => model.FindAnnotation(RelationalAnnotationNames.DefaultSchema)?.GetConfigurationSource(); /// - /// Returns all the tables mapped in the model. + /// Returns the database model. /// - /// The model to get the tables for. - /// All the tables mapped in the model. - public static IEnumerable GetTables([NotNull] this IModel model) => - ((IDictionary<(string, string), Table>)model[RelationalAnnotationNames.Tables])?.Values - ?? Enumerable.Empty(); - - /// - /// Gets the table with a given name. Returns null if no table with the given name is defined. - /// - /// The model to get the table for. - /// The name of the table. - /// The schema of the table. - /// The table with a given name or null if no table with the given name is defined. - public static ITable FindTable([NotNull] this IModel model, [NotNull] string name, [CanBeNull] string schema) + /// The model to get the database model for. + /// The database model. + public static IRelationalModel GetRelationalModel([NotNull] this IModel model) { - Table table = null; - return ((IDictionary<(string, string), Table>)model[RelationalAnnotationNames.Tables]) -?.TryGetValue((name, schema), out table) == true - ? table - : null; - } - - /// - /// Returns all the views mapped in the model. - /// - /// The model to get the views for. - /// All the views mapped in the model. - public static IEnumerable GetViews([NotNull] this IModel model) => - ((IDictionary<(string, string), View>)model[RelationalAnnotationNames.Views])?.Values - ?? Enumerable.Empty(); + var databaseModel = (IRelationalModel)model[RelationalAnnotationNames.RelationalModel]; + if (databaseModel == null) + { + throw new InvalidOperationException(RelationalStrings.DatabaseModelMissing); + } - /// - /// Gets the view with a given name. Returns null if no view with the given name is defined. - /// - /// The model to get the view for. - /// The name of the view. - /// The schema of the view. - /// The view with a given name or null if no view with the given name is defined. - public static IView FindView([NotNull] this IModel model, [NotNull] string name, [CanBeNull] string schema) - { - View view = null; - return ((IDictionary<(string, string), View>)model[RelationalAnnotationNames.Views]) - ?.TryGetValue((name, schema), out view) == true - ? view - : null; + return databaseModel; } /// @@ -295,7 +263,7 @@ public static IConventionDbFunction FindDbFunction([NotNull] this IConventionMod => (IConventionDbFunction)((IModel)model).FindDbFunction(method); /// - /// Finds a that is mapped to the method represented by the given . + /// Finds a that is mapped to the method represented by the given name. /// /// The model to find the function in. /// The model name of the function. @@ -306,7 +274,7 @@ public static IDbFunction FindDbFunction([NotNull] this IModel model, [NotNull] Check.NotNull(name, nameof(name))); /// - /// Finds a that is mapped to the method represented by the given . + /// Finds a that is mapped to the method represented by the given name. /// /// The model to find the function in. /// The model name of the function. @@ -315,7 +283,7 @@ public static IMutableDbFunction FindDbFunction([NotNull] this IMutableModel mod => (IMutableDbFunction)((IModel)model).FindDbFunction(name); /// - /// Finds a that is mapped to the method represented by the given . + /// Finds a that is mapped to the method represented by the given name. /// /// The model to find the function in. /// The model name of the function. diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs index e70312a710f..2c1b2ef9006 100644 --- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs +++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs @@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations.Internal; @@ -59,7 +60,7 @@ public class EntityFrameworkRelationalServicesBuilder : EntityFrameworkServicesB { typeof(IComparer), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IMigrationsIdGenerator), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(ISqlGenerationHelper), new ServiceCharacteristics(ServiceLifetime.Singleton) }, - { typeof(IMigrationsAnnotationProvider), new ServiceCharacteristics(ServiceLifetime.Singleton) }, + { typeof(IRelationalAnnotationProvider), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IMigrationCommandExecutor), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IRelationalTypeMappingSource), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IRelationalValueBufferFactoryFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, @@ -130,7 +131,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(); TryAdd(); - TryAdd(); + TryAdd(); TryAdd(); TryAdd(); TryAdd(); @@ -175,7 +176,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() - .AddDependencySingleton() + .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index 943eec3dbfc..80f96cdb351 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -397,28 +397,50 @@ private static bool IsIdentifyingPrincipal(IEntityType dependentEntityType, IEnt [NotNull] string columnName, [NotNull] string tableName) { - var currentTypeString = property.GetColumnType() - ?? property.GetRelationalTypeMapping().StoreType; - var previousTypeString = duplicateProperty.GetColumnType() - ?? duplicateProperty.GetRelationalTypeMapping().StoreType; - if (!string.Equals(currentTypeString, previousTypeString, StringComparison.OrdinalIgnoreCase)) + if (property.IsNullable != duplicateProperty.IsNullable) { throw new InvalidOperationException( - RelationalStrings.DuplicateColumnNameDataTypeMismatch( + RelationalStrings.DuplicateColumnNameNullabilityMismatch( + duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.Name, + property.DeclaringEntityType.DisplayName(), + property.Name, + columnName, + tableName)); + } + + var currentMaxLength = property.GetMaxLength(); + var previousMaxLength = duplicateProperty.GetMaxLength(); + if (currentMaxLength != previousMaxLength) + { + throw new InvalidOperationException( + RelationalStrings.DuplicateColumnNameMaxLengthMismatch( duplicateProperty.DeclaringEntityType.DisplayName(), duplicateProperty.Name, property.DeclaringEntityType.DisplayName(), property.Name, columnName, tableName, - previousTypeString, - currentTypeString)); + previousMaxLength, + currentMaxLength)); } - if (property.IsNullable != duplicateProperty.IsNullable) + if (property.IsUnicode() != duplicateProperty.IsUnicode()) { throw new InvalidOperationException( - RelationalStrings.DuplicateColumnNameNullabilityMismatch( + RelationalStrings.DuplicateColumnNameUnicodenessMismatch( + duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.Name, + property.DeclaringEntityType.DisplayName(), + property.Name, + columnName, + tableName)); + } + + if (property.IsFixedLength() != duplicateProperty.IsFixedLength()) + { + throw new InvalidOperationException( + RelationalStrings.DuplicateColumnNameFixedLengthMismatch( duplicateProperty.DeclaringEntityType.DisplayName(), duplicateProperty.Name, property.DeclaringEntityType.DisplayName(), @@ -427,6 +449,36 @@ private static bool IsIdentifyingPrincipal(IEntityType dependentEntityType, IEnt tableName)); } + if (property.IsConcurrencyToken != duplicateProperty.IsConcurrencyToken) + { + throw new InvalidOperationException( + RelationalStrings.DuplicateColumnNameConcurrencyTokenMismatch( + duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.Name, + property.DeclaringEntityType.DisplayName(), + property.Name, + columnName, + tableName)); + } + + var currentTypeString = property.GetColumnType() + ?? property.GetRelationalTypeMapping().StoreType; + var previousTypeString = duplicateProperty.GetColumnType() + ?? duplicateProperty.GetRelationalTypeMapping().StoreType; + if (!string.Equals(currentTypeString, previousTypeString, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException( + RelationalStrings.DuplicateColumnNameDataTypeMismatch( + duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.Name, + property.DeclaringEntityType.DisplayName(), + property.Name, + columnName, + tableName, + previousTypeString, + currentTypeString)); + } + var currentComputedColumnSql = property.GetComputedColumnSql() ?? ""; var previousComputedColumnSql = duplicateProperty.GetComputedColumnSql() ?? ""; if (!currentComputedColumnSql.Equals(previousComputedColumnSql, StringComparison.OrdinalIgnoreCase)) @@ -447,7 +499,12 @@ private static bool IsIdentifyingPrincipal(IEntityType dependentEntityType, IEnt var previousDefaultValue = duplicateProperty.GetDefaultValue(); if (!Equals(currentDefaultValue, previousDefaultValue)) { - throw new InvalidOperationException( + currentDefaultValue = GetDefaultColumnValue(property); + previousDefaultValue = GetDefaultColumnValue(duplicateProperty); + + if (!Equals(currentDefaultValue, previousDefaultValue)) + { + throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameDefaultSqlMismatch( duplicateProperty.DeclaringEntityType.DisplayName(), duplicateProperty.Name, @@ -457,6 +514,7 @@ private static bool IsIdentifyingPrincipal(IEntityType dependentEntityType, IEnt tableName, previousDefaultValue ?? "NULL", currentDefaultValue ?? "NULL")); + } } var currentDefaultValueSql = property.GetDefaultValueSql() ?? ""; @@ -492,6 +550,21 @@ private static bool IsIdentifyingPrincipal(IEntityType dependentEntityType, IEnt } } + /// + /// Returns the object that is used as the default value for the column the property is mapped to. + /// + /// The property to get the default value for. + /// The object that is used as the default value for the column the property is mapped to. + protected virtual object GetDefaultColumnValue([NotNull] IProperty property) + { + var value = property.GetDefaultValue(); + var converter = property.GetValueConverter() ?? property.FindRelationalTypeMapping()?.Converter; + + return converter != null + ? converter.ConvertToProvider(value) + : value; + } + /// /// Validates the compatibility of foreign keys in a given shared table. /// diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs index c4e41b7a835..1bca898bf86 100644 --- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs +++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs @@ -100,7 +100,7 @@ public override ConventionSet CreateConventionSet() ConventionSet.AddAfter( conventionSet.ModelFinalizedConventions, - new RelationalModelConvention(), + new RelationalModelConvention(Dependencies, RelationalDependencies), typeof(ValidatingConvention)); return conventionSet; diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilderDependencies.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilderDependencies.cs index b4c2cf6057b..da0feadbae2 100644 --- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilderDependencies.cs +++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilderDependencies.cs @@ -1,7 +1,9 @@ // 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.Utilities; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure @@ -57,8 +59,24 @@ public sealed class RelationalConventionSetBuilderDependencies /// /// [EntityFrameworkInternal] - public RelationalConventionSetBuilderDependencies() + public RelationalConventionSetBuilderDependencies([NotNull] IRelationalAnnotationProvider relationalAnnotationProvider) { + Check.NotNull(relationalAnnotationProvider, nameof(relationalAnnotationProvider)); + + RelationalAnnotationProvider = relationalAnnotationProvider; } + + /// + /// The relational annotation provider. + /// + public IRelationalAnnotationProvider RelationalAnnotationProvider { get; } + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// A replacement for the current dependency of this type. + /// A new parameter object with the given service replaced. + public RelationalConventionSetBuilderDependencies With([NotNull] IRelationalAnnotationProvider relationalAnnotationProvider) + => new RelationalConventionSetBuilderDependencies(relationalAnnotationProvider); } } diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalModelConvention.cs index 6d009a35914..fb4812e2659 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalModelConvention.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions @@ -10,8 +12,27 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions /// public class RelationalModelConvention : IModelFinalizedConvention { + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + /// Parameter object containing relational dependencies for this convention. + public RelationalModelConvention( + [NotNull] ProviderConventionSetBuilderDependencies dependencies, + [NotNull] RelationalConventionSetBuilderDependencies relationalDependencies) + { + RelationalDependencies = relationalDependencies; + } + + /// + /// The service dependencies for + /// + protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; } + /// public virtual IModel ProcessModelFinalized(IModel model) - => model is IConventionModel conventionModel ? RelationalModel.AddRelationalModel(conventionModel) : model; + => model is IConventionModel conventionModel + ? RelationalModel.Add(conventionModel, RelationalDependencies.RelationalAnnotationProvider) + : model; } } diff --git a/src/EFCore.Relational/Metadata/ICheckConstraint.cs b/src/EFCore.Relational/Metadata/ICheckConstraint.cs index c8e5620b8cd..fb65f02beb5 100644 --- a/src/EFCore.Relational/Metadata/ICheckConstraint.cs +++ b/src/EFCore.Relational/Metadata/ICheckConstraint.cs @@ -1,12 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.EntityFrameworkCore.Infrastructure; + namespace Microsoft.EntityFrameworkCore.Metadata { /// /// Represents a check constraint in the . /// - public interface ICheckConstraint + public interface ICheckConstraint : IAnnotatable { /// /// Gets the name of the check constraint in the database. diff --git a/src/EFCore.Relational/Metadata/IColumn.cs b/src/EFCore.Relational/Metadata/IColumn.cs index 3dcd9532cee..68cbab91468 100644 --- a/src/EFCore.Relational/Metadata/IColumn.cs +++ b/src/EFCore.Relational/Metadata/IColumn.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Linq; namespace Microsoft.EntityFrameworkCore.Metadata { @@ -19,5 +20,60 @@ public interface IColumn : IColumnBase /// Gets the property mappings. /// new IEnumerable PropertyMappings { get; } + + /// + /// Gets the maximum length of data that is allowed in this column. For example, if the property is a ' + /// then this is the maximum number of characters. + /// + int? MaxLength => PropertyMappings.First().Property.GetMaxLength(); + + /// + /// Gets a value indicating whether or not the property can persist Unicode characters. + /// + bool? IsUnicode => PropertyMappings.First().Property.IsUnicode(); + + /// + /// Returns a flag indicating if the property as capable of storing only fixed-length data, such as strings. + /// + bool? IsFixedLength => PropertyMappings.First().Property.IsFixedLength(); + + /// + /// Indicates whether or not this column acts as an automatic concurrency token by generating a different value + /// on every update in the same vein as 'rowversion'/'timestamp' columns on SQL Server. + /// + bool IsRowVersion => PropertyMappings.First().Property.IsConcurrencyToken + && PropertyMappings.First().Property.ValueGenerated == ValueGenerated.OnAddOrUpdate; + + /// + /// Returns the object that is used as the default value for this column. + /// + public virtual object GetDefaultValue + { + get + { + var property = PropertyMappings.First().Property; + var value = property.GetDefaultValue(); + var converter = property.GetValueConverter() ?? PropertyMappings.First().TypeMapping?.Converter; + + return converter != null + ? converter.ConvertToProvider(value) + : value; + } + } + + /// + /// Returns the SQL expression that is used as the default value for this column. + /// + public virtual string DefaultValueSql => PropertyMappings.First().Property.GetDefaultValueSql(); + + /// + /// Returns the SQL expression that is used as the computed value for this column. + /// + public virtual string ComputedColumnSql => PropertyMappings.First().Property.GetComputedColumnSql(); + + /// + /// Comment for this column + /// + public virtual string Comment => PropertyMappings.First().Property.GetComment(); } } diff --git a/src/EFCore.Relational/Metadata/IConventionCheckConstraint.cs b/src/EFCore.Relational/Metadata/IConventionCheckConstraint.cs index b1d4a3ec8e2..5abe78eb7a7 100644 --- a/src/EFCore.Relational/Metadata/IConventionCheckConstraint.cs +++ b/src/EFCore.Relational/Metadata/IConventionCheckConstraint.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata /// /// Represents a check constraint in the . /// - public interface IConventionCheckConstraint : ICheckConstraint + public interface IConventionCheckConstraint : ICheckConstraint, IConventionAnnotatable { /// /// Gets the in which this check constraint is defined. diff --git a/src/EFCore.Relational/Metadata/IConventionDbFunction.cs b/src/EFCore.Relational/Metadata/IConventionDbFunction.cs index 9ec5ff1d29c..dbb03463f44 100644 --- a/src/EFCore.Relational/Metadata/IConventionDbFunction.cs +++ b/src/EFCore.Relational/Metadata/IConventionDbFunction.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata /// Represents a relational database function in an in /// the a form that can be mutated while the model is being built. /// - public interface IConventionDbFunction : IDbFunction + public interface IConventionDbFunction : IConventionAnnotatable, IDbFunction { /// /// Gets the in which this function is defined. @@ -24,7 +24,7 @@ public interface IConventionDbFunction : IDbFunction /// /// Gets the builder that can be used to configure this function. /// - IConventionDbFunctionBuilder Builder { get; } + new IConventionDbFunctionBuilder Builder { get; } /// /// Gets the configuration source for this . diff --git a/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.cs b/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.cs index e2944154900..0621434c77c 100644 --- a/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata /// /// Represents a by-convention database function parameter in an . /// - public interface IConventionDbFunctionParameter : IDbFunctionParameter + public interface IConventionDbFunctionParameter : IConventionAnnotatable, IDbFunctionParameter { /// /// The to which this parameter belongs. @@ -19,7 +19,7 @@ public interface IConventionDbFunctionParameter : IDbFunctionParameter /// /// The for building a by-convention function parameter. /// - IConventionDbFunctionParameterBuilder Builder { get; } + new IConventionDbFunctionParameterBuilder Builder { get; } /// /// Sets the store type of the parameter in the database. diff --git a/src/EFCore.Relational/Metadata/IConventionSequence.cs b/src/EFCore.Relational/Metadata/IConventionSequence.cs index 4896f4fc1a7..8a2ed780aca 100644 --- a/src/EFCore.Relational/Metadata/IConventionSequence.cs +++ b/src/EFCore.Relational/Metadata/IConventionSequence.cs @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata /// Represents a database sequence in the in a form that /// can be mutated while building the model. /// - public interface IConventionSequence : ISequence + public interface IConventionSequence : ISequence, IConventionAnnotatable { /// /// Gets the in which this sequence is defined. @@ -21,7 +21,7 @@ public interface IConventionSequence : ISequence /// /// Gets the builder that can be used to configure this sequence. /// - IConventionSequenceBuilder Builder { get; } + new IConventionSequenceBuilder Builder { get; } /// /// Gets the configuration source for this . diff --git a/src/EFCore.Relational/Metadata/IDatabaseModel.cs b/src/EFCore.Relational/Metadata/IDatabaseModel.cs new file mode 100644 index 00000000000..a3c33ae2ea3 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IDatabaseModel.cs @@ -0,0 +1,88 @@ +// 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.Reflection; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + /// + /// Represents a table-like object in the database. + /// + public interface IRelationalModel : IAnnotatable + { + /// + /// Gets the full model. + /// + IModel Model { get; } + + /// + /// Returns all the tables mapped in the model. + /// + IEnumerable Tables { get; } + + /// + /// Returns all the views mapped in the model. + /// + /// All the views mapped in the model. + IEnumerable Views { get; } + + /// + /// Returns all sequences contained in the model. + /// + IEnumerable Sequences => Model.GetSequences(); + + /// + /// Returns all user-defined functions contained in the model. + /// + IEnumerable DbFunctions => Model.GetDbFunctions(); + + /// + /// Gets the table with a given name. Returns null if no table with the given name is defined. + /// + /// The name of the table. + /// The schema of the table. + /// The table with a given name or null if no table with the given name is defined. + ITable FindTable([NotNull] string name, [CanBeNull] string schema); + + /// + /// Gets the view with a given name. Returns null if no view with the given name is defined. + /// + /// The name of the view. + /// The schema of the view. + /// The view with a given name or null if no view with the given name is defined. + IView FindView([NotNull] string name, [CanBeNull] string schema); + + /// + /// Finds an with the given name. + /// + /// The sequence name. + /// The schema that contains the sequence. + /// + /// The or null if no sequence with the given name in + /// the given schema was found. + /// + ISequence FindSequence([NotNull] string name, [CanBeNull] string schema) + => Model.FindSequence(name, schema); + + /// + /// Finds a that is mapped to the method represented by the given . + /// + /// The for the method that is mapped to the function. + /// The or null if the method is not mapped. + IDbFunction FindDbFunction([NotNull] MethodInfo method) + => DbFunction.FindDbFunction(Model, Check.NotNull(method, nameof(method))); + + /// + /// Finds a that is mapped to the method represented by the given name. + /// + /// The model name of the function. + /// The or null if the method is not mapped. + IDbFunction FindDbFunction([NotNull] string name) + => DbFunction.FindDbFunction(Model, Check.NotNull(name, nameof(name))); + } +} diff --git a/src/EFCore.Relational/Metadata/IDbFunction.cs b/src/EFCore.Relational/Metadata/IDbFunction.cs index 046586af06d..86696ac7acb 100644 --- a/src/EFCore.Relational/Metadata/IDbFunction.cs +++ b/src/EFCore.Relational/Metadata/IDbFunction.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.Storage; @@ -12,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata /// /// Represents a relational database function in an . /// - public interface IDbFunction + public interface IDbFunction : IAnnotatable { /// /// Gets the name of the function in the database. diff --git a/src/EFCore.Relational/Metadata/IDbFunctionParameter.cs b/src/EFCore.Relational/Metadata/IDbFunctionParameter.cs index 49f92a3228a..df1ec93148e 100644 --- a/src/EFCore.Relational/Metadata/IDbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/IDbFunctionParameter.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; namespace Microsoft.EntityFrameworkCore.Metadata @@ -9,7 +10,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata /// /// Represents a database function parameter in an . /// - public interface IDbFunctionParameter + public interface IDbFunctionParameter : IAnnotatable { /// /// Gets the to which this parameter belongs. diff --git a/src/EFCore.Relational/Metadata/IMutableCheckConstraint.cs b/src/EFCore.Relational/Metadata/IMutableCheckConstraint.cs index f34ba9275a1..ecf8a55fa1f 100644 --- a/src/EFCore.Relational/Metadata/IMutableCheckConstraint.cs +++ b/src/EFCore.Relational/Metadata/IMutableCheckConstraint.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata /// /// Represents a check constraint in the . /// - public interface IMutableCheckConstraint : ICheckConstraint + public interface IMutableCheckConstraint : ICheckConstraint, IMutableAnnotatable { /// /// Gets the in which this check constraint is defined. diff --git a/src/EFCore.Relational/Metadata/IMutableDbFunction.cs b/src/EFCore.Relational/Metadata/IMutableDbFunction.cs index 0e1e52f4584..a1f71ca2f56 100644 --- a/src/EFCore.Relational/Metadata/IMutableDbFunction.cs +++ b/src/EFCore.Relational/Metadata/IMutableDbFunction.cs @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata /// Represents a relational database function in an in /// the a form that can be mutated while the model is being built. /// - public interface IMutableDbFunction : IDbFunction + public interface IMutableDbFunction : IMutableAnnotatable, IDbFunction { /// /// Gets or sets the name of the function in the database. diff --git a/src/EFCore.Relational/Metadata/IMutableDbFunctionParameter.cs b/src/EFCore.Relational/Metadata/IMutableDbFunctionParameter.cs index e0bb895b5b3..334aeaffcc1 100644 --- a/src/EFCore.Relational/Metadata/IMutableDbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/IMutableDbFunctionParameter.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata /// /// Represents a mutable database function parameter in an . /// - public interface IMutableDbFunctionParameter : IDbFunctionParameter + public interface IMutableDbFunctionParameter : IMutableAnnotatable, IDbFunctionParameter { /// /// Gets the to which this parameter belongs. diff --git a/src/EFCore.Relational/Metadata/IMutableSequence.cs b/src/EFCore.Relational/Metadata/IMutableSequence.cs index 0b370650ae4..11842db7c7c 100644 --- a/src/EFCore.Relational/Metadata/IMutableSequence.cs +++ b/src/EFCore.Relational/Metadata/IMutableSequence.cs @@ -10,7 +10,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata /// Represents a database sequence in the in a form that /// can be mutated while building the model. /// - public interface IMutableSequence : ISequence + public interface IMutableSequence : ISequence, IMutableAnnotatable { /// /// Gets the in which this sequence is defined. diff --git a/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs b/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs new file mode 100644 index 00000000000..7c876089c23 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IRelationalAnnotationProvider.cs @@ -0,0 +1,94 @@ +// 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; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + /// + /// + /// A service typically implemented by database providers that gives access to annotations + /// used by relational EF Core components on various elements of the . + /// + /// + /// The service lifetime is . This means a single instance + /// is used by many instances. The implementation must be thread-safe. + /// This service cannot depend on services registered as . + /// + /// + public interface IRelationalAnnotationProvider + { + /// + /// Gets provider-specific annotations for the given . + /// + /// The database model. + /// The annotations. + IEnumerable For([NotNull] IRelationalModel model); + + /// + /// Gets provider-specific annotations for the given . + /// + /// The table. + /// The annotations. + IEnumerable For([NotNull] ITable table); + + /// + /// Gets provider-specific annotations for the given . + /// + /// The column. + /// The annotations. + IEnumerable For([NotNull] IColumn column); + + /// + /// Gets provider-specific annotations for the given . + /// + /// The view. + /// The annotations. + IEnumerable For([NotNull] IView view); + + /// + /// Gets provider-specific annotations for the given . + /// + /// The column. + /// The annotations. + IEnumerable For([NotNull] IViewColumn column); + + /// + /// Gets provider-specific annotations for the given . + /// + /// The unique constraint. + /// The annotations. + IEnumerable For([NotNull] IUniqueConstraint constraint); + + /// + /// Gets provider-specific annotations for the given . + /// + /// The index. + /// The annotations. + IEnumerable For([NotNull] ITableIndex index); + + /// + /// Gets provider-specific annotations for the given . + /// + /// The foreign key. + /// The annotations. + IEnumerable For([NotNull] IForeignKeyConstraint foreignKey); + + /// + /// Gets provider-specific annotations for the given . + /// + /// The sequence. + /// The annotations. + IEnumerable For([NotNull] ISequence sequence); + + /// + /// Gets provider-specific annotations for the given . + /// + /// The check constraint. + /// The annotations. + IEnumerable For([NotNull] ICheckConstraint checkConstraint); + } +} diff --git a/src/EFCore.Relational/Metadata/ISequence.cs b/src/EFCore.Relational/Metadata/ISequence.cs index ff656e098aa..812939d21d7 100644 --- a/src/EFCore.Relational/Metadata/ISequence.cs +++ b/src/EFCore.Relational/Metadata/ISequence.cs @@ -2,13 +2,14 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.EntityFrameworkCore.Infrastructure; namespace Microsoft.EntityFrameworkCore.Metadata { /// /// Represents a database sequence in the . /// - public interface ISequence + public interface ISequence : IAnnotatable { /// /// Gets the name of the sequence in the database. diff --git a/src/EFCore.Relational/Metadata/ITable.cs b/src/EFCore.Relational/Metadata/ITable.cs index 8b25b0f1dcf..8cbe93ec597 100644 --- a/src/EFCore.Relational/Metadata/ITable.cs +++ b/src/EFCore.Relational/Metadata/ITable.cs @@ -39,6 +39,11 @@ public interface ITable : ITableBase /// IEnumerable UniqueConstraints { get; } + /// + /// Gets the primary key for this table. + /// + IUniqueConstraint PrimaryKey => UniqueConstraints.FirstOrDefault(c => c.IsPrimaryKey); + /// /// Gets the indexes for this table. /// diff --git a/src/EFCore.Relational/Metadata/ITableBase.cs b/src/EFCore.Relational/Metadata/ITableBase.cs index 95dae5aec4a..ecc7c6ad385 100644 --- a/src/EFCore.Relational/Metadata/ITableBase.cs +++ b/src/EFCore.Relational/Metadata/ITableBase.cs @@ -22,6 +22,11 @@ public interface ITableBase : IAnnotatable /// string Schema { get; } + /// + /// Gets the database model. + /// + IRelationalModel Model { get; } + /// /// Gets the value indicating whether multiple entity types are sharing the rows in the table. /// diff --git a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs index 8550a2d655c..509d4be2e2d 100644 --- a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs +++ b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs @@ -16,7 +16,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 CheckConstraint : IMutableCheckConstraint, IConventionCheckConstraint + public class CheckConstraint : ConventionAnnotatable, IMutableCheckConstraint, IConventionCheckConstraint { private ConfigurationSource _configurationSource; diff --git a/src/EFCore.Relational/Metadata/Internal/Column.cs b/src/EFCore.Relational/Metadata/Internal/Column.cs index e1ba989dadf..854969d3bb5 100644 --- a/src/EFCore.Relational/Metadata/Internal/Column.cs +++ b/src/EFCore.Relational/Metadata/Internal/Column.cs @@ -48,7 +48,7 @@ public Column([NotNull] string name, [CanBeNull] string type, [NotNull] Table ta /// 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(ColumnMappingComparer.Instance); + 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 diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnMappingComparer.cs b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs similarity index 72% rename from src/EFCore.Relational/Metadata/Internal/ColumnMappingComparer.cs rename to src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs index 7ba2d518f08..91386ccceed 100644 --- a/src/EFCore.Relational/Metadata/Internal/ColumnMappingComparer.cs +++ b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs @@ -12,9 +12,9 @@ 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 sealed class ColumnMappingComparer : IEqualityComparer, IComparer + public sealed class ColumnMappingBaseComparer : IEqualityComparer, IComparer { - private ColumnMappingComparer() + private ColumnMappingBaseComparer() { } @@ -24,7 +24,7 @@ private ColumnMappingComparer() /// 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 readonly ColumnMappingComparer Instance = new ColumnMappingComparer(); + public static readonly ColumnMappingBaseComparer Instance = new ColumnMappingBaseComparer(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -32,7 +32,7 @@ private ColumnMappingComparer() /// 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 int Compare(IColumnMapping x, IColumnMapping y) + public int Compare(IColumnMappingBase x, IColumnMappingBase y) { var result = StringComparer.Ordinal.Compare(x.Property.IsColumnNullable(), y.Property.IsColumnNullable()); if (result != 0) @@ -46,7 +46,25 @@ public int Compare(IColumnMapping x, IColumnMapping y) return result; } - return StringComparer.Ordinal.Compare(x.Column.Name, y.Column.Name); + result = StringComparer.Ordinal.Compare(x.Column.Name, y.Column.Name); + if (result != 0) + { + return result; + } + + result = EntityTypePathComparer.Instance.Compare(x.Property.DeclaringEntityType, y.Property.DeclaringEntityType); + if (result != 0) + { + return result; + } + + result = StringComparer.Ordinal.Compare(x.Column.Table.Name, y.Column.Table.Name); + if (result != 0) + { + return result; + } + + return StringComparer.Ordinal.Compare(x.Column.Table.Schema, y.Column.Table.Schema); } /// @@ -55,7 +73,7 @@ public int Compare(IColumnMapping x, IColumnMapping y) /// 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 bool Equals(IColumnMapping x, IColumnMapping y) + public bool Equals(IColumnMappingBase x, IColumnMappingBase y) => x.Property == y.Property && x.Column == y.Column; @@ -65,11 +83,15 @@ public bool Equals(IColumnMapping x, IColumnMapping y) /// 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 int GetHashCode(IColumnMapping obj) + public int GetHashCode(IColumnMappingBase obj) { var hashCode = new HashCode(); hashCode.Add(obj.Property.Name); hashCode.Add(obj.Column.Name); + hashCode.Add(obj.Property.DeclaringEntityType, EntityTypePathComparer.Instance); + hashCode.Add(obj.Column.Table.Name); + hashCode.Add(obj.Column.Table.Schema); + return hashCode.ToHashCode(); } } diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnMappingExtensions.cs b/src/EFCore.Relational/Metadata/Internal/ColumnMappingExtensions.cs index 7a5a058e9d7..fb7cc070650 100644 --- a/src/EFCore.Relational/Metadata/Internal/ColumnMappingExtensions.cs +++ b/src/EFCore.Relational/Metadata/Internal/ColumnMappingExtensions.cs @@ -36,9 +36,16 @@ public static class ColumnMappingExtensions builder.Append($"ColumnMapping: "); } - builder.Append(columnMapping.Property.Name).Append(" - "); - - builder.Append(columnMapping.Column.Name); + builder + .Append(columnMapping.Property.DeclaringEntityType.DisplayName()) + .Append(".") + .Append(columnMapping.Property.Name) + .Append(" - "); + + builder + .Append(columnMapping.Column.Table.Name) + .Append(".") + .Append(columnMapping.Column.Name); if (!singleLine && (options & MetadataDebugStringOptions.IncludeAnnotations) != 0) diff --git a/src/EFCore.Relational/Metadata/Internal/DatabaseModelExtensions.cs b/src/EFCore.Relational/Metadata/Internal/DatabaseModelExtensions.cs new file mode 100644 index 00000000000..8a17a1d6008 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/DatabaseModelExtensions.cs @@ -0,0 +1,51 @@ +// 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.Internal; + +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 static class DatabaseModelExtensions + { + /// + /// 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 ToDebugString( + [NotNull] this IRelationalModel model, + MetadataDebugStringOptions options, + [NotNull] string indent = "") + { + var builder = new StringBuilder(); + + builder.Append(indent).Append("DatabaseModel: "); + + foreach (var table in model.Tables) + { + builder.AppendLine().Append(table.ToDebugString(options, indent + " ")); + } + + foreach (var view in model.Views) + { + builder.AppendLine().Append(view.ToDebugString(options, indent + " ")); + } + + if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(model.AnnotationsToDebugString(indent)); + } + + return builder.ToString(); + } + } +} diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs index 56f3dd5edfc..1fdeda8c639 100644 --- a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs +++ b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs @@ -21,7 +21,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 DbFunction : IMutableDbFunction, IConventionDbFunction + public class DbFunction : ConventionAnnotatable, IMutableDbFunction, IConventionDbFunction { private readonly IMutableModel _model; private readonly List _parameters; diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs index 429c348a9e0..8496880d081 100644 --- a/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.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 DbFunctionParameter : IMutableDbFunctionParameter, IConventionDbFunctionParameter + public class DbFunctionParameter : ConventionAnnotatable, IMutableDbFunctionParameter, IConventionDbFunctionParameter { private readonly IMutableDbFunction _function; private readonly string _name; diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 1572202f2e8..ffc998fee94 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -2,10 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Migrations; namespace Microsoft.EntityFrameworkCore.Metadata.Internal @@ -16,7 +18,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 RelationalModel + public class RelationalModel : Annotatable, IRelationalModel { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -24,10 +26,54 @@ public class RelationalModel /// 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 IModel AddRelationalModel([NotNull] IConventionModel model) + public RelationalModel([NotNull] IModel model) { - var tables = new SortedDictionary<(string, string), Table>(); - var views = new SortedDictionary<(string, string), View>(); + Model = 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<(string, string), Table> Tables { get; } = new SortedDictionary<(string, string), 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 SortedDictionary<(string, string), View> Views { get; } = new SortedDictionary<(string, string), View>(); + + /// + public virtual ITable FindTable(string name, string schema) + => Tables.TryGetValue((name, schema), out var table) + ? table + : null; + + /// + public virtual IView FindView(string name, string schema) + => Views.TryGetValue((name, schema), out var view) + ? view + : null; + + /// + /// 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 IModel Add( + [NotNull] IConventionModel model, [CanBeNull] IRelationalAnnotationProvider relationalAnnotationProvider) + { + var databaseModel = new RelationalModel(model); + model.SetAnnotation(RelationalAnnotationNames.RelationalModel, databaseModel); + foreach (var entityType in model.GetEntityTypes()) { var tableName = entityType.GetTableName(); @@ -35,10 +81,10 @@ public static IModel AddRelationalModel([NotNull] IConventionModel model) if (tableName != null) { var schema = entityType.GetSchema(); - if (!tables.TryGetValue((tableName, schema), out var table)) + if (!databaseModel.Tables.TryGetValue((tableName, schema), out var table)) { - table = new Table(tableName, schema); - tables.Add((tableName, schema), table); + table = new Table(tableName, schema, databaseModel); + databaseModel.Tables.Add((tableName, schema), table); } table.IsMigratable = table.IsMigratable @@ -68,7 +114,7 @@ public static IModel AddRelationalModel([NotNull] IConventionModel model) var columnMappings = property[RelationalAnnotationNames.TableColumnMappings] as SortedSet; if (columnMappings == null) { - columnMappings = new SortedSet(ColumnMappingComparer.Instance); + columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); property.SetAnnotation(RelationalAnnotationNames.TableColumnMappings, columnMappings); } @@ -78,7 +124,7 @@ public static IModel AddRelationalModel([NotNull] IConventionModel model) var tableMappings = entityType[RelationalAnnotationNames.TableMappings] as SortedSet; if (tableMappings == null) { - tableMappings = new SortedSet(TableMappingComparer.Instance); + tableMappings = new SortedSet(TableMappingBaseComparer.Instance); entityType.SetAnnotation(RelationalAnnotationNames.TableMappings, tableMappings); } @@ -89,10 +135,10 @@ public static IModel AddRelationalModel([NotNull] IConventionModel model) if (viewName != null) { var schema = entityType.GetViewSchema(); - if (!views.TryGetValue((viewName, schema), out var view)) + if (!databaseModel.Views.TryGetValue((viewName, schema), out var view)) { - view = new View(viewName, schema); - views.Add((viewName, schema), view); + view = new View(viewName, schema, databaseModel); + databaseModel.Views.Add((viewName, schema), view); } var viewMapping = new ViewMapping(entityType, view, includesDerivedTypes: true); @@ -119,7 +165,7 @@ public static IModel AddRelationalModel([NotNull] IConventionModel model) var columnMappings = property[RelationalAnnotationNames.ViewColumnMappings] as SortedSet; if (columnMappings == null) { - columnMappings = new SortedSet(ViewColumnMappingComparer.Instance); + columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); property.SetAnnotation(RelationalAnnotationNames.ViewColumnMappings, columnMappings); } @@ -129,7 +175,7 @@ public static IModel AddRelationalModel([NotNull] IConventionModel model) var tableMappings = entityType[RelationalAnnotationNames.ViewMappings] as SortedSet; if (tableMappings == null) { - tableMappings = new SortedSet(ViewMappingComparer.Instance); + tableMappings = new SortedSet(TableMappingBaseComparer.Instance); entityType.SetAnnotation(RelationalAnnotationNames.ViewMappings, tableMappings); } @@ -138,31 +184,57 @@ public static IModel AddRelationalModel([NotNull] IConventionModel model) } } - if (tables.Any()) + foreach (var table in databaseModel.Tables.Values) { - foreach (var table in tables.Values) + if (relationalAnnotationProvider != null) { - PopulateConstraints(table); - PopulateInternalForeignKeys(table); + foreach (var column in table.Columns.Values) + { + column.AddAnnotations(relationalAnnotationProvider.For(column)); + } } - model.SetAnnotation(RelationalAnnotationNames.Tables, tables); + PopulateInternalForeignKeys(table); + PopulateConstraints(table, relationalAnnotationProvider); + + if (relationalAnnotationProvider != null) + { + table.AddAnnotations(relationalAnnotationProvider.For(table)); + } } - if (views.Any()) + foreach (var view in databaseModel.Views.Values) { - foreach (var view in views.Values) + if (relationalAnnotationProvider != null) { - PopulateInternalForeignKeys(view); + foreach (var viewColumn in view.Columns.Values) + { + viewColumn.AddAnnotations(relationalAnnotationProvider.For(viewColumn)); + } } - model.SetAnnotation(RelationalAnnotationNames.Views, views); + PopulateInternalForeignKeys(view); + + if (relationalAnnotationProvider != null) + { + view.AddAnnotations(relationalAnnotationProvider.For(view)); + } + } + + if (relationalAnnotationProvider != null) + { + foreach (Sequence sequence in ((IRelationalModel)databaseModel).Sequences) + { + sequence.AddAnnotations(relationalAnnotationProvider.For(sequence)); + } + + databaseModel.AddAnnotations(relationalAnnotationProvider.For(databaseModel)); } return model; } - private static void PopulateConstraints(Table table) + private static void PopulateConstraints(Table table, IRelationalAnnotationProvider relationalAnnotationProvider) { foreach (var entityTypeMapping in ((ITable)table).EntityTypeMappings) { @@ -268,7 +340,6 @@ private static void PopulateConstraints(Table table) } foreignKeyConstraints.Add(constraint); - table.ForeignKeyConstraints.Add(name, constraint); } @@ -362,6 +433,29 @@ private static void PopulateConstraints(Table table) tableIndex.MappedIndexes.Add(index); } } + + if (relationalAnnotationProvider != null) + { + foreach (var constraint in table.UniqueConstraints.Values) + { + constraint.AddAnnotations(relationalAnnotationProvider.For(constraint)); + } + + foreach (var index in table.Indexes.Values) + { + index.AddAnnotations(relationalAnnotationProvider.For(index)); + } + + foreach (var constraint in table.ForeignKeyConstraints.Values) + { + constraint.AddAnnotations(relationalAnnotationProvider.For(constraint)); + } + + foreach (CheckConstraint checkConstraint in ((ITable)table).CheckConstraints) + { + checkConstraint.AddAnnotations(relationalAnnotationProvider.For(checkConstraint)); + } + } } private static void PopulateInternalForeignKeys(TableBase table) @@ -442,14 +536,37 @@ private static ReferentialAction ToReferentialAction(DeleteBehavior deleteBehavi return ReferentialAction.Cascade; case DeleteBehavior.NoAction: case DeleteBehavior.ClientNoAction: - case DeleteBehavior.ClientSetNull: - case DeleteBehavior.ClientCascade: return ReferentialAction.NoAction; case DeleteBehavior.Restrict: + case DeleteBehavior.ClientSetNull: + case DeleteBehavior.ClientCascade: return ReferentialAction.Restrict; default: throw new NotImplementedException(deleteBehavior.ToString()); } } + + /// + /// 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 DebugView DebugView + => new DebugView( + () => this.ToDebugString(MetadataDebugStringOptions.ShortDefault), + () => this.ToDebugString(MetadataDebugStringOptions.LongDefault)); + + IEnumerable IRelationalModel.Tables + { + [DebuggerStepThrough] + get => Tables.Values; + } + + IEnumerable IRelationalModel.Views + { + [DebuggerStepThrough] + get => Views.Values; + } } } diff --git a/src/EFCore.Relational/Metadata/Internal/Sequence.cs b/src/EFCore.Relational/Metadata/Internal/Sequence.cs index 0080d904482..dfa97233265 100644 --- a/src/EFCore.Relational/Metadata/Internal/Sequence.cs +++ b/src/EFCore.Relational/Metadata/Internal/Sequence.cs @@ -19,7 +19,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 Sequence : IMutableSequence, IConventionSequence + public class Sequence : ConventionAnnotatable, IMutableSequence, IConventionSequence { private readonly IModel _model; diff --git a/src/EFCore.Relational/Metadata/Internal/Table.cs b/src/EFCore.Relational/Metadata/Internal/Table.cs index ea2a24846dd..fafaab17c6c 100644 --- a/src/EFCore.Relational/Metadata/Internal/Table.cs +++ b/src/EFCore.Relational/Metadata/Internal/Table.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using JetBrains.Annotations; namespace Microsoft.EntityFrameworkCore.Metadata.Internal @@ -22,8 +23,8 @@ public class Table : TableBase, ITable /// 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 Table([NotNull] string name, [CanBeNull] string schema) - : base(name, schema) + public Table([NotNull] string name, [CanBeNull] string schema, [NotNull] RelationalModel model) + : base(name, schema, model) { } @@ -33,7 +34,7 @@ public Table([NotNull] string name, [CanBeNull] string schema) /// 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(TableMappingComparer.Instance); + 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 @@ -144,13 +145,13 @@ IEnumerable ITableBase.GetInternalForeignKeys(IEntityType entityTyp => InternalForeignKeys != null && InternalForeignKeys.TryGetValue(entityType, out var foreignKeys) ? foreignKeys - : null; + : Enumerable.Empty(); /// IEnumerable ITableBase.GetReferencingInternalForeignKeys(IEntityType entityType) => ReferencingInternalForeignKeys != null && ReferencingInternalForeignKeys.TryGetValue(entityType, out var foreignKeys) ? foreignKeys - : null; + : Enumerable.Empty(); } } diff --git a/src/EFCore.Relational/Metadata/Internal/TableBase.cs b/src/EFCore.Relational/Metadata/Internal/TableBase.cs index f9839064dcb..c09dadb5957 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableBase.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableBase.cs @@ -22,10 +22,11 @@ public abstract class TableBase : Annotatable, ITableBase /// 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 TableBase([NotNull] string name, [CanBeNull] string schema) + public TableBase([NotNull] string name, [CanBeNull] string schema, [NotNull] RelationalModel model) { Schema = schema; Name = name; + Model = model; } /// @@ -34,6 +35,14 @@ public TableBase([NotNull] string name, [CanBeNull] string schema) /// public virtual string Name { 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 RelationalModel Model { get; } + /// public virtual bool IsSplit { get; set; } @@ -59,6 +68,9 @@ public TableBase([NotNull] string name, [CanBeNull] string schema) /// IEnumerable ITableBase.Columns => throw new NotImplementedException(); + /// + IRelationalModel ITableBase.Model => Model; + /// IColumnBase ITableBase.FindColumn(string name) => throw new NotImplementedException(); diff --git a/src/EFCore.Relational/Metadata/Internal/TableMapping.cs b/src/EFCore.Relational/Metadata/Internal/TableMapping.cs index 01ac8a154c4..20bca9b0ad8 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableMapping.cs @@ -44,7 +44,7 @@ public class TableMapping : Annotatable, ITableMapping /// 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(ColumnMappingComparer.Instance); + public virtual SortedSet ColumnMappings { get; } = new SortedSet(ColumnMappingBaseComparer.Instance); /// public virtual bool IncludesDerivedTypes { get; } diff --git a/src/EFCore.Relational/Metadata/Internal/TableMappingComparer.cs b/src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs similarity index 84% rename from src/EFCore.Relational/Metadata/Internal/TableMappingComparer.cs rename to src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs index 8a0b3633352..749c3230a90 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableMappingComparer.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs @@ -14,9 +14,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal /// doing so can result in application failures when updating to a new Entity Framework Core release. /// // Sealed for perf - public sealed class TableMappingComparer : IEqualityComparer, IComparer + public sealed class TableMappingBaseComparer : IEqualityComparer, IComparer { - private TableMappingComparer() + private TableMappingBaseComparer() { } @@ -26,7 +26,7 @@ private TableMappingComparer() /// 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 readonly TableMappingComparer Instance = new TableMappingComparer(); + public static readonly TableMappingBaseComparer Instance = new TableMappingBaseComparer(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -34,7 +34,7 @@ private TableMappingComparer() /// 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 int Compare(ITableMapping x, ITableMapping y) + public int Compare(ITableMappingBase x, ITableMappingBase y) { var result = EntityTypePathComparer.Instance.Compare(x.EntityType, y.EntityType); if (result != 0) @@ -66,7 +66,11 @@ public int Compare(ITableMapping x, ITableMapping y) return result; } - return x.ColumnMappings.Zip(y.ColumnMappings, (xc, yc) => ColumnMappingComparer.Instance.Compare(xc, yc)) + return x.ColumnMappings.Zip(y.ColumnMappings, (xc, yc) => + { + var columnResult = StringComparer.Ordinal.Compare(xc.Property.Name, yc.Property.Name); + return columnResult != 0 ? columnResult : StringComparer.Ordinal.Compare(xc.Column.Name, yc.Column.Name); + }) .FirstOrDefault(r => r != 0); } @@ -76,7 +80,7 @@ public int Compare(ITableMapping x, ITableMapping y) /// 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 bool Equals(ITableMapping x, ITableMapping y) + public bool Equals(ITableMappingBase x, ITableMappingBase y) => x.EntityType == y.EntityType && x.Table == y.Table && x.IncludesDerivedTypes == y.IncludesDerivedTypes @@ -88,7 +92,7 @@ public bool Equals(ITableMapping x, ITableMapping y) /// 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 int GetHashCode(ITableMapping obj) + public int GetHashCode(ITableMappingBase obj) { var hashCode = new HashCode(); hashCode.Add(obj.EntityType, EntityTypePathComparer.Instance); @@ -96,7 +100,8 @@ public int GetHashCode(ITableMapping obj) hashCode.Add(obj.Table.Schema); foreach (var columnMapping in obj.ColumnMappings) { - hashCode.Add(columnMapping, ColumnMappingComparer.Instance); + hashCode.Add(columnMapping.Property.Name); + hashCode.Add(columnMapping.Column.Name); } hashCode.Add(obj.IncludesDerivedTypes); return hashCode.ToHashCode(); diff --git a/src/EFCore.Relational/Metadata/Internal/View.cs b/src/EFCore.Relational/Metadata/Internal/View.cs index efc854db5e8..110b8d211dd 100644 --- a/src/EFCore.Relational/Metadata/Internal/View.cs +++ b/src/EFCore.Relational/Metadata/Internal/View.cs @@ -23,8 +23,8 @@ public class View : TableBase, IView /// 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 View([NotNull] string name, [CanBeNull] string schema) - : base(name, schema) + public View([NotNull] string name, [CanBeNull] string schema, [NotNull] RelationalModel model) + : base(name, schema, model) { } @@ -34,7 +34,7 @@ public View([NotNull] string name, [CanBeNull] string schema) /// 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(ViewMappingComparer.Instance); + 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 @@ -42,8 +42,8 @@ public View([NotNull] string name, [CanBeNull] string schema) /// 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 SortedDictionary Columns { get; } + = new SortedDictionary(StringComparer.Ordinal); /// public virtual string ViewDefinition @@ -100,13 +100,13 @@ IEnumerable ITableBase.GetInternalForeignKeys(IEntityType entityTyp => InternalForeignKeys != null && InternalForeignKeys.TryGetValue(entityType, out var foreignKeys) ? foreignKeys - : null; + : Enumerable.Empty(); /// IEnumerable ITableBase.GetReferencingInternalForeignKeys(IEntityType entityType) => ReferencingInternalForeignKeys != null && ReferencingInternalForeignKeys.TryGetValue(entityType, out var foreignKeys) ? foreignKeys - : null; + : Enumerable.Empty(); } } diff --git a/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs b/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs index ccc542be2b5..456a27055c6 100644 --- a/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs @@ -48,7 +48,7 @@ public ViewColumn([NotNull] string name, [NotNull] string type, [NotNull] View v /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual SortedSet PropertyMappings { get; } - = new SortedSet(ViewColumnMappingComparer.Instance); + = new SortedSet(ColumnMappingBaseComparer.Instance); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/ViewColumnMappingComparer.cs b/src/EFCore.Relational/Metadata/Internal/ViewColumnMappingComparer.cs deleted file mode 100644 index 634b0262ac5..00000000000 --- a/src/EFCore.Relational/Metadata/Internal/ViewColumnMappingComparer.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; - -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. - /// - // Sealed for perf - public sealed class ViewColumnMappingComparer : IEqualityComparer, IComparer - { - private ViewColumnMappingComparer() - { - } - - /// - /// 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 readonly ViewColumnMappingComparer Instance = new ViewColumnMappingComparer(); - - /// - /// 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 int Compare(IViewColumnMapping x, IViewColumnMapping y) - { - var result = StringComparer.Ordinal.Compare(x.Property.IsViewColumnNullable(), y.Property.IsViewColumnNullable()); - if (result != 0) - { - return result; - } - - result = StringComparer.Ordinal.Compare(x.Property.Name, y.Property.Name); - if (result != 0) - { - return result; - } - - return StringComparer.Ordinal.Compare(x.Column.Name, y.Column.Name); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public bool Equals(IViewColumnMapping x, IViewColumnMapping y) - => x.Property == y.Property - && x.Column == y.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 int GetHashCode(IViewColumnMapping obj) - { - var hashCode = new HashCode(); - hashCode.Add(obj.Property.Name); - hashCode.Add(obj.Column.Name); - return hashCode.ToHashCode(); - } - } -} diff --git a/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs b/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs index c62afee2b35..a4b5f103f0a 100644 --- a/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs @@ -45,7 +45,7 @@ public class ViewMapping : Annotatable, IViewMapping /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual SortedSet ColumnMappings { get; } - = new SortedSet(ViewColumnMappingComparer.Instance); + = new SortedSet(ColumnMappingBaseComparer.Instance); /// public virtual bool IncludesDerivedTypes { get; } diff --git a/src/EFCore.Relational/Metadata/Internal/ViewMappingComparer.cs b/src/EFCore.Relational/Metadata/Internal/ViewMappingComparer.cs deleted file mode 100644 index 019a01c2e48..00000000000 --- a/src/EFCore.Relational/Metadata/Internal/ViewMappingComparer.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; - -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. - /// - // Sealed for perf - public sealed class ViewMappingComparer : IEqualityComparer, IComparer - { - private ViewMappingComparer() - { - } - - /// - /// 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 readonly ViewMappingComparer Instance = new ViewMappingComparer(); - - /// - /// 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 int Compare(IViewMapping x, IViewMapping y) - { - var result = EntityTypePathComparer.Instance.Compare(x.EntityType, y.EntityType); - if (result != 0) - { - return result; - } - - result = StringComparer.Ordinal.Compare(x.View.Name, y.View.Name); - if (result != 0) - { - return result; - } - - result = StringComparer.Ordinal.Compare(x.View.Schema, y.View.Schema); - if (result != 0) - { - return result; - } - - result = x.IncludesDerivedTypes.CompareTo(y.IncludesDerivedTypes); - if (result != 0) - { - return result; - } - - result = x.ColumnMappings.Count().CompareTo(y.ColumnMappings.Count()); - if (result != 0) - { - return result; - } - - return x.ColumnMappings.Zip(y.ColumnMappings, (xc, yc) => ViewColumnMappingComparer.Instance.Compare(xc, yc)) - .FirstOrDefault(r => r != 0); - } - - /// - /// 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 bool Equals(IViewMapping x, IViewMapping y) - => x.EntityType == y.EntityType - && x.View == y.View - && x.IncludesDerivedTypes == y.IncludesDerivedTypes - && x.ColumnMappings.SequenceEqual(y.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 - /// 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 int GetHashCode(IViewMapping obj) - { - var hashCode = new HashCode(); - hashCode.Add(obj.EntityType, EntityTypePathComparer.Instance); - hashCode.Add(obj.View.Name); - hashCode.Add(obj.View.Schema); - foreach (var columnMapping in obj.ColumnMappings) - { - hashCode.Add(columnMapping, ViewColumnMappingComparer.Instance); - } - hashCode.Add(obj.IncludesDerivedTypes); - return hashCode.ToHashCode(); - } - } -} diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs index 6cc6d1ab023..dcf22cf94ae 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs @@ -129,9 +129,9 @@ public static class RelationalAnnotationNames public const string ViewDefinition = Prefix + "ViewDefinition"; /// - /// The name for tables annotation. + /// The name for database model annotation. /// - public const string Tables = Prefix + "Tables"; + public const string RelationalModel = Prefix + "RelationalModel"; /// /// The name for table mappings annotations. @@ -143,11 +143,6 @@ public static class RelationalAnnotationNames /// public const string TableColumnMappings = Prefix + "TableColumnMappings"; - /// - /// The name for tables annotation. - /// - public const string Views = Prefix + "Views"; - /// /// The name for table mappings annotations. /// diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs new file mode 100644 index 00000000000..3a23e8fbf24 --- /dev/null +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationProvider.cs @@ -0,0 +1,65 @@ +// 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.Linq; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Utilities; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + /// + /// + /// A base class inherited by database providers that gives access to annotations + /// used by relational EF Core components on various elements of the . + /// + /// + /// The service lifetime is . This means a single instance + /// is used by many instances. The implementation must be thread-safe. + /// This service cannot depend on services registered as . + /// + /// + public class RelationalAnnotationProvider : IRelationalAnnotationProvider + { + /// + /// Initializes a new instance of this class. + /// + /// Parameter object containing dependencies for this service. + public RelationalAnnotationProvider([NotNull] RelationalAnnotationProviderDependencies dependencies) + { + Check.NotNull(dependencies, nameof(dependencies)); + } + + /// + public virtual IEnumerable For(IRelationalModel model) => Enumerable.Empty(); + + /// + public virtual IEnumerable For(ITable table) => Enumerable.Empty(); + + /// + public virtual IEnumerable For(IColumn column) => Enumerable.Empty(); + + /// + public virtual IEnumerable For(IView view) => Enumerable.Empty(); + + /// + public virtual IEnumerable For(IViewColumn column) => Enumerable.Empty(); + + /// + public virtual IEnumerable For(IForeignKeyConstraint foreignKey) => Enumerable.Empty(); + + /// + public virtual IEnumerable For(ITableIndex index) => Enumerable.Empty(); + + /// + public virtual IEnumerable For(IUniqueConstraint constraint) => Enumerable.Empty(); + + /// + public virtual IEnumerable For(ISequence sequence) => Enumerable.Empty(); + + /// + public virtual IEnumerable For(ICheckConstraint checkConstraint) => Enumerable.Empty(); + } +} diff --git a/src/EFCore.Relational/Migrations/MigrationsAnnotationProviderDependencies.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationProviderDependencies.cs similarity index 93% rename from src/EFCore.Relational/Migrations/MigrationsAnnotationProviderDependencies.cs rename to src/EFCore.Relational/Metadata/RelationalAnnotationProviderDependencies.cs index ff879f8bdfc..f3db255faea 100644 --- a/src/EFCore.Relational/Migrations/MigrationsAnnotationProviderDependencies.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationProviderDependencies.cs @@ -4,11 +4,11 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection; -namespace Microsoft.EntityFrameworkCore.Migrations +namespace Microsoft.EntityFrameworkCore.Metadata { /// /// - /// Service dependencies parameter class for + /// Service dependencies parameter class for /// /// /// This type is typically used by database providers (and other extensions). It is generally @@ -36,11 +36,11 @@ namespace Microsoft.EntityFrameworkCore.Migrations /// /// [EntityFrameworkInternal] - public sealed class MigrationsAnnotationProviderDependencies + public sealed class RelationalAnnotationProviderDependencies { /// /// - /// Creates the service dependencies parameter object for a . + /// Creates the service dependencies parameter object for a . /// /// /// Do not call this constructor directly from either provider or application code as it may change @@ -64,7 +64,7 @@ public sealed class MigrationsAnnotationProviderDependencies /// /// [EntityFrameworkInternal] - public MigrationsAnnotationProviderDependencies() + public RelationalAnnotationProviderDependencies() { } } diff --git a/src/EFCore.Relational/Migrations/HistoryRepository.cs b/src/EFCore.Relational/Migrations/HistoryRepository.cs index 397c620be1e..695cd3a7a29 100644 --- a/src/EFCore.Relational/Migrations/HistoryRepository.cs +++ b/src/EFCore.Relational/Migrations/HistoryRepository.cs @@ -180,7 +180,7 @@ public virtual string GetCreateScript() { var model = EnsureModel(); - var operations = Dependencies.ModelDiffer.GetDifferences(null, model); + var operations = Dependencies.ModelDiffer.GetDifferences(null, model.GetRelationalModel()); var commandList = Dependencies.MigrationsSqlGenerator.Generate(operations, model); return string.Concat(commandList.Select(c => c.CommandText)); diff --git a/src/EFCore.Relational/Migrations/IMigrationsAnnotationProvider.cs b/src/EFCore.Relational/Migrations/IMigrationsAnnotationProvider.cs deleted file mode 100644 index dc6607212e6..00000000000 --- a/src/EFCore.Relational/Migrations/IMigrationsAnnotationProvider.cs +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.EntityFrameworkCore.Migrations -{ - /// - /// - /// A service typically implemented by database providers that gives access to annotations - /// used by EF Core Migrations on various elements of the . - /// - /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . - /// - /// - public interface IMigrationsAnnotationProvider - { - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// The model. - /// The annotations. - IEnumerable For([NotNull] IModel model); - - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// The index. - /// The annotations. - IEnumerable For([NotNull] IIndex index); - - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// The property. - /// The annotations. - IEnumerable For([NotNull] IProperty property); - - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// The key. - /// The annotations. - IEnumerable For([NotNull] IKey key); - - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// The foreign key. - /// The annotations. - IEnumerable For([NotNull] IForeignKey foreignKey); - - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// The entity type. - /// The annotations. - IEnumerable For([NotNull] IEntityType entityType); - - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// The sequence. - /// The annotations. - IEnumerable For([NotNull] ISequence sequence); - - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// The check constraint. - /// The annotations. - IEnumerable For([NotNull] ICheckConstraint checkConstraint); - - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// The model. - /// The annotations. - IEnumerable ForRemove([NotNull] IModel model); - - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// The index. - /// The annotations. - IEnumerable ForRemove([NotNull] IIndex index); - - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// The property. - /// The annotations. - IEnumerable ForRemove([NotNull] IProperty property); - - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// The key. - /// The annotations. - IEnumerable ForRemove([NotNull] IKey key); - - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// The foreign key. - /// The annotations. - IEnumerable ForRemove([NotNull] IForeignKey foreignKey); - - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// The entity type. - /// The annotations. - IEnumerable ForRemove([NotNull] IEntityType entityType); - - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// The sequence. - /// The annotations. - IEnumerable ForRemove([NotNull] ISequence sequence); - - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// The check constraint. - /// The annotations. - IEnumerable ForRemove([NotNull] ICheckConstraint checkConstraint); - } -} diff --git a/src/EFCore.Relational/Migrations/IMigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/IMigrationsModelDiffer.cs index 682a606c7d1..c0d5a365c71 100644 --- a/src/EFCore.Relational/Migrations/IMigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/IMigrationsModelDiffer.cs @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations { /// /// - /// A service for finding differences between two s and transforming + /// A service for finding differences between two s and transforming /// those differences into s that can be used to /// update the database. /// @@ -25,14 +25,14 @@ namespace Microsoft.EntityFrameworkCore.Migrations public interface IMigrationsModelDiffer { /// - /// Checks whether or not there are differences between the two models. + /// Checks whether there are differences between the two models. /// /// The first model. /// The second model. /// - /// True + /// true if there are any differences and false otherwise. /// - bool HasDifferences([CanBeNull] IModel source, [CanBeNull] IModel target); + bool HasDifferences([CanBeNull] IRelationalModel source, [CanBeNull] IRelationalModel target); /// /// Finds the differences between two models. @@ -43,6 +43,6 @@ public interface IMigrationsModelDiffer /// A list of the operations that need to applied to the database to migrate it /// from mapping to the source model so that is now mapping to the target model. /// - IReadOnlyList GetDifferences([CanBeNull] IModel source, [CanBeNull] IModel target); + IReadOnlyList GetDifferences([CanBeNull] IRelationalModel source, [CanBeNull] IRelationalModel target); } } diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 0a6a04fcfff..a877ec2ffc7 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -74,18 +74,16 @@ public class MigrationsModelDiffer : IMigrationsModelDiffer /// public MigrationsModelDiffer( [NotNull] IRelationalTypeMappingSource typeMappingSource, - [NotNull] IMigrationsAnnotationProvider migrationsAnnotations, [NotNull] IChangeDetector changeDetector, [NotNull] IUpdateAdapterFactory updateAdapterFactory, [NotNull] CommandBatchPreparerDependencies commandBatchPreparerDependencies) { Check.NotNull(typeMappingSource, nameof(typeMappingSource)); - Check.NotNull(migrationsAnnotations, nameof(migrationsAnnotations)); + Check.NotNull(changeDetector, nameof(changeDetector)); Check.NotNull(updateAdapterFactory, nameof(updateAdapterFactory)); Check.NotNull(commandBatchPreparerDependencies, nameof(commandBatchPreparerDependencies)); TypeMappingSource = typeMappingSource; - MigrationsAnnotations = migrationsAnnotations; ChangeDetector = changeDetector; UpdateAdapterFactory = updateAdapterFactory; CommandBatchPreparerDependencies = commandBatchPreparerDependencies; @@ -99,14 +97,6 @@ public class MigrationsModelDiffer : IMigrationsModelDiffer /// protected virtual IRelationalTypeMappingSource TypeMappingSource { 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. - /// - protected virtual IMigrationsAnnotationProvider MigrationsAnnotations { 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 @@ -137,8 +127,8 @@ public class MigrationsModelDiffer : IMigrationsModelDiffer /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool HasDifferences(IModel source, IModel target) - => Diff(source, target, new DiffContext(source, target)).Any(); + public virtual bool HasDifferences(IRelationalModel source, IRelationalModel target) + => Diff(source, target, new DiffContext()).Any(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -146,9 +136,9 @@ public virtual bool HasDifferences(IModel source, IModel target) /// 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 IReadOnlyList GetDifferences(IModel source, IModel target) + public virtual IReadOnlyList GetDifferences(IRelationalModel source, IRelationalModel target) { - var diffContext = new DiffContext(source, target); + var diffContext = new DiffContext(); return Sort(Diff(source, target, diffContext), diffContext); } @@ -309,15 +299,14 @@ public virtual IReadOnlyList GetDifferences(IModel source, I return true; }).ToList(); - var dropTableGraph = new Multigraph(); + var dropTableGraph = new Multigraph(); dropTableGraph.AddVertices(dropTableOperations); foreach (var dropTableOperation in dropTableOperations) { var table = diffContext.FindTable(dropTableOperation); - foreach (var foreignKey in GetForeignKeys(table)) + foreach (var foreignKey in table.ForeignKeyConstraints) { - var principalRootEntityType = foreignKey.PrincipalEntityType; - var principalDropTableOperation = diffContext.FindDrop(principalRootEntityType); + var principalDropTableOperation = diffContext.FindDrop(foreignKey.PrincipalTable); if (principalDropTableOperation != null && principalDropTableOperation != dropTableOperation) { @@ -326,7 +315,7 @@ public virtual IReadOnlyList GetDifferences(IModel source, I } } - var newDiffContext = new DiffContext(null, null); + var newDiffContext = new DiffContext(); dropTableOperations = dropTableGraph.TopologicalSort( (dropTableOperation, principalDropTableOperation, foreignKeys) => { @@ -367,8 +356,8 @@ public virtual IReadOnlyList GetDifferences(IModel source, I /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual IEnumerable Diff( - [CanBeNull] IModel source, - [CanBeNull] IModel target, + [CanBeNull] IRelationalModel source, + [CanBeNull] IRelationalModel target, [NotNull] DiffContext diffContext) { TrackData(source, target); @@ -376,12 +365,12 @@ public virtual IReadOnlyList GetDifferences(IModel source, I var schemaOperations = source != null && target != null ? DiffAnnotations(source, target) .Concat(Diff(GetSchemas(source), GetSchemas(target), diffContext)) - .Concat(Diff(diffContext.GetSourceTables(), diffContext.GetTargetTables(), diffContext)) - .Concat(Diff(source.GetSequences(), target.GetSequences(), diffContext)) + .Concat(Diff(source.Tables, target.Tables, diffContext)) + .Concat(Diff(source.Sequences, target.Sequences, diffContext)) .Concat( Diff( - diffContext.GetSourceTables().SelectMany(s => GetForeignKeys(s)), - diffContext.GetTargetTables().SelectMany(t => GetForeignKeys(t)), + source.Tables.SelectMany(s => s.ForeignKeyConstraints), + target.Tables.SelectMany(t => t.ForeignKeyConstraints), diffContext)) : target != null ? Add(target, diffContext) @@ -393,11 +382,11 @@ public virtual IReadOnlyList GetDifferences(IModel source, I } private IEnumerable DiffAnnotations( - IModel source, - IModel target) + IRelationalModel source, + IRelationalModel target) { - var sourceMigrationsAnnotations = source == null ? null : MigrationsAnnotations.For(source).ToList(); - var targetMigrationsAnnotations = target == null ? null : MigrationsAnnotations.For(target).ToList(); + var sourceMigrationsAnnotations = source?.GetAnnotations().ToList(); + var targetMigrationsAnnotations = target?.GetAnnotations().ToList(); if (source == null) { @@ -413,11 +402,10 @@ public virtual IReadOnlyList GetDifferences(IModel source, I if (target == null) { - sourceMigrationsAnnotations = MigrationsAnnotations.ForRemove(source).ToList(); if (sourceMigrationsAnnotations.Count > 0) { var alterDatabaseOperation = new AlterDatabaseOperation(); - alterDatabaseOperation.OldDatabase.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + alterDatabaseOperation.OldDatabase.AddAnnotations(sourceMigrationsAnnotations); yield return alterDatabaseOperation; } @@ -439,12 +427,12 @@ public virtual IReadOnlyList GetDifferences(IModel source, I /// 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 virtual IEnumerable Add([NotNull] IModel target, [NotNull] DiffContext diffContext) + protected virtual IEnumerable Add([NotNull] IRelationalModel target, [NotNull] DiffContext diffContext) => DiffAnnotations(null, target) .Concat(GetSchemas(target).SelectMany(t => Add(t, diffContext))) - .Concat(diffContext.GetTargetTables().SelectMany(t => Add(t, diffContext))) - .Concat(target.GetSequences().SelectMany(t => Add(t, diffContext))) - .Concat(diffContext.GetTargetTables().SelectMany(t => GetForeignKeys(t)).SelectMany(k => Add(k, diffContext))); + .Concat(target.Tables.SelectMany(t => Add(t, diffContext))) + .Concat(target.Sequences.SelectMany(t => Add(t, diffContext))) + .Concat(target.Tables.SelectMany(t => t.ForeignKeyConstraints).SelectMany(k => Add(k, diffContext))); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -452,10 +440,10 @@ protected virtual IEnumerable Add([NotNull] IModel target, [ /// 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 virtual IEnumerable Remove([NotNull] IModel source, [NotNull] DiffContext diffContext) + protected virtual IEnumerable Remove([NotNull] IRelationalModel source, [NotNull] DiffContext diffContext) => DiffAnnotations(source, null) - .Concat(diffContext.GetSourceTables().SelectMany(t => Remove(t, diffContext))) - .Concat(source.GetSequences().SelectMany(t => Remove(t, diffContext))); + .Concat(source.Tables.SelectMany(t => Remove(t, diffContext))) + .Concat(source.Sequences.SelectMany(t => Remove(t, diffContext))); #endregion @@ -544,8 +532,7 @@ protected virtual IEnumerable Remove([NotNull] string source (s, t, c) => string.Equals(GetRootType(s).Name, GetRootType(t).Name, StringComparison.OrdinalIgnoreCase), (s, t, c) => s.EntityTypeMappings.Any( se => t.EntityTypeMappings.Any( - te => - string.Equals(se.EntityType.Name, te.EntityType.Name, StringComparison.OrdinalIgnoreCase)))); + te => string.Equals(se.EntityType.Name, te.EntityType.Name, StringComparison.OrdinalIgnoreCase)))); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -576,9 +563,8 @@ protected virtual IEnumerable Remove([NotNull] string source }; } - // Validation should ensure that all the relevant annotations for the collocated entity types are the same - var sourceMigrationsAnnotations = MigrationsAnnotations.For(GetRootType(source)).ToList(); - var targetMigrationsAnnotations = MigrationsAnnotations.For(GetRootType(target)).ToList(); + var sourceMigrationsAnnotations = source.GetAnnotations(); + var targetMigrationsAnnotations = target.GetAnnotations(); if (source.Comment != target.Comment || HasDifferences(sourceMigrationsAnnotations, targetMigrationsAnnotations)) @@ -597,10 +583,9 @@ protected virtual IEnumerable Remove([NotNull] string source yield return alterTableOperation; } - var operations = Diff(source.Columns.Select(c => c.PropertyMappings.First().Property), - target.Columns.Select(c => c.PropertyMappings.First().Property), diffContext) - .Concat(Diff(GetKeys(source), GetKeys(target), diffContext)) - .Concat(Diff(GetIndexes(source), GetIndexes(target), diffContext)) + var operations = Diff(source.Columns, target.Columns, diffContext) + .Concat(Diff(source.UniqueConstraints, target.UniqueConstraints, diffContext)) + .Concat(Diff(source.Indexes, target.Indexes, diffContext)) .Concat(Diff(source.CheckConstraints, target.CheckConstraints, diffContext)); foreach (var operation in operations) @@ -632,31 +617,28 @@ protected virtual IEnumerable Remove([NotNull] string source Name = target.Name, Comment = target.Comment }; - createTableOperation.AddAnnotations(MigrationsAnnotations.For(entityType)); + createTableOperation.AddAnnotations(target.GetAnnotations()); createTableOperation.Columns.AddRange( - GetSortedProperties(target).SelectMany(p => Add(p, diffContext, inline: true)).Cast()); - var primaryKey = entityType.FindPrimaryKey(); + GetSortedColumns(target).SelectMany(p => Add(p, diffContext, inline: true)).Cast()); + var primaryKey = target.PrimaryKey; if (primaryKey != null) { createTableOperation.PrimaryKey = Add(primaryKey, diffContext).Cast().Single(); } createTableOperation.UniqueConstraints.AddRange( - GetKeys(target).Where(k => !k.IsPrimaryKey()).SelectMany(k => Add(k, diffContext)) + target.UniqueConstraints.Where(c => !c.IsPrimaryKey).SelectMany(c => Add(c, diffContext)) .Cast()); createTableOperation.CheckConstraints.AddRange( target.CheckConstraints.SelectMany(c => Add(c, diffContext)) .Cast()); - foreach (var targetMapping in target.EntityTypeMappings) - { - diffContext.AddCreate(targetMapping.EntityType, createTableOperation); - } + diffContext.AddCreate(target, createTableOperation); yield return createTableOperation; - foreach (var operation in GetIndexes(target).SelectMany(i => Add(i, diffContext))) + foreach (var operation in target.Indexes.SelectMany(i => Add(i, diffContext))) { yield return operation; } @@ -677,18 +659,30 @@ protected virtual IEnumerable Remove([NotNull] string source } var operation = new DropTableOperation { Schema = source.Schema, Name = source.Name }; - operation.AddAnnotations(MigrationsAnnotations.ForRemove(GetRootType(source))); + operation.AddAnnotations(source.GetAnnotations()); diffContext.AddDrop(source, operation); yield return operation; } - private static IEnumerable GetSortedProperties(ITable table) - => GetSortedProperties(GetRootType(table)) - .Distinct((x, y) => x.GetColumnName() == y.GetColumnName()); + private static IEnumerable GetSortedColumns(ITable table) + { + var columns = table.Columns.ToHashSet(); + var sortedColumns = new List(columns.Count); + foreach (var property in GetSortedProperties(GetRootType(table), table)) + { + var column = property.GetTableColumnMappings().FirstOrDefault(m => m.TableMapping.Table == table)?.Column; + if (columns.Remove(column)) + { + sortedColumns.Add(column); + } + } + + return sortedColumns; + } - private static IEnumerable GetSortedProperties(IEntityType entityType) + private static IEnumerable GetSortedProperties(IEntityType entityType, ITable table) { var leastPriorityProperties = new List(); var leastPriorityPrimaryKeyProperties = new List(); @@ -748,35 +742,25 @@ private static IEnumerable GetSortedProperties(IEntityType entityType groups.Add(group.Key, group.Value.Values.ToList()); } - foreach (var definingForeignKey in entityType.GetDeclaredReferencingForeignKeys() - .Where( - fk => fk.DeclaringEntityType.GetRootType() != entityType.GetRootType() - && fk.DeclaringEntityType.GetTableName() == entityType.GetTableName() - && fk.DeclaringEntityType.GetSchema() == entityType.GetSchema() - && fk - == fk.DeclaringEntityType - .FindForeignKey( - fk.DeclaringEntityType.FindPrimaryKey().Properties, - entityType.FindPrimaryKey(), - entityType))) - { - var clrProperty = definingForeignKey.PrincipalToDependent?.PropertyInfo; - var properties = GetSortedProperties(definingForeignKey.DeclaringEntityType).ToList(); - if (clrProperty == null) + foreach (var linkingForeignKey in table.GetReferencingInternalForeignKeys(entityType)) + { + var linkingNavigationProperty = linkingForeignKey.PrincipalToDependent?.PropertyInfo; + var properties = GetSortedProperties(linkingForeignKey.DeclaringEntityType, table).ToList(); + if (linkingNavigationProperty == null) { leastPriorityProperties.AddRange(properties); continue; } - groups.Add(clrProperty, properties); + groups.Add(linkingNavigationProperty, properties); - var clrType = clrProperty.DeclaringType; + var clrType = linkingNavigationProperty.DeclaringType; var index = clrType.GetTypeInfo().DeclaredProperties - .IndexOf(clrProperty, PropertyInfoEqualityComparer.Instance); + .IndexOf(linkingNavigationProperty, PropertyInfoEqualityComparer.Instance); Check.DebugAssert(clrType != null, "clrType is null"); - types.GetOrAddNew(clrType)[index] = clrProperty; + types.GetOrAddNew(clrType)[index] = linkingNavigationProperty; } var graph = new Multigraph(); @@ -813,7 +797,7 @@ private static IEnumerable GetSortedProperties(IEntityType entityType .Concat(leastPriorityPrimaryKeyProperties) .Concat(sortedPropertyInfos.Where(pi => !primaryKeyPropertyGroups.ContainsKey(pi)).SelectMany(p => groups[p])) .Concat(leastPriorityProperties) - .Concat(entityType.GetDirectlyDerivedTypes().SelectMany(GetSortedProperties)); + .Concat(entityType.GetDirectlyDerivedTypes().SelectMany(et => GetSortedProperties(et, table))); } private sealed class PropertyInfoEqualityComparer : IEqualityComparer @@ -842,8 +826,8 @@ public int GetHashCode(PropertyInfo obj) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual IEnumerable Diff( - [NotNull] IEnumerable source, - [NotNull] IEnumerable target, + [NotNull] IEnumerable source, + [NotNull] IEnumerable target, [NotNull] DiffContext diffContext) => DiffCollection( source, @@ -852,16 +836,21 @@ public int GetHashCode(PropertyInfo obj) Diff, (t, c) => Add(t, c), Remove, - (s, t, c) => string.Equals( - s.GetColumnName(), - t.GetColumnName(), - StringComparison.OrdinalIgnoreCase), - (s, t, c) => string.Equals(s.Name, t.Name, StringComparison.OrdinalIgnoreCase) - && EntityTypePathEquals(s.DeclaringEntityType, t.DeclaringEntityType, c), (s, t, c) => string.Equals(s.Name, t.Name, StringComparison.OrdinalIgnoreCase), - (s, t, c) => EntityTypePathEquals(s.DeclaringEntityType, t.DeclaringEntityType, c) - && PropertyStructureEquals(s, t), - (s, t, c) => PropertyStructureEquals(s, t)); + (s, t, c) => s.PropertyMappings.Any(sm => + t.PropertyMappings.Any(tm => + string.Equals(sm.Property.Name, tm.Property.Name, StringComparison.OrdinalIgnoreCase) + && EntityTypePathEquals(sm.Property.DeclaringEntityType, tm.Property.DeclaringEntityType, c))), + (s, t, c) => s.PropertyMappings.Any(sm => + t.PropertyMappings.Any(tm => + string.Equals(sm.Property.Name, tm.Property.Name, StringComparison.OrdinalIgnoreCase))), + (s, t, c) => s.PropertyMappings.Any(sm => + t.PropertyMappings.Any(tm => + EntityTypePathEquals(sm.Property.DeclaringEntityType, tm.Property.DeclaringEntityType, c) + && PropertyStructureEquals(sm.Property, tm.Property))), + (s, t, c) => s.PropertyMappings.Any(sm => + t.PropertyMappings.Any(tm => + PropertyStructureEquals(sm.Property, tm.Property)))); private bool PropertyStructureEquals(IProperty source, IProperty target) => source.ClrType == target.ClrType @@ -933,48 +922,48 @@ private static string GetDefiningNavigationName(IEntityType entityType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual IEnumerable Diff( - [NotNull] IProperty source, [NotNull] IProperty target, [NotNull] DiffContext diffContext) + [NotNull] IColumn source, [NotNull] IColumn target, [NotNull] DiffContext diffContext) { - var sourceMapping = source.GetTableColumnMappings().Single(); - var sourceColumn = sourceMapping.Column; - var targetMapping = target.GetTableColumnMappings().Single(); - var targetColumn = targetMapping.Column; - var table = targetColumn.Table; + var sourceMapping = source.PropertyMappings.First(); + var targetMapping = target.PropertyMappings.First(); + var table = target.Table; - if (source.GetColumnName() != target.GetColumnName()) + if (source.Name != target.Name) { yield return new RenameColumnOperation { Schema = table.Schema, Table = table.Name, - Name = sourceColumn.Name, - NewName = targetColumn.Name + Name = source.Name, + NewName = target.Name }; } - var sourceTypeMapping = sourceMapping.TypeMapping ?? TypeMappingSource.GetMapping(source); - var targetTypeMapping = targetMapping.TypeMapping ?? TypeMappingSource.GetMapping(target); + var sourceProperty = sourceMapping.Property; + var targetProperty = targetMapping.Property; + + var sourceTypeMapping = sourceMapping.TypeMapping; + var targetTypeMapping = targetMapping.TypeMapping; - var sourceColumnType = sourceColumn.Type - ?? sourceTypeMapping.StoreType; - var targetColumnType = targetColumn.Type - ?? targetTypeMapping.StoreType; + var sourceColumnType = source.Type ?? sourceTypeMapping.StoreType; + var targetColumnType = target.Type ?? targetTypeMapping.StoreType; - var sourceMigrationsAnnotations = MigrationsAnnotations.For(source).ToList(); - var targetMigrationsAnnotations = MigrationsAnnotations.For(target).ToList(); + var sourceMigrationsAnnotations = source.GetAnnotations(); + var targetMigrationsAnnotations = target.GetAnnotations(); - var isNullableChanged = sourceColumn.IsNullable != targetColumn.IsNullable; + var isNullableChanged = source.IsNullable != target.IsNullable; var columnTypeChanged = sourceColumnType != targetColumnType; if (isNullableChanged || columnTypeChanged - || source.GetDefaultValueSql() != target.GetDefaultValueSql() - || source.GetComputedColumnSql() != target.GetComputedColumnSql() - || !Equals(GetDefaultValue(source), GetDefaultValue(target)) - || source.GetComment() != target.GetComment() + || source.DefaultValueSql != target.DefaultValueSql + || source.ComputedColumnSql != target.ComputedColumnSql + || !Equals(GetDefaultValue(sourceProperty, GetValueConverter(sourceProperty, sourceTypeMapping)), + GetDefaultValue(targetProperty, GetValueConverter(sourceProperty, targetTypeMapping))) + || source.Comment != target.Comment || HasDifferences(sourceMigrationsAnnotations, targetMigrationsAnnotations)) { - var isDestructiveChange = isNullableChanged && sourceColumn.IsNullable + var isDestructiveChange = isNullableChanged && source.IsNullable // TODO: Detect type narrowing || columnTypeChanged; @@ -982,17 +971,17 @@ private static string GetDefiningNavigationName(IEntityType entityType) { Schema = table.Schema, Table = table.Name, - Name = targetColumn.Name, + Name = target.Name, IsDestructiveChange = isDestructiveChange }; Initialize( alterColumnOperation, target, targetTypeMapping, - targetColumn.IsNullable, targetMigrationsAnnotations, inline: true); + target.IsNullable, targetMigrationsAnnotations, inline: true); Initialize( alterColumnOperation.OldColumn, source, sourceTypeMapping, - sourceColumn.IsNullable, sourceMigrationsAnnotations, inline: true); + source.IsNullable, sourceMigrationsAnnotations, inline: true); yield return alterColumnOperation; } @@ -1005,24 +994,25 @@ private static string GetDefiningNavigationName(IEntityType entityType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual IEnumerable Add( - [NotNull] IProperty target, + [NotNull] IColumn target, [NotNull] DiffContext diffContext, bool inline = false) { - var mapping = target.GetTableColumnMappings().Single(); - var column = mapping.Column; - var table = column.Table; + var table = target.Table; var operation = new AddColumnOperation { Schema = table.Schema, Table = table.Name, - Name = column.Name + Name = target.Name }; + var targetMapping = target.PropertyMappings.First(); + var targetTypeMapping = targetMapping.TypeMapping; + Initialize( - operation, target, mapping.TypeMapping, column.IsNullable, - MigrationsAnnotations.For(target), inline); + operation, target, targetTypeMapping, target.IsNullable, + target.GetAnnotations(), inline); yield return operation; } @@ -1033,53 +1023,51 @@ private static string GetDefiningNavigationName(IEntityType entityType) /// 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 virtual IEnumerable Remove([NotNull] IProperty source, [NotNull] DiffContext diffContext) + protected virtual IEnumerable Remove([NotNull] IColumn source, [NotNull] DiffContext diffContext) { - var mapping = source.GetTableColumnMappings().Single(); - var column = mapping.Column; - var table = column.Table; + var table = source.Table; var operation = new DropColumnOperation { Schema = table.Schema, Table = table.Name, - Name = column.Name + Name = source.Name }; - operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + operation.AddAnnotations(source.GetAnnotations()); yield return operation; } private void Initialize( ColumnOperation columnOperation, - IProperty property, - CoreTypeMapping typeMapping, + IColumn column, + RelationalTypeMapping typeMapping, bool isNullable, IEnumerable migrationsAnnotations, bool inline = false) { + var property = column.PropertyMappings.First().Property; + var valueConverter = GetValueConverter(property, typeMapping); columnOperation.ClrType - = (typeMapping.Converter?.ProviderClrType + = (valueConverter?.ProviderClrType ?? typeMapping.ClrType).UnwrapNullableType(); - columnOperation.ColumnType = property.GetConfiguredColumnType(); - columnOperation.MaxLength = property.GetMaxLength(); - columnOperation.IsUnicode = property.IsUnicode(); - columnOperation.IsFixedLength = property.IsFixedLength(); - columnOperation.IsRowVersion = property.ClrType == typeof(byte[]) - && property.IsConcurrencyToken - && property.ValueGenerated == ValueGenerated.OnAddOrUpdate; + columnOperation.ColumnType = column.Type; + columnOperation.MaxLength = column.MaxLength; + columnOperation.IsUnicode = column.IsUnicode; + columnOperation.IsFixedLength = column.IsFixedLength; + columnOperation.IsRowVersion = column.IsRowVersion; columnOperation.IsNullable = isNullable; - var defaultValue = GetDefaultValue(property); + var defaultValue = GetDefaultValue(property, valueConverter); columnOperation.DefaultValue = (defaultValue == DBNull.Value ? null : defaultValue) ?? (inline || isNullable ? null : GetDefaultValue(columnOperation.ClrType)); - columnOperation.DefaultValueSql = property.GetDefaultValueSql(); - columnOperation.ComputedColumnSql = property.GetComputedColumnSql(); - columnOperation.Comment = property.GetComment(); + columnOperation.DefaultValueSql = column.DefaultValueSql; + columnOperation.ComputedColumnSql = column.ComputedColumnSql; + columnOperation.Comment = column.Comment; columnOperation.AddAnnotations(migrationsAnnotations); } @@ -1094,8 +1082,8 @@ protected virtual IEnumerable Remove([NotNull] IProperty sou /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual IEnumerable Diff( - [NotNull] IEnumerable source, - [NotNull] IEnumerable target, + [NotNull] IEnumerable source, + [NotNull] IEnumerable target, [NotNull] DiffContext diffContext) => DiffCollection( source, @@ -1104,11 +1092,11 @@ protected virtual IEnumerable Remove([NotNull] IProperty sou Diff, Add, Remove, - (s, t, c) => s.GetName() == t.GetName() - && s.Properties.Select(p => p.GetColumnName()).SequenceEqual( - t.Properties.Select(p => c.FindSource(p)?.GetColumnName())) - && s.IsPrimaryKey() == t.IsPrimaryKey() - && !HasDifferences(MigrationsAnnotations.For(s), MigrationsAnnotations.For(t))); + (s, t, c) => s.Name == t.Name + && s.Columns.Select(p => p.Name).SequenceEqual( + t.Columns.Select(p => c.FindSource(p)?.Name)) + && s.IsPrimaryKey == t.IsPrimaryKey + && !HasDifferences(s.GetAnnotations(), t.GetAnnotations())); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1117,8 +1105,8 @@ protected virtual IEnumerable Remove([NotNull] IProperty sou /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual IEnumerable Diff( - [NotNull] IKey source, - [NotNull] IKey target, + [NotNull] IUniqueConstraint source, + [NotNull] IUniqueConstraint target, [NotNull] DiffContext diffContext) => Enumerable.Empty(); @@ -1128,34 +1116,34 @@ protected virtual IEnumerable Remove([NotNull] IProperty sou /// 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 virtual IEnumerable Add([NotNull] IKey target, [NotNull] DiffContext diffContext) + protected virtual IEnumerable Add([NotNull] IUniqueConstraint target, [NotNull] DiffContext diffContext) { - var targetEntityType = target.DeclaringEntityType.GetRootType(); - var columns = GetColumns(target.Properties); + var targetTable = target.Table; + var columns = target.Columns; MigrationOperation operation; - if (target.IsPrimaryKey()) + if (target.IsPrimaryKey) { operation = new AddPrimaryKeyOperation { - Schema = targetEntityType.GetSchema(), - Table = targetEntityType.GetTableName(), - Name = target.GetName(), - Columns = columns + Schema = targetTable.Schema, + Table = targetTable.Name, + Name = target.Name, + Columns = columns.Select(c => c.Name).ToArray() }; } else { operation = new AddUniqueConstraintOperation { - Schema = targetEntityType.GetSchema(), - Table = targetEntityType.GetTableName(), - Name = target.GetName(), - Columns = columns + Schema = targetTable.Schema, + Table = targetTable.Name, + Name = target.Name, + Columns = columns.Select(c => c.Name).ToArray() }; } - operation.AddAnnotations(MigrationsAnnotations.For(target)); + operation.AddAnnotations(target.GetAnnotations()); yield return operation; } @@ -1167,32 +1155,32 @@ protected virtual IEnumerable Add([NotNull] IKey target, [No /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual IEnumerable Remove( - [NotNull] IKey source, + [NotNull] IUniqueConstraint source, [NotNull] DiffContext diffContext) { - var sourceEntityType = source.DeclaringEntityType.GetRootType(); + var table = source.Table; MigrationOperation operation; - if (source.IsPrimaryKey()) + if (source.IsPrimaryKey) { operation = new DropPrimaryKeyOperation { - Schema = sourceEntityType.GetSchema(), - Table = sourceEntityType.GetTableName(), - Name = source.GetName() + Schema = table.Schema, + Table = table.Name, + Name = source.Name }; } else { operation = new DropUniqueConstraintOperation { - Schema = sourceEntityType.GetSchema(), - Table = sourceEntityType.GetTableName(), - Name = source.GetName() + Schema = table.Schema, + Table = table.Name, + Name = source.Name }; } - operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + operation.AddAnnotations(source.GetAnnotations()); yield return operation; } @@ -1208,8 +1196,8 @@ protected virtual IEnumerable Add([NotNull] IKey target, [No /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual IEnumerable Diff( - [NotNull] IEnumerable source, - [NotNull] IEnumerable target, + [NotNull] IEnumerable source, + [NotNull] IEnumerable target, [NotNull] DiffContext diffContext) => DiffCollection( source, @@ -1218,15 +1206,14 @@ protected virtual IEnumerable Add([NotNull] IKey target, [No Diff, Add, Remove, - (s, t, c) => s.GetConstraintName() == t.GetConstraintName() - && s.Properties.Select(p => p.GetColumnName()).SequenceEqual( - t.Properties.Select(p => c.FindSource(p)?.GetColumnName())) - && c.GetTable(s.PrincipalEntityType) - == c.FindSource(c.GetTable(t.PrincipalEntityType)) - && s.PrincipalKey.Properties.Select(p => p.GetColumnName()).SequenceEqual( - t.PrincipalKey.Properties.Select(p => c.FindSource(p)?.GetColumnName())) - && ToReferentialAction(s.DeleteBehavior) == ToReferentialAction(t.DeleteBehavior) - && !HasDifferences(MigrationsAnnotations.For(s), MigrationsAnnotations.For(t))); + (s, t, context) => s.Name == t.Name + && s.Columns.Select(c => c.Name).SequenceEqual( + t.Columns.Select(c => context.FindSource(c)?.Name)) + && s.PrincipalTable == context.FindSource(t.PrincipalTable) + && s.PrincipalColumns.Select(c => c.Name).SequenceEqual( + t.PrincipalColumns.Select(c => context.FindSource(c)?.Name)) + && s.OnDeleteAction == t.OnDeleteAction + && !HasDifferences(s.GetAnnotations(), t.GetAnnotations())); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1235,7 +1222,7 @@ protected virtual IEnumerable Add([NotNull] IKey target, [No /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual IEnumerable Diff( - [NotNull] IForeignKey source, [NotNull] IForeignKey target, [NotNull] DiffContext diffContext) + [NotNull] IForeignKeyConstraint source, [NotNull] IForeignKeyConstraint target, [NotNull] DiffContext diffContext) => Enumerable.Empty(); /// @@ -1244,25 +1231,30 @@ protected virtual IEnumerable Add([NotNull] IKey target, [No /// 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 virtual IEnumerable Add([NotNull] IForeignKey target, [NotNull] DiffContext diffContext) + protected virtual IEnumerable Add([NotNull] IForeignKeyConstraint target, [NotNull] DiffContext diffContext) { - var targetEntityType = target.DeclaringEntityType.GetRootType(); - var targetPrincipalEntityType = target.PrincipalEntityType.GetRootType(); + var targetTable = target.Table; + if (!targetTable.IsMigratable) + { + yield break; + } + + var targetPrincipleTable = target.PrincipalTable; var operation = new AddForeignKeyOperation { - Schema = targetEntityType.GetSchema(), - Table = targetEntityType.GetTableName(), - Name = target.GetConstraintName(), - Columns = GetColumns(target.Properties), - PrincipalSchema = targetPrincipalEntityType.GetSchema(), - PrincipalTable = targetPrincipalEntityType.GetTableName(), - PrincipalColumns = GetColumns(target.PrincipalKey.Properties), - OnDelete = ToReferentialAction(target.DeleteBehavior) + Schema = targetTable.Schema, + Table = targetTable.Name, + Name = target.Name, + Columns = target.Columns.Select(c => c.Name).ToArray(), + PrincipalSchema = targetPrincipleTable.Schema, + PrincipalTable = targetPrincipleTable.Name, + PrincipalColumns = target.PrincipalColumns.Select(c => c.Name).ToArray(), + OnDelete = target.OnDeleteAction }; - operation.AddAnnotations(MigrationsAnnotations.For(target)); + operation.AddAnnotations(target.GetAnnotations()); - var createTableOperation = diffContext.FindCreate(targetEntityType); + var createTableOperation = diffContext.FindCreate(targetTable); if (createTableOperation != null) { createTableOperation.ForeignKeys.Add(operation); @@ -1279,20 +1271,20 @@ protected virtual IEnumerable Add([NotNull] IForeignKey targ /// 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 virtual IEnumerable Remove([NotNull] IForeignKey source, [NotNull] DiffContext diffContext) + protected virtual IEnumerable Remove([NotNull] IForeignKeyConstraint source, [NotNull] DiffContext diffContext) { - var declaringRootEntityType = source.DeclaringEntityType.GetRootType(); + var sourceTable = source.Table; - var dropTableOperation = diffContext.FindDrop(declaringRootEntityType); + var dropTableOperation = diffContext.FindDrop(sourceTable); if (dropTableOperation == null) { var operation = new DropForeignKeyOperation { - Schema = declaringRootEntityType.GetSchema(), - Table = declaringRootEntityType.GetTableName(), - Name = source.GetConstraintName() + Schema = sourceTable.Schema, + Table = sourceTable.Name, + Name = source.Name }; - operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + operation.AddAnnotations(source.GetAnnotations()); yield return operation; } @@ -1309,8 +1301,8 @@ protected virtual IEnumerable Remove([NotNull] IForeignKey s /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual IEnumerable Diff( - [NotNull] IEnumerable source, - [NotNull] IEnumerable target, + [NotNull] IEnumerable source, + [NotNull] IEnumerable target, [NotNull] DiffContext diffContext) => DiffCollection( source, @@ -1319,19 +1311,16 @@ protected virtual IEnumerable Remove([NotNull] IForeignKey s Diff, Add, Remove, - (s, t, c) => string.Equals( - s.GetName(), - t.GetName(), - StringComparison.OrdinalIgnoreCase) + (s, t, c) => string.Equals(s.Name, t.Name, StringComparison.OrdinalIgnoreCase) && IndexStructureEquals(s, t, c), (s, t, c) => IndexStructureEquals(s, t, c)); - private bool IndexStructureEquals(IIndex source, IIndex target, DiffContext diffContext) + private bool IndexStructureEquals(ITableIndex source, ITableIndex target, DiffContext diffContext) => source.IsUnique == target.IsUnique - && source.GetFilter() == target.GetFilter() - && !HasDifferences(MigrationsAnnotations.For(source), MigrationsAnnotations.For(target)) - && source.Properties.Select(p => p.GetColumnName()).SequenceEqual( - target.Properties.Select(p => diffContext.FindSource(p)?.GetColumnName())); + && source.Filter == target.Filter + && !HasDifferences(source.GetAnnotations(), target.GetAnnotations()) + && source.Columns.Select(p => p.Name).SequenceEqual( + target.Columns.Select(p => diffContext.FindSource(p)?.Name)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1340,20 +1329,20 @@ private bool IndexStructureEquals(IIndex source, IIndex target, DiffContext diff /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual IEnumerable Diff( - [NotNull] IIndex source, - [NotNull] IIndex target, + [NotNull] ITableIndex source, + [NotNull] ITableIndex target, [NotNull] DiffContext diffContext) { - var targetEntityType = target.DeclaringEntityType.GetRootType(); - var sourceName = source.GetName(); - var targetName = target.GetName(); + var targetTable = target.Table; + var sourceName = source.Name; + var targetName = target.Name; if (sourceName != targetName) { yield return new RenameIndexOperation { - Schema = targetEntityType.GetSchema(), - Table = targetEntityType.GetTableName(), + Schema = targetTable.Schema, + Table = targetTable.Name, Name = sourceName, NewName = targetName }; @@ -1367,21 +1356,21 @@ private bool IndexStructureEquals(IIndex source, IIndex target, DiffContext diff /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual IEnumerable Add( - [NotNull] IIndex target, + [NotNull] ITableIndex target, [NotNull] DiffContext diffContext) { - var targetEntityType = target.DeclaringEntityType.GetRootType(); + var targetTable = target.Table; var operation = new CreateIndexOperation { - Name = target.GetName(), - Schema = targetEntityType.GetSchema(), - Table = targetEntityType.GetTableName(), - Columns = GetColumns(target.Properties), + Name = target.Name, + Schema = targetTable.Schema, + Table = targetTable.Name, + Columns = target.Columns.Select(c => c.Name).ToArray(), IsUnique = target.IsUnique, - Filter = target.GetFilter() + Filter = target.Filter }; - operation.AddAnnotations(MigrationsAnnotations.For(target)); + operation.AddAnnotations(target.GetAnnotations()); yield return operation; } @@ -1392,17 +1381,17 @@ private bool IndexStructureEquals(IIndex source, IIndex target, DiffContext diff /// 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 virtual IEnumerable Remove([NotNull] IIndex source, [NotNull] DiffContext diffContext) + protected virtual IEnumerable Remove([NotNull] ITableIndex source, [NotNull] DiffContext diffContext) { - var sourceEntityType = source.DeclaringEntityType.GetRootType(); + var sourceTable = source.Table; var operation = new DropIndexOperation { - Name = source.GetName(), - Schema = sourceEntityType.GetSchema(), - Table = sourceEntityType.GetTableName() + Name = source.Name, + Schema = sourceTable.Schema, + Table = sourceTable.Name }; - operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + operation.AddAnnotations(source.GetAnnotations()); yield return operation; } @@ -1461,7 +1450,7 @@ protected virtual IEnumerable Add([NotNull] ICheckConstraint }; operation.Sql = target.Sql; - operation.AddAnnotations(MigrationsAnnotations.For(target)); + operation.AddAnnotations(target.GetAnnotations()); yield return operation; } @@ -1482,7 +1471,7 @@ protected virtual IEnumerable Remove([NotNull] ICheckConstra Schema = sourceEntityType.GetSchema(), Table = sourceEntityType.GetTableName() }; - operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + operation.AddAnnotations(source.GetAnnotations()); yield return operation; } @@ -1545,8 +1534,8 @@ protected virtual IEnumerable Remove([NotNull] ICheckConstra }; } - var sourceMigrationsAnnotations = MigrationsAnnotations.For(source).ToList(); - var targetMigrationsAnnotations = MigrationsAnnotations.For(target).ToList(); + var sourceMigrationsAnnotations = source.GetAnnotations(); + var targetMigrationsAnnotations = target.GetAnnotations(); if (source.IncrementBy != target.IncrementBy || source.MaxValue != target.MaxValue @@ -1579,7 +1568,7 @@ protected virtual IEnumerable Add([NotNull] ISequence target StartValue = target.StartValue }; - yield return Initialize(operation, target, MigrationsAnnotations.For(target)); + yield return Initialize(operation, target, target.GetAnnotations()); } /// @@ -1591,7 +1580,7 @@ protected virtual IEnumerable Add([NotNull] ISequence target protected virtual IEnumerable Remove([NotNull] ISequence source, [NotNull] DiffContext diffContext) { var operation = new DropSequenceOperation { Schema = source.Schema, Name = source.Name }; - operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + operation.AddAnnotations(source.GetAnnotations()); yield return operation; } @@ -1621,8 +1610,8 @@ protected virtual IEnumerable Remove([NotNull] ISequence sou /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual void TrackData( - [CanBeNull] IModel source, - [CanBeNull] IModel target) + [CanBeNull] IRelationalModel source, + [CanBeNull] IRelationalModel target) { if (target == null) { @@ -1630,10 +1619,10 @@ protected virtual IEnumerable Remove([NotNull] ISequence sou return; } - _targetUpdateAdapter = UpdateAdapterFactory.CreateStandalone(target); + _targetUpdateAdapter = UpdateAdapterFactory.CreateStandalone(target.Model); _targetUpdateAdapter.CascadeDeleteTiming = CascadeTiming.Never; - foreach (var targetEntityType in target.GetEntityTypes()) + foreach (var targetEntityType in target.Model.GetEntityTypes()) { foreach (var targetSeed in targetEntityType.GetSeedData()) { @@ -1649,10 +1638,10 @@ protected virtual IEnumerable Remove([NotNull] ISequence sou return; } - _sourceUpdateAdapter = UpdateAdapterFactory.CreateStandalone(source); + _sourceUpdateAdapter = UpdateAdapterFactory.CreateStandalone(source.Model); _sourceUpdateAdapter.CascadeDeleteTiming = CascadeTiming.OnSaveChanges; - foreach (var sourceEntityType in source.GetEntityTypes()) + foreach (var sourceEntityType in source.Model.GetEntityTypes()) { foreach (var sourceSeed in sourceEntityType.GetSeedData()) { @@ -1710,28 +1699,31 @@ protected virtual IEnumerable Remove([NotNull] ISequence sou var keyPropertiesMap = new List<(IProperty, ValueConverter, ValueConverter)>(); foreach (var keyProperty in targetKey.Properties) { - var sourceProperty = diffContext.FindSource(keyProperty); - if (sourceProperty == null) + var targetColumn = keyProperty.GetTableColumnMappings().Single(m => m.TableMapping.Table == target).Column; + var sourceColumn = diffContext.FindSource(targetColumn); + if (sourceColumn == null) { break; } - foreach (var matchingSourceProperty in sourceEntityType.GetProperties()) + foreach (var sourceProperty in sourceColumn.PropertyMappings.Select(m => m.Property)) { - if (matchingSourceProperty.GetColumnName() == sourceProperty.GetColumnName()) + if (!sourceProperty.DeclaringEntityType.IsAssignableFrom(sourceEntityType)) { - var sourceConverter = GetValueConverter(sourceProperty); - var targetConverter = GetValueConverter(keyProperty); - if (matchingSourceProperty.ClrType != keyProperty.ClrType - && (sourceConverter == null || sourceConverter.ProviderClrType != keyProperty.ClrType) - && (targetConverter == null || targetConverter.ProviderClrType != matchingSourceProperty.ClrType)) - { - continue; - } + continue; + } - keyPropertiesMap.Add((matchingSourceProperty, sourceConverter, targetConverter)); - break; + var sourceConverter = GetValueConverter(sourceProperty); + var targetConverter = GetValueConverter(keyProperty); + if (sourceProperty.ClrType != keyProperty.ClrType + && (sourceConverter == null || sourceConverter.ProviderClrType != keyProperty.ClrType) + && (targetConverter == null || targetConverter.ProviderClrType != sourceProperty.ClrType)) + { + continue; } + + keyPropertiesMap.Add((sourceProperty, sourceConverter, targetConverter)); + break; } } @@ -1815,10 +1807,21 @@ protected virtual IEnumerable Remove([NotNull] ISequence sou foreach (var targetProperty in entry.EntityType.GetProperties()) { - var sourceProperty = diffContext.FindSource(targetProperty); - if (sourceProperty == null - || !sourceEntityType.GetProperties().Contains(sourceProperty) - || targetProperty.ValueGenerated != ValueGenerated.Never) + if (targetProperty.ValueGenerated != ValueGenerated.Never) + { + continue; + } + + var targetColumn = targetProperty.GetTableColumnMappings().Single(m => m.TableMapping.Table == target).Column; + var sourceColumn = diffContext.FindSource(targetColumn); + if (sourceColumn == null) + { + continue; + } + + var sourceProperty = sourceColumn.PropertyMappings.Select(m => m.Property) + .SingleOrDefault(p => p.DeclaringEntityType.IsAssignableFrom(sourceEntityType)); + if (sourceProperty == null) { continue; } @@ -1971,6 +1974,7 @@ protected virtual IEnumerable GetDataOperations([NotNull] Di continue; } + var model = updateAdapter.Model.GetRelationalModel(); var commandBatches = new CommandBatchPreparer(CommandBatchPreparerDependencies) .BatchCommands(entries, updateAdapter); @@ -2036,7 +2040,7 @@ protected virtual IEnumerable GetDataOperations([NotNull] Di batchInsertOperation = null; } - if (c.Entries.All(e => diffContext.FindDrop(e.EntityType.GetRootType()) == null)) + if (diffContext.FindDrop(model.FindTable(c.TableName, c.Schema)) == null) { yield return new DeleteDataOperation { @@ -2178,9 +2182,10 @@ protected virtual bool HasDifferences([NotNull] IEnumerable source, /// 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 virtual IEnumerable GetSchemas([NotNull] IModel model) - => model.GetRootEntityTypes().Where(t => !t.IsIgnoredByMigrations()).Select(t => t.GetSchema()) - .Concat(model.GetSequences().Select(s => s.Schema)) + protected virtual IEnumerable GetSchemas([NotNull] IRelationalModel model) + => model.Tables.Where(t => t.IsMigratable).Select(t => t.Schema) + .Concat(model.Views.Where(t => t.ViewDefinition != null).Select(s => s.Schema)) + .Concat(model.Sequences.Select(s => s.Schema)) .Where(s => !string.IsNullOrEmpty(s)) .Distinct(); @@ -2197,21 +2202,21 @@ protected virtual object GetDefaultValue([NotNull] Type type) ? Array.CreateInstance(type.GetElementType(), 0) : type.UnwrapNullableType().GetDefaultValue(); - private object GetDefaultValue(IProperty property) + private object GetDefaultValue(IProperty property, ValueConverter valueConverter = null) { var value = property.GetDefaultValue(); - var converter = GetValueConverter(property); + var converter = valueConverter ?? GetValueConverter(property); return converter != null ? converter.ConvertToProvider(value) : value; } - private ValueConverter GetValueConverter(IProperty property) - => property.GetValueConverter() ?? property.FindRelationalTypeMapping()?.Converter; + private ValueConverter GetValueConverter(IProperty property, RelationalTypeMapping typeMapping = null) + => property.GetValueConverter() ?? (property.FindRelationalTypeMapping() ?? typeMapping)?.Converter; private static IEntityType GetRootType(ITable table) => table.EntityTypeMappings.Select(m => m.EntityType).FirstOrDefault( - t => t.BaseType == null && table.GetInternalForeignKeys(t) == null); + t => t.BaseType == null && !table.GetInternalForeignKeys(t).Any()); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2237,38 +2242,6 @@ public static IProperty[] GetMappedProperties([NotNull] ITable table, [NotNull] return properties; } - private static IEnumerable GetKeys(ITable table) - => table.EntityTypeMappings.SelectMany(m => m.EntityType.GetDeclaredKeys()) - .Distinct((x, y) => x.GetName() == y.GetName()); - - private static IEnumerable GetIndexes(ITable table) - => table.EntityTypeMappings.SelectMany(m => m.EntityType.GetDeclaredIndexes()) - .Distinct((x, y) => x.GetName() == y.GetName()); - - private static IEnumerable GetForeignKeys(ITable table) - => table.EntityTypeMappings.SelectMany(m => m.EntityType.GetDeclaredForeignKeys()) - .Distinct((x, y) => x.GetConstraintName() == y.GetConstraintName()) - .Where(fk => !fk.PrincipalKey.Properties - .Select(p => p.GetTableColumnMappings().Select(m => m.Column).SingleOrDefault(m => m.Table == table)) - .SequenceEqual(fk.Properties. - Select(p => p.GetTableColumnMappings().Select(m => m.Column).SingleOrDefault(m => m.Table == table)))); - - private static ReferentialAction ToReferentialAction(DeleteBehavior deleteBehavior) - { - switch (deleteBehavior) - { - case DeleteBehavior.SetNull: - return ReferentialAction.SetNull; - case DeleteBehavior.Cascade: - return ReferentialAction.Cascade; - case DeleteBehavior.NoAction: - case DeleteBehavior.ClientNoAction: - return ReferentialAction.NoAction; - default: - return ReferentialAction.Restrict; - } - } - private static object[,] ToMultidimensionalArray(IReadOnlyList values) { var result = new object[1, values.Count]; @@ -2316,56 +2289,18 @@ private sealed class EntryMapping /// protected class DiffContext { - private readonly IEnumerable _sourceTables; - private readonly IEnumerable _targetTables; - private readonly IDictionary _targetToSource = new Dictionary(); private readonly IDictionary _sourceToTarget = new Dictionary(); - private readonly IDictionary _createTableOperations - = new Dictionary(); + private readonly IDictionary _createTableOperations + = new Dictionary(); - private readonly IDictionary _dropTableOperations - = new Dictionary(); + private readonly IDictionary _dropTableOperations + = new Dictionary(); private readonly IDictionary _removedTables = new Dictionary(); - /// - /// 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 DiffContext([CanBeNull] IModel source, [CanBeNull] IModel target) - { - if (source != null) - { - _sourceTables = source.GetTables(); - } - - if (target != null) - { - _targetTables = target.GetTables(); - } - } - - /// - /// 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 IEnumerable GetSourceTables() => _sourceTables; - - /// - /// 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 IEnumerable GetTargetTables() => _targetTables; - /// /// 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 @@ -2384,7 +2319,7 @@ public virtual void AddMapping([NotNull] T source, [NotNull] T target) /// 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 void AddCreate([NotNull] IEntityType target, [NotNull] CreateTableOperation operation) + public virtual void AddCreate([NotNull] ITable target, [NotNull] CreateTableOperation operation) => _createTableOperations.Add(target, operation); /// @@ -2395,11 +2330,7 @@ public virtual void AddCreate([NotNull] IEntityType target, [NotNull] CreateTabl /// public virtual void AddDrop([NotNull] ITable source, [NotNull] DropTableOperation operation) { - foreach (var sourceMapping in source.EntityTypeMappings) - { - _dropTableOperations.Add(sourceMapping.EntityType, operation); - } - + _dropTableOperations.Add(source, operation); _removedTables.Add(operation, source); } @@ -2426,40 +2357,6 @@ public virtual T FindSource([CanBeNull] T target) ? (T)source : null; - /// - /// 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 IProperty FindSource([NotNull] IProperty target) - { - var source = FindSource(target); - if (source != null) - { - return source; - } - - var synonymousTargets = target.GetTableColumnMappings().Single().Column.PropertyMappings.Select(m => m.Property); - foreach (var synonymousTarget in synonymousTargets) - { - if (synonymousTarget == target) - { - continue; - } - - source = FindSource(synonymousTarget); - if (source != null) - { - _targetToSource.Add(target, source); - - return source; - } - } - - return null; - } - /// /// 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 @@ -2480,7 +2377,7 @@ public virtual T FindTarget([CanBeNull] T source) /// 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 CreateTableOperation FindCreate([NotNull] IEntityType target) + public virtual CreateTableOperation FindCreate([NotNull] ITable target) => _createTableOperations.TryGetValue(target, out var operation) ? operation : null; @@ -2491,7 +2388,7 @@ public virtual CreateTableOperation FindCreate([NotNull] IEntityType target) /// 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 DropTableOperation FindDrop([NotNull] IEntityType source) + public virtual DropTableOperation FindDrop([NotNull] ITable source) => _dropTableOperations.TryGetValue(source, out var operation) ? operation : null; diff --git a/src/EFCore.Relational/Migrations/Migration.cs b/src/EFCore.Relational/Migrations/Migration.cs index b2d445b4970..aa1c065512d 100644 --- a/src/EFCore.Relational/Migrations/Migration.cs +++ b/src/EFCore.Relational/Migrations/Migration.cs @@ -37,6 +37,7 @@ IModel Create() var modelBuilder = new ModelBuilder(model); BuildTargetModel(modelBuilder); + model = (Model)RelationalModel.Add(model, null); return model.FinalizeModel(); } diff --git a/src/EFCore.Relational/Migrations/MigrationsAnnotationProvider.cs b/src/EFCore.Relational/Migrations/MigrationsAnnotationProvider.cs deleted file mode 100644 index c4e7850697d..00000000000 --- a/src/EFCore.Relational/Migrations/MigrationsAnnotationProvider.cs +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Utilities; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.EntityFrameworkCore.Migrations -{ - /// - /// - /// A base class inherited by database providers that gives access to annotations - /// used by EF Core Migrations on various elements of the . - /// - /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . - /// - /// - public class MigrationsAnnotationProvider : IMigrationsAnnotationProvider - { - /// - /// Initializes a new instance of this class. - /// - /// Parameter object containing dependencies for this service. - public MigrationsAnnotationProvider([NotNull] MigrationsAnnotationProviderDependencies dependencies) - { - Check.NotNull(dependencies, nameof(dependencies)); - } - - /// - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The model. - /// The annotations. - public virtual IEnumerable For(IModel model) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The entity type. - /// The annotations. - public virtual IEnumerable For(IEntityType entityType) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The foreign key. - /// The annotations. - public virtual IEnumerable For(IForeignKey foreignKey) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The index. - /// The annotations. - public virtual IEnumerable For(IIndex index) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The key. - /// The annotations. - public virtual IEnumerable For(IKey key) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The property. - /// The annotations. - public virtual IEnumerable For(IProperty property) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The sequence. - /// The annotations. - public virtual IEnumerable For(ISequence sequence) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given . - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The check constraint. - /// The annotations. - public virtual IEnumerable For(ICheckConstraint checkConstraint) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The model. - /// The annotations. - public virtual IEnumerable ForRemove(IModel model) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The entity type. - /// The annotations. - public virtual IEnumerable ForRemove(IEntityType entityType) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The foreign key. - /// The annotations. - public virtual IEnumerable ForRemove(IForeignKey foreignKey) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The index. - /// The annotations. - public virtual IEnumerable ForRemove(IIndex index) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The key. - /// The annotations. - public virtual IEnumerable ForRemove(IKey key) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The property. - /// The annotations. - public virtual IEnumerable ForRemove(IProperty property) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The sequence. - /// The annotations. - public virtual IEnumerable ForRemove(ISequence sequence) => Enumerable.Empty(); - - /// - /// - /// Gets provider-specific Migrations annotations for the given - /// when it is being removed/altered. - /// - /// - /// The default implementation returns an empty collection. - /// - /// - /// The check constraint. - /// The annotations. - public virtual IEnumerable ForRemove(ICheckConstraint checkConstraint) => Enumerable.Empty(); - } -} diff --git a/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs b/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs index fc45e8aa7b0..26e0dc359f9 100644 --- a/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs +++ b/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs @@ -1177,36 +1177,39 @@ protected virtual IUpdateSqlGenerator SqlGenerator /// Gets the store/database type of a column given the provided metadata. /// /// The schema that contains the table, or null to use the default schema. - /// The table that contains the column. + /// The table that contains the column. /// The column name. /// The column metadata. /// The target model which may be null if the operations exist without a model. /// The database/store type for the column. protected virtual string GetColumnType( [CanBeNull] string schema, - [NotNull] string table, + [NotNull] string tableName, [NotNull] string name, [NotNull] ColumnOperation operation, [CanBeNull] IModel model) { - Check.NotEmpty(table, nameof(table)); + Check.NotEmpty(tableName, nameof(tableName)); Check.NotEmpty(name, nameof(name)); Check.NotNull(operation, nameof(operation)); var keyOrIndex = false; - var property = FindProperty(model, schema, table, name); - if (property != null) + var table = model?.GetRelationalModel().FindTable(tableName, schema); + var column = table?.FindColumn(name); + if (column != null) { - if (operation.IsUnicode == property.IsUnicode() - && operation.MaxLength == property.GetMaxLength() - && operation.IsFixedLength == property.IsFixedLength() - && operation.IsRowVersion == (property.IsConcurrencyToken && property.ValueGenerated == ValueGenerated.OnAddOrUpdate)) + if (operation.IsUnicode == column.IsUnicode + && operation.MaxLength == column.MaxLength + && operation.IsFixedLength == column.IsFixedLength + && operation.IsRowVersion == column.IsRowVersion) { - return Dependencies.TypeMappingSource.FindMapping(property).StoreType; + return column.Type; } - keyOrIndex = property.IsKey() || property.IsForeignKey(); + keyOrIndex = table.UniqueConstraints.Any(u => u.Columns.Contains(column)) + || table.ForeignKeyConstraints.Any(u => u.Columns.Contains(column)) + || table.Indexes.Any(u => u.Columns.Contains(column)); } return Dependencies.TypeMappingSource.FindMapping( @@ -1582,8 +1585,8 @@ protected virtual IUpdateSqlGenerator SqlGenerator [CanBeNull] IModel model, [CanBeNull] string schema, [NotNull] string tableName) - => model?.GetEntityTypes().Where( - t => t.GetTableName() == tableName && t.GetSchema() == schema && !t.IsIgnoredByMigrations()); + => model?.GetRelationalModel().FindTable(Check.NotEmpty(tableName, nameof(tableName)), schema) + ?.EntityTypeMappings.Select(m => m.EntityType); /// /// @@ -1592,7 +1595,7 @@ protected virtual IUpdateSqlGenerator SqlGenerator /// /// If multiple properties map to the same column, then the property returned is one chosen /// arbitrarily. The model validator ensures that all properties mapped to a given column - /// have consistent mappings. + /// have consistent configuration. /// /// /// The target model which may be null if the operations exist without a model. @@ -1605,10 +1608,8 @@ protected virtual IUpdateSqlGenerator SqlGenerator [CanBeNull] string schema, [NotNull] string tableName, [NotNull] string columnName) - // Any property that maps to the column will work because model validator has - // checked that all properties result in the same column definition. - => FindEntityTypes(model, schema, tableName)?.SelectMany(e => e.GetDeclaredProperties()) - .FirstOrDefault(p => p.GetColumnName() == columnName); + => model?.GetRelationalModel().FindTable(Check.NotEmpty(tableName, nameof(tableName)), schema) + .Columns.FirstOrDefault(c => c.Name == columnName)?.PropertyMappings.First().Property; /// /// Generates a SQL fragment to terminate the SQL command. diff --git a/src/EFCore.Relational/Migrations/Operations/AlterTableOperation.cs b/src/EFCore.Relational/Migrations/Operations/AlterTableOperation.cs index 020fd601dc3..5be88a13ff3 100644 --- a/src/EFCore.Relational/Migrations/Operations/AlterTableOperation.cs +++ b/src/EFCore.Relational/Migrations/Operations/AlterTableOperation.cs @@ -10,7 +10,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations.Operations /// /// A to alter an existing table. /// - [DebuggerDisplay("ALTER TABLE {Table}")] + [DebuggerDisplay("ALTER TABLE {Name}")] public class AlterTableOperation : TableOperation, IAlterMigrationOperation { /// diff --git a/src/EFCore.Relational/Migrations/Operations/DeleteDataOperation.cs b/src/EFCore.Relational/Migrations/Operations/DeleteDataOperation.cs index c3245837e7a..d5446291a47 100644 --- a/src/EFCore.Relational/Migrations/Operations/DeleteDataOperation.cs +++ b/src/EFCore.Relational/Migrations/Operations/DeleteDataOperation.cs @@ -49,7 +49,7 @@ public virtual IEnumerable GenerateModificationCommands([Ca KeyColumns.Length == KeyValues.GetLength(1), $"The number of key values doesn't match the number of keys (${KeyColumns.Length})"); - var table = model?.FindTable(Table, Schema); + var table = model?.GetRelationalModel().FindTable(Table, Schema); var properties = table != null ? MigrationsModelDiffer.GetMappedProperties(table, KeyColumns) : null; diff --git a/src/EFCore.Relational/Migrations/Operations/InsertDataOperation.cs b/src/EFCore.Relational/Migrations/Operations/InsertDataOperation.cs index eeeafb4a32a..8f627436a7e 100644 --- a/src/EFCore.Relational/Migrations/Operations/InsertDataOperation.cs +++ b/src/EFCore.Relational/Migrations/Operations/InsertDataOperation.cs @@ -48,7 +48,7 @@ public virtual IEnumerable GenerateModificationCommands([Ca Columns.Length == Values.GetLength(1), $"The number of values doesn't match the number of keys (${Columns.Length})"); - var table = model?.FindTable(Table, Schema); + var table = model?.GetRelationalModel().FindTable(Table, Schema); var properties = table != null ? MigrationsModelDiffer.GetMappedProperties(table, Columns) : null; diff --git a/src/EFCore.Relational/Migrations/Operations/UpdateDataOperation.cs b/src/EFCore.Relational/Migrations/Operations/UpdateDataOperation.cs index 8e4d5e18fe9..a8ba583e1a1 100644 --- a/src/EFCore.Relational/Migrations/Operations/UpdateDataOperation.cs +++ b/src/EFCore.Relational/Migrations/Operations/UpdateDataOperation.cs @@ -68,7 +68,7 @@ public virtual IEnumerable GenerateModificationCommands([Ca KeyValues.GetLength(0) == Values.GetLength(0), $"The number of key values doesn't match the number of values (${KeyValues.GetLength(0)})"); - var table = model?.FindTable(Table, Schema); + var table = model?.GetRelationalModel().FindTable(Table, Schema); var keyProperties = table != null ? MigrationsModelDiffer.GetMappedProperties(table, KeyColumns) : null; diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 759460ef53d..8f49c7f97da 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -586,6 +586,44 @@ public static string UnknownProjectionMappingType([CanBeNull] object type1, [Can public static string NestedAmbientTransactionError => GetString("NestedAmbientTransactionError"); + /// + /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different concurrency token configuration. + /// + public static string DuplicateColumnNameConcurrencyTokenMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table) + => string.Format( + GetString("DuplicateColumnNameConcurrencyTokenMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table)), + entityType1, property1, entityType2, property2, columnName, table); + + /// + /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different fixed length configuration. + /// + public static string DuplicateColumnNameFixedLengthMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table) + => string.Format( + GetString("DuplicateColumnNameFixedLengthMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table)), + entityType1, property1, entityType2, property2, columnName, table); + + /// + /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different max lengths ('{maxLength1}' and '{maxLength2}'). + /// + public static string DuplicateColumnNameMaxLengthMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table, [CanBeNull] object maxLength1, [CanBeNull] object maxLength2) + => string.Format( + GetString("DuplicateColumnNameMaxLengthMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table), nameof(maxLength1), nameof(maxLength2)), + entityType1, property1, entityType2, property2, columnName, table, maxLength1, maxLength2); + + /// + /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different unicode configuration + /// + public static string DuplicateColumnNameUnicodenessMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table) + => string.Format( + GetString("DuplicateColumnNameUnicodenessMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table)), + entityType1, property1, entityType2, property2, columnName, table); + + /// + /// The database model hasn't been initialized. The model needs to be finalized before the database model can be accessed. + /// + public static string DatabaseModelMissing + => GetString("DatabaseModelMissing"); + /// /// Cannot set custom translation on the DbFunction '{function}' since it returns IQueryable type. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 2c3ef28ddfa..7063f58e440 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -500,6 +500,21 @@ Root ambient transaction was completed before the nested transaction. The more nested transactions should be completed first. + + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different concurrency token configuration. + + + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different fixed length configuration. + + + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured to use different max lengths ('{maxLength1}' and '{maxLength2}'). + + + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different unicode configuration + + + The database model hasn't been initialized. The model needs to be finalized before the database model can be accessed. + Cannot set custom translation on the DbFunction '{function}' since it returns IQueryable type. diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index 81d1c786c9f..d8485f4dfbd 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -771,8 +771,7 @@ public virtual SelectExpression Select(IEntityType entityType, string sql, Expre var discriminatorAdded = AddDiscriminatorCondition(selectExpression, entityType); var linkingFks = table.GetInternalForeignKeys(entityType); - if (linkingFks != null - && linkingFks.Any()) + if (linkingFks.Any()) { if (!discriminatorAdded) { @@ -940,7 +939,7 @@ bool HasSiblings(IEntityType entityType) { var otherSelectExpression = new SelectExpression(entityType); - var sameTable = table.GetInternalForeignKeys(referencingFk.DeclaringEntityType) != null; + var sameTable = table.GetInternalForeignKeys(referencingFk.DeclaringEntityType).Any(); AddInnerJoin( otherSelectExpression, referencingFk, sameTable ? table : null, diff --git a/src/EFCore.Relational/Storage/RelationalDatabaseCreator.cs b/src/EFCore.Relational/Storage/RelationalDatabaseCreator.cs index 95d866e645a..59a93ec3963 100644 --- a/src/EFCore.Relational/Storage/RelationalDatabaseCreator.cs +++ b/src/EFCore.Relational/Storage/RelationalDatabaseCreator.cs @@ -140,7 +140,7 @@ public virtual Task CreateTablesAsync(CancellationToken cancellationToken = defa /// The generated commands. protected virtual IReadOnlyList GetCreateTablesCommands() => Dependencies.MigrationsSqlGenerator.Generate( - Dependencies.ModelDiffer.GetDifferences(null, Dependencies.Model), Dependencies.Model); + Dependencies.ModelDiffer.GetDifferences(null, Dependencies.Model.GetRelationalModel()), Dependencies.Model); /// /// Determines whether the database contains any tables. No attempt is made to determine if diff --git a/src/EFCore.Relational/Update/Internal/SharedTableEntryMap.cs b/src/EFCore.Relational/Update/Internal/SharedTableEntryMap.cs index 11b15754a55..c9606345b11 100644 --- a/src/EFCore.Relational/Update/Internal/SharedTableEntryMap.cs +++ b/src/EFCore.Relational/Update/Internal/SharedTableEntryMap.cs @@ -54,7 +54,7 @@ public class SharedTableEntryMap [NotNull] IUpdateAdapter updateAdapter) { var sharedTablesMap = new Dictionary<(string, string), SharedTableEntryMapFactory>(); - foreach (var table in model.GetTables()) + foreach (var table in model.GetRelationalModel().Tables) { if (!table.IsSplit) { @@ -118,17 +118,12 @@ public virtual TValue GetOrAddValue([NotNull] IUpdateEntry entry) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool IsMainEntityType([NotNull] IEntityType entityType) => _table.GetInternalForeignKeys(entityType) == null; + public virtual bool IsMainEntityType([NotNull] IEntityType entityType) => !_table.GetInternalForeignKeys(entityType).Any(); private IUpdateEntry GetMainEntry(IUpdateEntry entry) { var entityType = entry.EntityType.GetRootType(); var foreignKeys = _table.GetInternalForeignKeys(entityType); - if (foreignKeys == null) - { - return entry; - } - foreach (var foreignKey in foreignKeys) { var principalEntry = _updateAdapter.FindPrincipal(entry, foreignKey); @@ -159,7 +154,7 @@ private void AddAllDependentsInclusive(IUpdateEntry entry, List en { entries.Add(entry); var foreignKeys = _table.GetReferencingInternalForeignKeys(entry.EntityType); - if (foreignKeys == null) + if (!foreignKeys.Any()) { return; } @@ -184,9 +179,9 @@ public EntryComparer(ITable table) } public int Compare(IUpdateEntry x, IUpdateEntry y) - => _table.GetInternalForeignKeys(x.EntityType) == null + => !_table.GetInternalForeignKeys(x.EntityType).Any() ? -1 - : _table.GetInternalForeignKeys(y.EntityType) == null + : !_table.GetInternalForeignKeys(y.EntityType).Any() ? 1 : StringComparer.Ordinal.Compare(x.EntityType.Name, y.EntityType.Name); } diff --git a/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs index 10760758cc8..3e6946a42b3 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -12,6 +13,7 @@ using Microsoft.EntityFrameworkCore.SqlServer.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Migrations.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; @@ -57,7 +59,7 @@ public static IServiceCollection AddEntityFrameworkSqlServer([NotNull] this ISer .TryAdd(p => p.GetService()) .TryAdd() .TryAdd() - .TryAdd() + .TryAdd() .TryAdd() .TryAdd() .TryAdd(p => p.GetService()) diff --git a/src/EFCore.SqlServer/Migrations/Internal/SqlServerMigrationsAnnotationProvider.cs b/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs similarity index 70% rename from src/EFCore.SqlServer/Migrations/Internal/SqlServerMigrationsAnnotationProvider.cs rename to src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs index 6e661f9afe2..e2dc0b645ae 100644 --- a/src/EFCore.SqlServer/Migrations/Internal/SqlServerMigrationsAnnotationProvider.cs +++ b/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs @@ -8,11 +8,9 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; using Microsoft.Extensions.DependencyInjection; -namespace Microsoft.EntityFrameworkCore.SqlServer.Migrations.Internal +namespace Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal { /// /// @@ -27,13 +25,13 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Migrations.Internal /// This service cannot depend on services registered as . /// /// - public class SqlServerMigrationsAnnotationProvider : MigrationsAnnotationProvider + public class SqlServerAnnotationProvider : RelationalAnnotationProvider { /// /// Initializes a new instance of this class. /// /// Parameter object containing dependencies for this service. - public SqlServerMigrationsAnnotationProvider([NotNull] MigrationsAnnotationProviderDependencies dependencies) + public SqlServerAnnotationProvider([NotNull] RelationalAnnotationProviderDependencies dependencies) : base(dependencies) { } @@ -44,11 +42,11 @@ public SqlServerMigrationsAnnotationProvider([NotNull] MigrationsAnnotationProvi /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override IEnumerable For(IModel model) + public override IEnumerable For(IRelationalModel model) { - var maxSize = model.GetDatabaseMaxSize(); - var serviceTier = model.GetServiceTierSql(); - var performanceLevel = model.GetPerformanceLevelSql(); + var maxSize = model.Model.GetDatabaseMaxSize(); + var serviceTier = model.Model.GetServiceTierSql(); + var performanceLevel = model.Model.GetPerformanceLevelSql(); if (maxSize != null || serviceTier != null || performanceLevel != null) @@ -81,9 +79,11 @@ public override IEnumerable For(IModel model) yield return new Annotation(SqlServerAnnotationNames.EditionOptions, options.ToString()); } - foreach (var annotationForRemove in ForRemove(model)) + if (model.Tables.Any(t => t.IsMigratable && (t[SqlServerAnnotationNames.MemoryOptimized] as bool? == true))) { - yield return annotationForRemove; + yield return new Annotation( + SqlServerAnnotationNames.MemoryOptimized, + true); } } @@ -93,7 +93,16 @@ public override IEnumerable For(IModel model) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override IEnumerable For(IEntityType entityType) => ForRemove(entityType); + public override IEnumerable For(ITable table) + { + // Model validation ensures that these facets are the same on all mapped entity types + if (table.EntityTypeMappings.First().EntityType.IsMemoryOptimized()) + { + yield return new Annotation( + SqlServerAnnotationNames.MemoryOptimized, + true); + } + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -101,8 +110,11 @@ public override IEnumerable For(IModel model) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override IEnumerable For(IKey key) + public override IEnumerable For(IUniqueConstraint constraint) { + // Model validation ensures that these facets are the same on all mapped keys + var key = constraint.MappedKeys.First(); + var isClustered = key.IsClustered(); if (isClustered.HasValue) { @@ -118,9 +130,12 @@ public override IEnumerable For(IKey key) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override IEnumerable For(IIndex index) + public override IEnumerable For(ITableIndex index) { - var isClustered = index.IsClustered(); + // Model validation ensures that these facets are the same on all mapped indexes + var modelIndex = index.MappedIndexes.First(); + + var isClustered = modelIndex.IsClustered(); if (isClustered.HasValue) { yield return new Annotation( @@ -128,11 +143,11 @@ public override IEnumerable For(IIndex index) isClustered.Value); } - var includeProperties = index.GetIncludeProperties(); + var includeProperties = modelIndex.GetIncludeProperties(); if (includeProperties != null) { var includeColumns = (IReadOnlyList)includeProperties - .Select(p => index.DeclaringEntityType.FindProperty(p).GetColumnName()) + .Select(p => modelIndex.DeclaringEntityType.FindProperty(p).GetColumnName()) .ToArray(); yield return new Annotation( @@ -140,7 +155,7 @@ public override IEnumerable For(IIndex index) includeColumns); } - var isOnline = index.IsCreatedOnline(); + var isOnline = modelIndex.IsCreatedOnline(); if (isOnline.HasValue) { yield return new Annotation( @@ -155,9 +170,11 @@ public override IEnumerable For(IIndex index) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override IEnumerable For(IProperty property) + public override IEnumerable For(IColumn column) { - if (property.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.IdentityColumn) + var property = column.PropertyMappings.Select(m => m.Property) + .FirstOrDefault(p => p.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.IdentityColumn); + if (property != null) { var seed = property.GetIdentitySeed(); var increment = property.GetIdentityIncrement(); @@ -167,40 +184,5 @@ public override IEnumerable For(IProperty property) string.Format(CultureInfo.InvariantCulture, "{0}, {1}", seed ?? 1, increment ?? 1)); } } - - /// - /// 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 IEnumerable ForRemove(IModel model) - { - if (model.GetEntityTypes().Any(e => e.BaseType == null && e.IsMemoryOptimized())) - { - yield return new Annotation( - SqlServerAnnotationNames.MemoryOptimized, - true); - } - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public override IEnumerable ForRemove(IEntityType entityType) - { - if (IsMemoryOptimized(entityType)) - { - yield return new Annotation( - SqlServerAnnotationNames.MemoryOptimized, - true); - } - } - - private static bool IsMemoryOptimized(IEntityType entityType) - => entityType.GetAllBaseTypesInclusive().Any(t => t.IsMemoryOptimized()); } } diff --git a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs index 7bf31a915f2..23c3bfe718b 100644 --- a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs +++ b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs @@ -35,8 +35,6 @@ namespace Microsoft.EntityFrameworkCore.Migrations /// public class SqlServerMigrationsSqlGenerator : MigrationsSqlGenerator { - private readonly IMigrationsAnnotationProvider _migrationsAnnotations; - private IReadOnlyList _operations; private int _variableCounter; @@ -47,8 +45,10 @@ public class SqlServerMigrationsSqlGenerator : MigrationsSqlGenerator /// Provider-specific Migrations annotations to use. public SqlServerMigrationsSqlGenerator( [NotNull] MigrationsSqlGeneratorDependencies dependencies, - [NotNull] IMigrationsAnnotationProvider migrationsAnnotations) - : base(dependencies) => _migrationsAnnotations = migrationsAnnotations; + [NotNull] IRelationalAnnotationProvider migrationsAnnotations) + : base(dependencies) + { + } /// /// Generates commands from a list of operations. @@ -212,8 +212,9 @@ protected override void Generate(MigrationOperation operation, IModel model, Mig Check.NotNull(operation, nameof(operation)); Check.NotNull(builder, nameof(builder)); - IEnumerable indexesToRebuild = null; - var property = FindProperty(model, operation.Schema, operation.Table, operation.Name); + IEnumerable indexesToRebuild = null; + var column = model?.GetRelationalModel().FindTable(operation.Table, operation.Schema) + ?.Columns.FirstOrDefault(c => c.Name == operation.Name); if (operation.ComputedColumnSql != null) { @@ -223,9 +224,9 @@ protected override void Generate(MigrationOperation operation, IModel model, Mig Table = operation.Table, Name = operation.Name }; - if (property != null) + if (column != null) { - dropColumnOperation.AddAnnotations(_migrationsAnnotations.ForRemove(property)); + dropColumnOperation.AddAnnotations(column.GetAnnotations()); } var addColumnOperation = new AddColumnOperation @@ -247,7 +248,7 @@ protected override void Generate(MigrationOperation operation, IModel model, Mig addColumnOperation.AddAnnotations(operation.GetAnnotations()); // TODO: Use a column rebuild instead - indexesToRebuild = GetIndexesToRebuild(property, operation).ToList(); + indexesToRebuild = GetIndexesToRebuild(column, operation).ToList(); DropIndexes(indexesToRebuild, builder); Generate(dropColumnOperation, model, builder, terminate: false); builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); @@ -286,7 +287,7 @@ protected override void Generate(MigrationOperation operation, IModel model, Mig if (narrowed) { - indexesToRebuild = GetIndexesToRebuild(property, operation).ToList(); + indexesToRebuild = GetIndexesToRebuild(column, operation).ToList(); DropIndexes(indexesToRebuild, builder); } @@ -1605,43 +1606,41 @@ protected override void ForeignKeyAction(ReferentialAction referentialAction, Mi } /// - /// Gets the list of indexes that need to be rebuilt when the given property is changing. + /// Gets the list of indexes that need to be rebuilt when the given column is changing. /// - /// The property. + /// The column. /// The operation which may require a rebuild. /// The list of indexes affected. - protected virtual IEnumerable GetIndexesToRebuild( - [CanBeNull] IProperty property, + protected virtual IEnumerable GetIndexesToRebuild( + [CanBeNull] IColumn column, [NotNull] MigrationOperation currentOperation) { Check.NotNull(currentOperation, nameof(currentOperation)); - if (property == null) + if (column == null) { yield break; } + var table = column.Table; var createIndexOperations = _operations.SkipWhile(o => o != currentOperation).Skip(1) - .OfType().ToList(); - foreach (var index in property.DeclaringEntityType.GetIndexes() - .Concat(property.DeclaringEntityType.GetDerivedTypes().SelectMany(et => et.GetDeclaredIndexes()))) + .OfType().Where(o => o.Table == table.Name && o.Schema == table.Schema).ToList(); + foreach (var index in table.Indexes) { - var indexName = index.GetName(); + var indexName = index.Name; if (createIndexOperations.Any(o => o.Name == indexName)) { continue; } - if (index.Properties.Any(p => p == property)) + if (index.Columns.Any(c => c == column)) { yield return index; } - else if (index.GetIncludeProperties() is IReadOnlyList includeProperties) + else if (index[SqlServerAnnotationNames.Include] is IReadOnlyList includeColumns + && includeColumns.Contains(column.Name)) { - if (includeProperties.Contains(property.Name)) - { - yield return index; - } + yield return index; } } } @@ -1652,7 +1651,7 @@ protected override void ForeignKeyAction(ReferentialAction referentialAction, Mi /// The indexes to drop. /// The command builder to use to build the commands. protected virtual void DropIndexes( - [NotNull] IEnumerable indexes, + [NotNull] IEnumerable indexes, [NotNull] MigrationCommandListBuilder builder) { Check.NotNull(indexes, nameof(indexes)); @@ -1660,15 +1659,16 @@ protected override void ForeignKeyAction(ReferentialAction referentialAction, Mi foreach (var index in indexes) { + var table = index.Table; var operation = new DropIndexOperation { - Schema = index.DeclaringEntityType.GetSchema(), - Table = index.DeclaringEntityType.GetTableName(), - Name = index.GetName() + Schema = table.Schema, + Table = table.Name, + Name = index.Name }; - operation.AddAnnotations(_migrationsAnnotations.ForRemove(index)); + operation.AddAnnotations(index.GetAnnotations()); - Generate(operation, index.DeclaringEntityType.Model, builder, terminate: false); + Generate(operation, table.Model.Model, builder, terminate: false); builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); } } @@ -1679,7 +1679,7 @@ protected override void ForeignKeyAction(ReferentialAction referentialAction, Mi /// The indexes to create. /// The command builder to use to build the commands. protected virtual void CreateIndexes( - [NotNull] IEnumerable indexes, + [NotNull] IEnumerable indexes, [NotNull] MigrationCommandListBuilder builder) { Check.NotNull(indexes, nameof(indexes)); @@ -1687,18 +1687,19 @@ protected override void ForeignKeyAction(ReferentialAction referentialAction, Mi foreach (var index in indexes) { + var table = index.Table; var operation = new CreateIndexOperation { IsUnique = index.IsUnique, - Name = index.GetName(), - Schema = index.DeclaringEntityType.GetSchema(), - Table = index.DeclaringEntityType.GetTableName(), - Columns = index.Properties.Select(p => p.GetColumnName()).ToArray(), - Filter = index.GetFilter() + Name = index.Name, + Schema = table.Schema, + Table = table.Name, + Columns = index.Columns.Select(c => c.Name).ToArray(), + Filter = index.Filter }; - operation.AddAnnotations(_migrationsAnnotations.For(index)); + operation.AddAnnotations(index.GetAnnotations()); - Generate(operation, index.DeclaringEntityType.Model, builder, terminate: false); + Generate(operation, table.Model.Model, builder, terminate: false); builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); } } @@ -1835,7 +1836,7 @@ private string IntegerConstant(long value) private bool IsMemoryOptimized(Annotatable annotatable, IModel model, string schema, string tableName) => annotatable[SqlServerAnnotationNames.MemoryOptimized] as bool? - ?? FindEntityTypes(model, schema, tableName)?.Any(t => t.IsMemoryOptimized()) == true; + ?? model?.GetRelationalModel().FindTable(tableName, schema)?[SqlServerAnnotationNames.MemoryOptimized] as bool? == true; private static bool IsMemoryOptimized(Annotatable annotatable) => annotatable[SqlServerAnnotationNames.MemoryOptimized] as bool? == true; diff --git a/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs b/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs index f3b405259ff..c93ff1f6db2 100644 --- a/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs +++ b/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -12,6 +13,7 @@ using Microsoft.EntityFrameworkCore.Sqlite.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Internal; +using Microsoft.EntityFrameworkCore.Sqlite.Metadata.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Migrations.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal; @@ -54,7 +56,7 @@ public static IServiceCollection AddEntityFrameworkSqlite([NotNull] this IServic .TryAdd>() .TryAdd() .TryAdd() - .TryAdd() + .TryAdd() .TryAdd() .TryAdd() .TryAdd() diff --git a/src/EFCore.Sqlite.Core/Internal/SqliteModelValidator.cs b/src/EFCore.Sqlite.Core/Internal/SqliteModelValidator.cs index 1ac22a3d41e..5e53564852c 100644 --- a/src/EFCore.Sqlite.Core/Internal/SqliteModelValidator.cs +++ b/src/EFCore.Sqlite.Core/Internal/SqliteModelValidator.cs @@ -1,6 +1,7 @@ // 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.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; @@ -81,5 +82,38 @@ public override void Validate(IModel model, IDiagnosticsLogger /// @@ -27,7 +25,7 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Migrations.Internal /// This service cannot depend on services registered as . /// /// - public class SqliteMigrationsAnnotationProvider : MigrationsAnnotationProvider + public class SqliteAnnotationProvider : RelationalAnnotationProvider { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -35,7 +33,7 @@ public class SqliteMigrationsAnnotationProvider : MigrationsAnnotationProvider /// 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 SqliteMigrationsAnnotationProvider([NotNull] MigrationsAnnotationProviderDependencies dependencies) + public SqliteAnnotationProvider([NotNull] RelationalAnnotationProviderDependencies dependencies) : base(dependencies) { } @@ -46,10 +44,10 @@ public SqliteMigrationsAnnotationProvider([NotNull] MigrationsAnnotationProvider /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override IEnumerable For(IModel model) + public override IEnumerable For(IRelationalModel model) { - if (model.GetEntityTypes().SelectMany(t => t.GetProperties()).Any( - p => SqliteTypeMappingSource.IsSpatialiteType(p.GetColumnType()))) + if (model.Tables.SelectMany(t => t.Columns).Any( + c => SqliteTypeMappingSource.IsSpatialiteType(c.Type))) { yield return new Annotation(SqliteAnnotationNames.InitSpatialMetaData, true); } @@ -61,8 +59,10 @@ public override IEnumerable For(IModel model) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override IEnumerable For(IProperty property) + public override IEnumerable For(IColumn column) { + // Model validation ensures that these facets are the same on all mapped properties + var property = column.PropertyMappings.First().Property; if (property.ValueGenerated == ValueGenerated.OnAdd && property.ClrType.UnwrapNullableType().IsInteger() && !HasConverter(property)) @@ -84,6 +84,6 @@ public override IEnumerable For(IProperty property) } private static bool HasConverter(IProperty property) - => property.FindTypeMapping()?.Converter != null; + => (property.GetValueConverter() ?? property.FindTypeMapping()?.Converter) != null; } } diff --git a/src/EFCore.Sqlite.Core/Migrations/SqliteMigrationsSqlGenerator.cs b/src/EFCore.Sqlite.Core/Migrations/SqliteMigrationsSqlGenerator.cs index 9cd7c7db9f7..500f9973e05 100644 --- a/src/EFCore.Sqlite.Core/Migrations/SqliteMigrationsSqlGenerator.cs +++ b/src/EFCore.Sqlite.Core/Migrations/SqliteMigrationsSqlGenerator.cs @@ -31,8 +31,6 @@ namespace Microsoft.EntityFrameworkCore.Migrations /// public class SqliteMigrationsSqlGenerator : MigrationsSqlGenerator { - private readonly IMigrationsAnnotationProvider _migrationsAnnotations; - /// /// Creates a new instance. /// @@ -40,9 +38,10 @@ public class SqliteMigrationsSqlGenerator : MigrationsSqlGenerator /// Provider-specific Migrations annotations to use. public SqliteMigrationsSqlGenerator( [NotNull] MigrationsSqlGeneratorDependencies dependencies, - [NotNull] IMigrationsAnnotationProvider migrationsAnnotations) + [NotNull] IRelationalAnnotationProvider migrationsAnnotations) : base(dependencies) - => _migrationsAnnotations = migrationsAnnotations; + { + } /// /// Generates commands from a list of operations. @@ -229,9 +228,8 @@ protected override void Generate(AddColumnOperation operation, IModel model, Mig /// The command builder to use to build the commands. protected override void Generate(RenameIndexOperation operation, IModel model, MigrationCommandListBuilder builder) { - var index = FindEntityTypes(model, operation.Schema, operation.Table) - ?.SelectMany(t => t.GetDeclaredIndexes()).Where(i => i.GetName() == operation.NewName) - .FirstOrDefault(); + var index = model.GetRelationalModel().FindTable(operation.Table, operation.Schema) + ?.Indexes.FirstOrDefault(i => i.Name == operation.NewName); if (index == null) { throw new NotSupportedException( @@ -244,7 +242,7 @@ protected override void Generate(RenameIndexOperation operation, IModel model, M Table = operation.Table, Name = operation.Name }; - dropOperation.AddAnnotations(_migrationsAnnotations.ForRemove(index)); + dropOperation.AddAnnotations(index.GetAnnotations()); var createOperation = new CreateIndexOperation { @@ -252,10 +250,10 @@ protected override void Generate(RenameIndexOperation operation, IModel model, M Name = operation.NewName, Schema = operation.Schema, Table = operation.Table, - Columns = index.Properties.Select(p => p.GetColumnName()).ToArray(), - Filter = index.GetFilter() + Columns = index.Columns.Select(p => p.Name).ToArray(), + Filter = index.Filter }; - createOperation.AddAnnotations(_migrationsAnnotations.For(index)); + createOperation.AddAnnotations(index.GetAnnotations()); Generate(dropOperation, model, builder, terminate: false); builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); diff --git a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs index 35a560eb8f6..7f59f5cd3a0 100644 --- a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs +++ b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs @@ -55,6 +55,22 @@ public static string OrderByNotSupported([CanBeNull] object type) GetString("OrderByNotSupported", nameof(type)), type); + /// + /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different geometric dimensions. + /// + public static string DuplicateColumnNameDimensionMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table) + => string.Format( + GetString("DuplicateColumnNameDimensionMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table)), + entityType1, property1, entityType2, property2, columnName, table); + + /// + /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different SRIDs. + /// + public static string DuplicateColumnNameSridMismatch([CanBeNull] object entityType1, [CanBeNull] object property1, [CanBeNull] object entityType2, [CanBeNull] object property2, [CanBeNull] object columnName, [CanBeNull] object table) + => string.Format( + GetString("DuplicateColumnNameSridMismatch", nameof(entityType1), nameof(property1), nameof(entityType2), nameof(property2), nameof(columnName), nameof(table)), + entityType1, property1, entityType2, property2, columnName, table); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx index 777bfdc6e01..0e50c2f2c09 100644 --- a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx +++ b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx @@ -184,4 +184,10 @@ A connection of an unexpected type ({type}) is being used. The SQL functions prefixed with 'ef_' could not be created automatically. Manually define them if you encounter errors while querying. Warning SqliteEventId.UnexpectedConnectionTypeWarning string + + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different geometric dimensions. + + + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different SRIDs. + \ No newline at end of file diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs index 75708e07c22..d0a198fb4d7 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs @@ -9,6 +9,7 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal { @@ -583,6 +584,18 @@ public virtual T Run([NotNull] Func func, [CanBeNull] ref IConventionForei return result; } + /// + /// 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. + /// + [Conditional("DEBUG")] + public virtual void AssertNoScope() + { + Check.DebugAssert(_scope == _immediateConventionScope, "Expected no active convention scopes"); + } + private sealed class ConventionBatch : IConventionBatch { private readonly ConventionDispatcher _dispatcher; diff --git a/src/EFCore/Metadata/Internal/Model.cs b/src/EFCore/Metadata/Internal/Model.cs index 11c8f3f3299..310e31cfdaf 100644 --- a/src/EFCore/Metadata/Internal/Model.cs +++ b/src/EFCore/Metadata/Internal/Model.cs @@ -903,6 +903,7 @@ public virtual string RemoveOwned([NotNull] Type clrType) /// public virtual IModel FinalizeModel() { + ConventionDispatcher.AssertNoScope(); IModel finalizedModel = ConventionDispatcher.OnModelFinalizing(Builder)?.Metadata; if (finalizedModel != null) { diff --git a/src/EFCore/Metadata/Internal/PropertyExtensions.cs b/src/EFCore/Metadata/Internal/PropertyExtensions.cs index 701120bbba6..0e35fdf078d 100644 --- a/src/EFCore/Metadata/Internal/PropertyExtensions.cs +++ b/src/EFCore/Metadata/Internal/PropertyExtensions.cs @@ -280,7 +280,7 @@ private static void AddPrincipals(IProperty property, List visited) if (property.GetMaxLength() != null) { - builder.Append(" MaxLength").Append(property.GetMaxLength()); + builder.Append(" MaxLength(").Append(property.GetMaxLength()).Append(")"); } if (property.IsUnicode() == false) diff --git a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs index a77e576929c..f64fdff9690 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs @@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; @@ -67,8 +67,8 @@ var migrationAssembly var historyRepository = new MockHistoryRepository(); var services = RelationalTestHelpers.Instance.CreateContextServices(); - IModel model = new Model(); - model = new RelationalModelConvention().ProcessModelFinalized(model); + var model = new Model(); + model[RelationalAnnotationNames.RelationalModel] = new RelationalModel(model); return new MigrationsScaffolder( new MigrationsScaffolderDependencies( @@ -79,7 +79,6 @@ var migrationAssembly new TestRelationalTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()), - new MigrationsAnnotationProvider(new MigrationsAnnotationProviderDependencies()), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService()), @@ -101,7 +100,7 @@ var migrationAssembly historyRepository, reporter, new MockProvider(), - new SnapshotModelProcessor(reporter), + new SnapshotModelProcessor(reporter, services.GetRequiredService()), new Migrator( migrationAssembly, historyRepository, diff --git a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs index bb076cce177..93d967c2ac0 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs @@ -10,8 +10,10 @@ using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Extensions.DependencyInjection; using Xunit; // ReSharper disable ClassNeverInstantiated.Local @@ -51,7 +53,7 @@ public void Updates_provider_annotations_on_model() var reporter = new TestOperationReporter(); - new SnapshotModelProcessor(reporter).Process(model); + new SnapshotModelProcessor(reporter, NullConventionSetBuilder.Instance).Process(model); AssertAnnotations(model); AssertAnnotations(entityType); @@ -77,7 +79,7 @@ public void Warns_for_conflicting_annotations() var reporter = new TestOperationReporter(); - new SnapshotModelProcessor(reporter).Process(model); + new SnapshotModelProcessor(reporter, NullConventionSetBuilder.Instance).Process(model); Assert.Equal("warn: " + DesignStrings.MultipleAnnotationConflict("DefaultSchema"), reporter.Messages.Single()); Assert.Equal(2, model.GetAnnotations().Count()); @@ -98,7 +100,7 @@ public void Warns_for_conflicting_annotations_one_relational() var reporter = new TestOperationReporter(); - new SnapshotModelProcessor(reporter).Process(model); + new SnapshotModelProcessor(reporter, NullConventionSetBuilder.Instance).Process(model); Assert.Equal("warn: " + DesignStrings.MultipleAnnotationConflict("DefaultSchema"), reporter.Messages.Single()); Assert.Equal(2, model.GetAnnotations().Count()); @@ -119,7 +121,7 @@ public void Does_not_warn_for_duplicate_non_conflicting_annotations() var reporter = new TestOperationReporter(); - new SnapshotModelProcessor(reporter).Process(model); + new SnapshotModelProcessor(reporter, NullConventionSetBuilder.Instance).Process(model); Assert.Empty(reporter.Messages); @@ -138,7 +140,7 @@ public void Does_not_process_non_v1_models() var reporter = new TestOperationReporter(); - new SnapshotModelProcessor(reporter).Process(model); + new SnapshotModelProcessor(reporter, NullConventionSetBuilder.Instance).Process(model); Assert.Empty(reporter.Messages); @@ -164,7 +166,7 @@ public void Sets_owned_type_keys() }); var reporter = new TestOperationReporter(); - new SnapshotModelProcessor(reporter).Process(model); + new SnapshotModelProcessor(reporter, NullConventionSetBuilder.Instance).Process(model); Assert.Empty(reporter.Messages); Assert.Equal( @@ -184,9 +186,11 @@ public void Can_diff_against_older_ownership_model(Type snapshotType) var differ = context.GetService(); var snapshot = (ModelSnapshot)Activator.CreateInstance(snapshotType); var reporter = new TestOperationReporter(); - var processor = new SnapshotModelProcessor(reporter); + var setBuilder = SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService(); + var processor = new SnapshotModelProcessor(reporter, setBuilder); + var model = processor.Process(snapshot.Model); - var differences = differ.GetDifferences(processor.Process(snapshot.Model), context.Model); + var differences = differ.GetDifferences(model.GetRelationalModel(), context.Model.GetRelationalModel()); Assert.Empty(differences); } @@ -201,9 +205,11 @@ public void Can_diff_against_older_sequence_model(Type snapshotType) var differ = context.GetService(); var snapshot = (ModelSnapshot)Activator.CreateInstance(snapshotType); var reporter = new TestOperationReporter(); - var processor = new SnapshotModelProcessor(reporter); + var setBuilder = SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService(); + var processor = new SnapshotModelProcessor(reporter, setBuilder); + var model = processor.Process(snapshot.Model); - var differences = differ.GetDifferences(processor.Process(snapshot.Model), context.Model); + var differences = differ.GetDifferences(model.GetRelationalModel(), context.Model.GetRelationalModel()); Assert.Empty(differences); } @@ -225,10 +231,9 @@ private void AssertAnnotations(IMutableAnnotatable element) { foreach (var annotationName in GetAnnotationNames() .Where(a => a != RelationalAnnotationNames.MaxIdentifierLength - && a != RelationalAnnotationNames.Tables + && a != RelationalAnnotationNames.RelationalModel && a != RelationalAnnotationNames.TableMappings && a != RelationalAnnotationNames.TableColumnMappings - && a != RelationalAnnotationNames.Views && a != RelationalAnnotationNames.ViewMappings && a != RelationalAnnotationNames.ViewColumnMappings && a != RelationalAnnotationNames.ForeignKeyMappings @@ -249,6 +254,21 @@ private static IEnumerable GetAnnotationNames() .Where(p => p.Name != nameof(RelationalAnnotationNames.Prefix)) .Select(p => (string)p.GetValue(null)); + + private class NullConventionSetBuilder : IConventionSetBuilder + { + private NullConventionSetBuilder() + { + } + + public ConventionSet CreateConventionSet() + { + return new ConventionSet(); + } + + public static NullConventionSetBuilder Instance { get; } = new NullConventionSetBuilder(); + } + private class Blog { public int Id { get; set; } diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 3f12beaa03a..064100e1470 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -243,7 +243,7 @@ public virtual void Model_annotations_are_stored_in_snapshot() .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), o => { - Assert.Equal(6, o.GetAnnotations().Count()); + Assert.Equal(7, o.GetAnnotations().Count()); Assert.Equal("AnnotationValue", o["AnnotationName"]); }); } @@ -266,7 +266,7 @@ public virtual void Model_default_schema_annotation_is_stored_in_snapshot_as_flu .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), o => { - Assert.Equal(4, o.GetAnnotations().Count()); + Assert.Equal(5, o.GetAnnotations().Count()); Assert.Equal("AnnotationValue", o["AnnotationName"]); Assert.Equal("DefaultSchema", o[RelationalAnnotationNames.DefaultSchema]); }); @@ -355,7 +355,7 @@ public virtual void Sequence_is_stored_in_snapshot_as_fluent_api() .IsCyclic();"), o => { - Assert.Equal(3, o.GetAnnotations().Count()); + Assert.Equal(4, o.GetAnnotations().Count()); }); } @@ -2062,7 +2062,7 @@ public virtual void Property_multiple_annotations_are_stored_in_snapshot() o => { var property = o.GetEntityTypes().First().FindProperty("AlternateId"); - Assert.Equal(4, property.GetAnnotations().Count()); + Assert.Equal(5, property.GetAnnotations().Count()); Assert.Equal("AnnotationValue", property["AnnotationName"]); Assert.Equal("CName", property["Relational:ColumnName"]); Assert.Equal("int", property["Relational:ColumnType"]); @@ -3727,7 +3727,7 @@ protected void Test(IModel model, string expectedCode, Action as var services = RelationalTestHelpers.Instance.CreateContextServices(); - var processor = new SnapshotModelProcessor(new TestOperationReporter()); + var processor = new SnapshotModelProcessor(new TestOperationReporter(), services.GetService()); var modelFromSnapshot = processor.Process(builder.Model); assert(modelFromSnapshot, model); diff --git a/test/EFCore.Relational.Specification.Tests/MigrationsInfrastructureTestBase.cs b/test/EFCore.Relational.Specification.Tests/MigrationsInfrastructureTestBase.cs index 53c8cd41715..da02facc01a 100644 --- a/test/EFCore.Relational.Specification.Tests/MigrationsInfrastructureTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/MigrationsInfrastructureTestBase.cs @@ -288,14 +288,17 @@ public virtual void Can_get_active_provider() protected virtual void DiffSnapshot(ModelSnapshot snapshot, DbContext context) { var dependencies = context.GetService(); + var relationalDependencies = context.GetService(); var typeMappingConvention = new TypeMappingConvention(dependencies); typeMappingConvention.ProcessModelFinalizing(((IConventionModel)snapshot.Model).Builder, null); - var relationalModelConvention = new RelationalModelConvention(); + var relationalModelConvention = new RelationalModelConvention(dependencies, relationalDependencies); var sourceModel = relationalModelConvention.ProcessModelFinalized(snapshot.Model); var modelDiffer = context.GetService(); - var operations = modelDiffer.GetDifferences(((IMutableModel)sourceModel).FinalizeModel(), context.Model); + var operations = modelDiffer.GetDifferences( + ((IMutableModel)sourceModel).FinalizeModel().GetRelationalModel(), + context.Model.GetRelationalModel()); Assert.Equal(0, operations.Count); } diff --git a/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs b/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs index 50009ee91f6..aef5e7017bf 100644 --- a/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs @@ -1348,7 +1348,7 @@ protected virtual IRelationalTypeMappingSource TypeMappingSource var serviceProvider = ((IInfrastructure)context).Instance; var modelDiffer = serviceProvider.GetRequiredService(); - var operations = modelDiffer.GetDifferences(sourceModel, targetModel); + var operations = modelDiffer.GetDifferences(sourceModel.GetRelationalModel(), targetModel.GetRelationalModel()); return Test(sourceModel, targetModel, operations, asserter); } @@ -1397,7 +1397,7 @@ protected virtual IRelationalTypeMappingSource TypeMappingSource using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) { await migrationsCommandExecutor.ExecuteNonQueryAsync( - migrationsSqlGenerator.Generate(modelDiffer.GetDifferences(null, sourceModel), sourceModel), + migrationsSqlGenerator.Generate(modelDiffer.GetDifferences(null, sourceModel.GetRelationalModel()), sourceModel), connection); } @@ -1446,8 +1446,10 @@ protected virtual ModelBuilder CreateConventionlessModelBuilder(bool sensitiveDa var conventionSet = new ConventionSet(); var dependencies = Fixture.TestHelpers.CreateContextServices().GetRequiredService(); + var relationalDependencies = Fixture.TestHelpers.CreateContextServices() + .GetRequiredService(); conventionSet.ModelFinalizingConventions.Add(new TypeMappingConvention(dependencies)); - conventionSet.ModelFinalizedConventions.Add(new RelationalModelConvention()); + conventionSet.ModelFinalizedConventions.Add(new RelationalModelConvention(dependencies, relationalDependencies)); return new ModelBuilder(conventionSet); } diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 0a58a859ccf..77d48833d9f 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -355,7 +355,7 @@ public virtual void Detects_duplicate_column_names() var modelBuilder = CreateConventionalModelBuilder(); GenerateMapping(modelBuilder.Entity().Property(b => b.Id).HasColumnName("Name").Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Name).HasColumnName("Name").Metadata); + GenerateMapping(modelBuilder.Entity().Property(d => d.Name).HasColumnName("Name").IsRequired().Metadata); VerifyError( RelationalStrings.DuplicateColumnNameDataTypeMismatch( @@ -370,7 +370,7 @@ public virtual void Detects_duplicate_columns_in_derived_types_with_different_ty var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); - GenerateMapping(modelBuilder.Entity().Property(c => c.Type).HasColumnName("Type").Metadata); + GenerateMapping(modelBuilder.Entity().Property(c => c.Type).HasColumnName("Type").IsRequired().Metadata); GenerateMapping(modelBuilder.Entity().Property(d => d.Type).HasColumnName("Type").Metadata); VerifyError( @@ -389,9 +389,51 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe GenerateMapping(modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").HasMaxLength(15).Metadata); VerifyError( - RelationalStrings.DuplicateColumnNameDataTypeMismatch( - nameof(Cat), nameof(Cat.Breed), nameof(Dog), nameof(Dog.Breed), nameof(Cat.Breed), nameof(Animal), "just_string(30)", - "just_string(15)"), modelBuilder.Model); + RelationalStrings.DuplicateColumnNameMaxLengthMismatch( + nameof(Cat), nameof(Cat.Breed), nameof(Dog), nameof(Dog.Breed), nameof(Cat.Breed), nameof(Animal), "30", + "15"), modelBuilder.Model); + } + + [ConditionalFact] + public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_IsUnicode() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(); + + GenerateMapping(modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").IsUnicode().Metadata); + GenerateMapping(modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").Metadata); + + VerifyError( + RelationalStrings.DuplicateColumnNameUnicodenessMismatch( + nameof(Cat), nameof(Cat.Breed), nameof(Dog), nameof(Dog.Breed), nameof(Cat.Breed), nameof(Animal)), modelBuilder.Model); + } + + [ConditionalFact] + public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_IsFixedLength() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(); + + GenerateMapping(modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").IsFixedLength().Metadata); + GenerateMapping(modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").Metadata); + + VerifyError( + RelationalStrings.DuplicateColumnNameFixedLengthMismatch( + nameof(Cat), nameof(Cat.Breed), nameof(Dog), nameof(Dog.Breed), nameof(Cat.Breed), nameof(Animal)), modelBuilder.Model); + } + + [ConditionalFact] + public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_IsConcurrencyToken() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(); + + GenerateMapping(modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").IsConcurrencyToken().Metadata); + GenerateMapping(modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").Metadata); + + VerifyError( + RelationalStrings.DuplicateColumnNameConcurrencyTokenMismatch( + nameof(Cat), nameof(Cat.Breed), nameof(Dog), nameof(Dog.Breed), nameof(Cat.Breed), nameof(Animal)), modelBuilder.Model); } [ConditionalFact] @@ -485,7 +527,7 @@ public virtual void Passes_for_compatible_duplicate_column_names_within_hierarch eb.Property(c => c.Breed).HasMaxLength(25); eb.Property(c => c.Breed).HasColumnName("BreedName"); eb.Property(c => c.Breed).HasDefaultValue("None"); - eb.Property("Selected").HasDefaultValue(false); + eb.Property("Selected").HasDefaultValue("false").HasConversion(); }); Validate(modelBuilder.Model); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index 399b7a8fd46..8b4bf0407a8 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -21,10 +21,10 @@ public void Can_use_relational_model_with_tables(bool useExplicitMapping) { var model = CreateTestModel(mapToTables: useExplicitMapping); - Assert.Equal(6, model.GetEntityTypes().Count()); - Assert.Equal(2, model.GetTables().Count()); - Assert.Empty(model.GetViews()); - Assert.True(model.GetEntityTypes().All(et => !et.GetViewMappings().Any())); + Assert.Equal(6, model.Model.GetEntityTypes().Count()); + Assert.Equal(2, model.Tables.Count()); + Assert.Empty(model.Views); + Assert.True(model.Model.GetEntityTypes().All(et => !et.GetViewMappings().Any())); AssertTables(model); } @@ -34,10 +34,10 @@ public void Can_use_relational_model_with_views() { var model = CreateTestModel(mapToTables: false, mapToViews: true); - Assert.Equal(6, model.GetEntityTypes().Count()); - Assert.Equal(2, model.GetViews().Count()); - Assert.Empty(model.GetTables()); - Assert.True(model.GetEntityTypes().All(et => !et.GetTableMappings().Any())); + Assert.Equal(6, model.Model.GetEntityTypes().Count()); + Assert.Equal(2, model.Views.Count()); + Assert.Empty(model.Tables); + Assert.True(model.Model.GetEntityTypes().All(et => !et.GetTableMappings().Any())); AssertViews(model); } @@ -47,17 +47,17 @@ public void Can_use_relational_model_with_views_and_tables() { var model = CreateTestModel(mapToTables: true, mapToViews: true); - Assert.Equal(6, model.GetEntityTypes().Count()); - Assert.Equal(2, model.GetTables().Count()); - Assert.Equal(2, model.GetViews().Count()); + Assert.Equal(6, model.Model.GetEntityTypes().Count()); + Assert.Equal(2, model.Tables.Count()); + Assert.Equal(2, model.Views.Count()); AssertTables(model); AssertViews(model); } - private static void AssertViews(IModel model) + private static void AssertViews(IRelationalModel model) { - var orderType = model.FindEntityType(typeof(Order)); + var orderType = model.Model.FindEntityType(typeof(Order)); var orderMapping = orderType.GetViewMappings().Single(); Assert.Same(orderType.GetViewMappings(), orderType.GetViewOrTableMappings()); Assert.True(orderMapping.IncludesDerivedTypes); @@ -108,18 +108,18 @@ private static void AssertViews(IModel model) Assert.False(orderDateColumn.IsNullable); Assert.Same(ordersView, orderDateColumn.Table); - var customerType = model.FindEntityType(typeof(Customer)); + var customerType = model.Model.FindEntityType(typeof(Customer)); var customerView = customerType.GetViewMappings().Single().Table; Assert.Equal("CustomerView", customerView.Name); Assert.Equal("viewSchema", customerView.Schema); - var specialCustomerType = model.FindEntityType(typeof(SpecialCustomer)); + var specialCustomerType = model.Model.FindEntityType(typeof(SpecialCustomer)); Assert.Same(customerView, specialCustomerType.GetViewMappings().Single().Table); } - private static void AssertTables(IModel model) + private static void AssertTables(IRelationalModel model) { - var orderType = model.FindEntityType(typeof(Order)); + var orderType = model.Model.FindEntityType(typeof(Order)); var orderMapping = orderType.GetTableMappings().Single(); Assert.True(orderMapping.IncludesDerivedTypes); Assert.Equal( @@ -207,16 +207,16 @@ private static void AssertTables(IModel model) Assert.False(orderDateColumn.IsNullable); Assert.Same(ordersTable, orderDateColumn.Table); - var customerType = model.FindEntityType(typeof(Customer)); + var customerType = model.Model.FindEntityType(typeof(Customer)); var customerTable = customerType.GetTableMappings().Single().Table; Assert.Equal("Customer", customerTable.Name); Assert.Empty(customerTable.ForeignKeyConstraints); - var specialCustomerType = model.FindEntityType(typeof(SpecialCustomer)); + var specialCustomerType = model.Model.FindEntityType(typeof(SpecialCustomer)); Assert.Same(customerTable, specialCustomerType.GetTableMappings().Single().Table); } - private IModel CreateTestModel(bool mapToTables = false, bool mapToViews = false) + private IRelationalModel CreateTestModel(bool mapToTables = false, bool mapToViews = false) { var modelBuilder = CreateConventionModelBuilder(); @@ -257,7 +257,7 @@ private IModel CreateTestModel(bool mapToTables = false, bool mapToViews = false } }); - return modelBuilder.FinalizeModel(); + return modelBuilder.FinalizeModel().GetRelationalModel(); } protected virtual ModelBuilder CreateConventionModelBuilder() => RelationalTestHelpers.Instance.CreateConventionBuilder(); diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index 9acaf943be8..2fe54a628fb 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -6393,14 +6393,6 @@ public void Update_AK_seed_value_with_a_referencing_foreign_key() upOps => Assert.Collection( upOps, o => - { - var operation = Assert.IsType(o); - Assert.Equal("Table", operation.Table); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(43, v)); - }, - o => { var operation = Assert.IsType(o); Assert.Equal("ReferencedTable", operation.Table); @@ -6416,27 +6408,10 @@ public void Update_AK_seed_value_with_a_referencing_foreign_key() operation.Values, v => Assert.Equal(42, v), v => Assert.Equal(4343, v)); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("Table", operation.Table); - AssertMultidimensionalArray( - operation.Values, - v => Assert.Equal(43, v), - v => Assert.Equal(42, v)); }), downOps => Assert.Collection( downOps, o => - { - var operation = Assert.IsType(o); - Assert.Equal("Table", operation.Table); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(43, v)); - }, - o => { var operation = Assert.IsType(o); Assert.Equal("ReferencedTable", operation.Table); @@ -6452,15 +6427,6 @@ public void Update_AK_seed_value_with_a_referencing_foreign_key() operation.Values, v => Assert.Equal(42, v), v => Assert.Equal(4242, v)); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("Table", operation.Table); - AssertMultidimensionalArray( - operation.Values, - v => Assert.Equal(43, v), - v => Assert.Equal(42, v)); })); } @@ -7548,18 +7514,18 @@ private void SeedData_with_navigation_properties(Action buildTarge o => { var m = Assert.IsType(o); - Assert.Equal("Post", m.Table); + Assert.Equal("Blog", m.Table); AssertMultidimensionalArray( m.KeyValues, - v => Assert.Equal(416, v)); + v => Assert.Equal(38, v)); }, o => { var m = Assert.IsType(o); - Assert.Equal("Post", m.Table); + Assert.Equal("Blog", m.Table); AssertMultidimensionalArray( m.KeyValues, - v => Assert.Equal(545, v)); + v => Assert.Equal(316, v)); }, o => { @@ -7570,22 +7536,6 @@ private void SeedData_with_navigation_properties(Action buildTarge v => Assert.Equal(546, v)); }, o => - { - var m = Assert.IsType(o); - Assert.Equal("Blog", m.Table); - AssertMultidimensionalArray( - m.KeyValues, - v => Assert.Equal(38, v)); - }, - o => - { - var m = Assert.IsType(o); - Assert.Equal("Blog", m.Table); - AssertMultidimensionalArray( - m.KeyValues, - v => Assert.Equal(316, v)); - }, - o => { var m = Assert.IsType(o); Assert.Equal("Blog", m.Table); @@ -7598,21 +7548,13 @@ private void SeedData_with_navigation_properties(Action buildTarge }, o => { - var m = Assert.IsType(o); + var m = Assert.IsType(o); Assert.Equal("Post", m.Table); AssertMultidimensionalArray( - m.Values, - v => Assert.Equal(416, v), - v => Assert.Equal(316, v), - v => Assert.Equal("Post To Non-existent BlogId", v)); - }, - o => - { - var m = Assert.IsType(o); - Assert.Equal("Post", m.Table); + m.KeyValues, + v => Assert.Equal(545, v)); AssertMultidimensionalArray( m.Values, - v => Assert.Equal(545, v), v => Assert.Equal(32, v), v => Assert.Equal("Original Title", v)); }, @@ -8167,7 +8109,7 @@ public void Rename_property_on_subtype_and_add_similar_to_base() }); } - [ConditionalFact] + [ConditionalFact(Skip = "#15339")] public void Owner_pk_properties_appear_before_owned_pk_which_preserves_annotations() { Execute( diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs index b2b3be0e577..768fda4e9d7 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations.Operations; @@ -80,14 +81,14 @@ public abstract class MigrationsModelDifferTestBase var modelDiffer = CreateModelDiffer(targetOptionsBuilder.Options); - var operationsUp = modelDiffer.GetDifferences(sourceModel, targetModel); + var operationsUp = modelDiffer.GetDifferences(sourceModel.GetRelationalModel(), targetModel.GetRelationalModel()); assertActionUp(operationsUp); if (assertActionDown != null) { modelDiffer = CreateModelDiffer(sourceOptionsBuilder.Options); - var operationsDown = modelDiffer.GetDifferences(targetModel, sourceModel); + var operationsDown = modelDiffer.GetDifferences(targetModel.GetRelationalModel(), sourceModel.GetRelationalModel()); assertActionDown(operationsDown); } } @@ -147,8 +148,11 @@ private ConventionSet CreateEmptyConventionSet() { var conventions = new ConventionSet(); var conventionSetDependencies = TestHelpers.CreateContextServices().GetRequiredService(); + var relationalConventionSetDependencies = new RelationalConventionSetBuilderDependencies( + new RelationalAnnotationProvider( + new RelationalAnnotationProviderDependencies())); conventions.ModelFinalizingConventions.Add(new TypeMappingConvention(conventionSetDependencies)); - conventions.ModelFinalizedConventions.Add(new RelationalModelConvention()); + conventions.ModelFinalizedConventions.Add(new RelationalModelConvention(conventionSetDependencies, relationalConventionSetDependencies)); return conventions; } @@ -159,8 +163,6 @@ protected virtual MigrationsModelDiffer CreateModelDiffer(DbContextOptions optio new TestRelationalTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()), - new MigrationsAnnotationProvider( - new MigrationsAnnotationProviderDependencies()), ctx.GetService(), ctx.GetService(), ctx.GetService()); diff --git a/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTestBase.cs b/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTestBase.cs index c79fb418b04..cc85cdb802c 100644 --- a/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTestBase.cs +++ b/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTestBase.cs @@ -3,6 +3,9 @@ using System; using System.Linq; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations.Operations; using Microsoft.EntityFrameworkCore.TestUtilities; @@ -30,7 +33,7 @@ public virtual void AddColumnOperation_without_column_type() [ConditionalFact] public virtual void AddColumnOperation_with_unicode_overridden() => Generate( - modelBuilder => modelBuilder.Entity("Person").Property("Name").IsUnicode(false), + modelBuilder => modelBuilder.Entity().Property("Name").IsUnicode(false), new AddColumnOperation { Table = "Person", @@ -69,7 +72,7 @@ public virtual void AddColumnOperation_with_fixed_length_no_model() [ConditionalFact] public virtual void AddColumnOperation_with_maxLength_overridden() => Generate( - modelBuilder => modelBuilder.Entity("Person").Property("Name").HasMaxLength(30), + modelBuilder => modelBuilder.Entity().Property("Name").HasMaxLength(30), new AddColumnOperation { Table = "Person", @@ -154,8 +157,15 @@ protected virtual void Generate(Action buildAction, params Migrati modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); buildAction(modelBuilder); - var batch = TestHelpers.CreateContextServices().GetRequiredService() - .Generate(operation, modelBuilder.Model); + var services = TestHelpers.CreateContextServices(); + + IModel model = modelBuilder.Model; + var conventionSet = services.GetRequiredService().CreateConventionSet(); + var relationalModelConvention = conventionSet.ModelFinalizedConventions.OfType().First(); + model = relationalModelConvention.ProcessModelFinalized((IConventionModel)model); + model = ((IMutableModel)model).FinalizeModel(); + + var batch = services.GetRequiredService().Generate(operation, modelBuilder.Model); Sql = string.Join( "GO" + EOL + EOL, @@ -164,5 +174,11 @@ protected virtual void Generate(Action buildAction, params Migrati protected void AssertSql(string expected) => Assert.Equal(expected, Sql, ignoreLineEndingDifferences: true); + + protected class Person + { + public int Id { get; set; } + public string Name { get; set; } + } } } diff --git a/test/EFCore.Relational.Tests/Migrations/MigrationsAnnotationProviderDependenciesTest.cs b/test/EFCore.Relational.Tests/Migrations/MigrationsAnnotationProviderDependenciesTest.cs index d21d68684c5..07ff1c06804 100644 --- a/test/EFCore.Relational.Tests/Migrations/MigrationsAnnotationProviderDependenciesTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/MigrationsAnnotationProviderDependenciesTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -11,7 +12,7 @@ public class MigrationsAnnotationProviderDependenciesTest [ConditionalFact] public void Can_use_With_methods_to_clone_and_replace_service() { - RelationalTestHelpers.Instance.TestDependenciesClone(); + RelationalTestHelpers.Instance.TestDependenciesClone(); } } } diff --git a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs index 08ea5030ea8..2a4dee20bdf 100644 --- a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs +++ b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs @@ -24,7 +24,7 @@ public override void Detects_duplicate_column_names() var modelBuilder = CreateConventionalModelBuilder(); GenerateMapping(modelBuilder.Entity().Property(b => b.Id).HasColumnName("Name").Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Name).HasColumnName("Name").Metadata); + GenerateMapping(modelBuilder.Entity().Property(d => d.Name).IsRequired().HasColumnName("Name").Metadata); VerifyError( RelationalStrings.DuplicateColumnNameDataTypeMismatch( @@ -33,20 +33,6 @@ public override void Detects_duplicate_column_names() modelBuilder.Model); } - public override void Detects_duplicate_columns_in_derived_types_with_different_types() - { - var modelBuilder = CreateConventionalModelBuilder(); - modelBuilder.Entity(); - - GenerateMapping(modelBuilder.Entity().Property(c => c.Type).HasColumnName("Type").Metadata); - GenerateMapping(modelBuilder.Entity().Property(c => c.Type).HasColumnName("Type").Metadata); - - VerifyError( - RelationalStrings.DuplicateColumnNameDataTypeMismatch( - typeof(Cat).Name, "Type", typeof(Dog).Name, "Type", "Type", nameof(Animal), "nvarchar(max)", "int"), - modelBuilder.Model); - } - public override void Detects_incompatible_shared_columns_with_shared_table() { var modelBuilder = CreateConventionalModelBuilder(); @@ -62,34 +48,6 @@ public override void Detects_incompatible_shared_columns_with_shared_table() nameof(A), nameof(A.P0), nameof(B), nameof(B.P0), nameof(B.P0), "Table", "someInt", "int"), modelBuilder.Model); } - public override void Detects_duplicate_column_names_within_hierarchy_with_different_MaxLength() - { - var modelBuilder = CreateConventionalModelBuilder(); - modelBuilder.Entity(); - GenerateMapping(modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasMaxLength(30).Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").HasMaxLength(15).Metadata); - - VerifyError( - RelationalStrings.DuplicateColumnNameDataTypeMismatch( - nameof(Cat), nameof(Cat.Breed), nameof(Dog), nameof(Dog.Breed), nameof(Cat.Breed), nameof(Animal), "nvarchar(30)", - "nvarchar(15)"), modelBuilder.Model); - } - - [ConditionalFact] - public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_unicode() - { - var modelBuilder = CreateConventionalModelBuilder(); - modelBuilder.Entity(); - - GenerateMapping(modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").IsUnicode(false).Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").IsUnicode().Metadata); - - VerifyError( - RelationalStrings.DuplicateColumnNameDataTypeMismatch( - nameof(Cat), nameof(Cat.Breed), nameof(Dog), nameof(Dog.Breed), nameof(Cat.Breed), nameof(Animal), "varchar(max)", - "nvarchar(max)"), modelBuilder.Model); - } - [ConditionalFact] public virtual void Passes_for_duplicate_column_names_within_hierarchy_with_identity() { diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationSqlGeneratorTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationSqlGeneratorTest.cs index 5b7010ce925..a282980a365 100644 --- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationSqlGeneratorTest.cs +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationSqlGeneratorTest.cs @@ -76,7 +76,7 @@ public override void AddColumnOperation_with_unicode_overridden() public virtual void AddColumnOperation_with_rowversion_overridden() { Generate( - modelBuilder => modelBuilder.Entity("Person").Property("RowVersion"), + modelBuilder => modelBuilder.Entity().Property("RowVersion"), new AddColumnOperation { Table = "Person", @@ -154,8 +154,8 @@ public virtual void AlterColumnOperation_with_index_no_oldColumn() Generate( modelBuilder => modelBuilder .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.0.0-rtm") - .Entity( - "Person", x => + .Entity( + x => { x.Property("Name").HasMaxLength(30); x.HasIndex("Name"); @@ -187,8 +187,8 @@ public virtual void AlterColumnOperation_with_added_index() Generate( modelBuilder => modelBuilder .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") - .Entity( - "Person", x => + .Entity( + x => { x.Property("Name").HasMaxLength(30); x.HasIndex("Name"); diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsAnnotationProviderTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsAnnotationProviderTest.cs index 86639a0c2cf..8c95dd4e542 100644 --- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsAnnotationProviderTest.cs +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsAnnotationProviderTest.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Linq; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -12,22 +12,23 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Migrations.Internal public class SqlServerMigrationsAnnotationProviderTest { private readonly ModelBuilder _modelBuilder; - private readonly SqlServerMigrationsAnnotationProvider _annotations; + private readonly SqlServerAnnotationProvider _annotations; public SqlServerMigrationsAnnotationProviderTest() { _modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder( /*skipValidation: true*/); - _annotations = new SqlServerMigrationsAnnotationProvider(new MigrationsAnnotationProviderDependencies()); + _annotations = new SqlServerAnnotationProvider(new RelationalAnnotationProviderDependencies()); } [Fact] public void For_property_handles_identity_annotations() { - var property = _modelBuilder.Entity("Entity") + var property = _modelBuilder.Entity() .Property("Id").UseIdentityColumn(2, 3) .Metadata; + _modelBuilder.FinalizeModel(); - var migrationAnnotations = _annotations.For(property).ToList(); + var migrationAnnotations = _annotations.For(property.GetTableColumnMappings().Single().Column).ToList(); var identity = Assert.Single(migrationAnnotations, a => a.Name == SqlServerAnnotationNames.Identity); Assert.Equal("2, 3", identity.Value); @@ -41,7 +42,8 @@ public void Resolves_column_names_for_Index_with_included_properties() _modelBuilder.FinalizeModel(); Assert.Contains( - _annotations.For(index), a => a.Name == SqlServerAnnotationNames.Include && ((string[])a.Value).Contains("IncludedColumn")); + _annotations.For(index.GetMappedTableIndexes().Single()), + a => a.Name == SqlServerAnnotationNames.Include && ((string[])a.Value).Contains("IncludedColumn")); } private class Entity diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs index e0312a92823..12c708c6b2f 100644 --- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs @@ -5,10 +5,10 @@ using System.Reflection; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations.Internal; using Microsoft.EntityFrameworkCore.Migrations.Operations; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; -using Microsoft.EntityFrameworkCore.SqlServer.Migrations.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; @@ -827,18 +827,7 @@ public void Rebuild_index_when_changing_online_option() protected override TestHelpers TestHelpers => SqlServerTestHelpers.Instance; protected override MigrationsModelDiffer CreateModelDiffer(DbContextOptions options) - { - var ctx = TestHelpers.CreateContext(options); - return new MigrationsModelDiffer( - new SqlServerTypeMappingSource( - TestServiceFactory.Instance.Create(), - TestServiceFactory.Instance.Create()), - new SqlServerMigrationsAnnotationProvider( - new MigrationsAnnotationProviderDependencies()), - ctx.GetService(), - ctx.GetService(), - ctx.GetService()); - } + => (MigrationsModelDiffer)TestHelpers.CreateContext(options).GetService(); private bool? IsMemoryOptimized(Annotatable annotatable) => annotatable[SqlServerAnnotationNames.MemoryOptimized] as bool?; diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/ModelBuilderSqlServerTest.Other.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/ModelBuilderSqlServerTest.Other.cs index 7532a713775..9606a5f70cc 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/ModelBuilderSqlServerTest.Other.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/ModelBuilderSqlServerTest.Other.cs @@ -19,7 +19,7 @@ protected override DbContextOptions Configure() .UseSqlServer("Database = None") .Options; - protected override void RunThrowDifferPipeline(DbContext context) - => context.GetService().GetDifferences(null, context.Model); + protected override void RunThroughDifferPipeline(DbContext context) + => context.GetService().GetDifferences(null, context.Model.GetRelationalModel()); } } diff --git a/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs b/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs index 506cf60f2c1..029343b1c87 100644 --- a/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs +++ b/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore.Sqlite.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; +using NetTopologySuite.Geometries; using Xunit; // ReSharper disable InconsistentNaming @@ -20,7 +21,7 @@ public override void Detects_duplicate_column_names() var modelBuilder = CreateConventionalModelBuilder(); GenerateMapping(modelBuilder.Entity().Property(b => b.Id).HasColumnName("Name").Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Name).HasColumnName("Name").Metadata); + GenerateMapping(modelBuilder.Entity().Property(d => d.Name).IsRequired().HasColumnName("Name").Metadata); VerifyError( RelationalStrings.DuplicateColumnNameDataTypeMismatch( @@ -34,7 +35,7 @@ public override void Detects_duplicate_columns_in_derived_types_with_different_t var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); - GenerateMapping(modelBuilder.Entity().Property(c => c.Type).HasColumnName("Type").Metadata); + GenerateMapping(modelBuilder.Entity().Property(c => c.Type).IsRequired().HasColumnName("Type").Metadata); GenerateMapping(modelBuilder.Entity().Property(d => d.Type).HasColumnName("Type").Metadata); VerifyError( @@ -42,8 +43,32 @@ public override void Detects_duplicate_columns_in_derived_types_with_different_t typeof(Cat).Name, "Type", typeof(Dog).Name, "Type", "Type", nameof(Animal), "TEXT", "INTEGER"), modelBuilder.Model); } - public override void Detects_duplicate_column_names_within_hierarchy_with_different_MaxLength() + [ConditionalFact] + public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_srid() { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(); + + GenerateMapping(modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasSrid(30).Metadata); + GenerateMapping(modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").HasSrid(15).Metadata); + + VerifyError( + SqliteStrings.DuplicateColumnNameSridMismatch( + nameof(Cat), nameof(Cat.Breed), nameof(Dog), nameof(Dog.Breed), nameof(Cat.Breed), nameof(Animal)), modelBuilder.Model); + } + + [ConditionalFact] + public virtual void Detects_duplicate_column_names_within_hierarchy_with_different_dimension() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(); + + GenerateMapping(modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasGeometricDimension(Ordinates.M).Metadata); + GenerateMapping(modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").HasGeometricDimension(Ordinates.Z).Metadata); + + VerifyError( + SqliteStrings.DuplicateColumnNameDimensionMismatch( + nameof(Cat), nameof(Cat.Breed), nameof(Dog), nameof(Dog.Breed), nameof(Cat.Breed), nameof(Animal)), modelBuilder.Model); } public override void Detects_incompatible_shared_columns_with_shared_table() diff --git a/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs b/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs index 25774450efb..4e695f96f31 100644 --- a/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs +++ b/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs @@ -1,9 +1,10 @@ // 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 Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Sqlite.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Sqlite.Migrations.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -12,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations public class SqliteMigrationAnnotationProviderTest { private readonly ModelBuilder _modelBuilder; - private readonly SqliteMigrationsAnnotationProvider _provider; + private readonly SqliteAnnotationProvider _provider; private readonly Annotation _autoincrement = new Annotation(SqliteAnnotationNames.Autoincrement, true); @@ -20,7 +21,7 @@ public SqliteMigrationAnnotationProviderTest() { _modelBuilder = SqliteTestHelpers.Instance.CreateConventionBuilder(); - _provider = new SqliteMigrationsAnnotationProvider(new MigrationsAnnotationProviderDependencies()); + _provider = new SqliteAnnotationProvider(new RelationalAnnotationProviderDependencies()); } [ConditionalFact] @@ -29,7 +30,8 @@ public void Adds_Autoincrement_for_OnAdd_integer_property() var property = _modelBuilder.Entity().Property(e => e.IntProp).ValueGeneratedOnAdd().Metadata; _modelBuilder.FinalizeModel(); - Assert.Contains(_provider.For(property), a => a.Name == _autoincrement.Name && (bool)a.Value); + Assert.Contains(_provider.For(property.GetTableColumnMappings().Single().Column), + a => a.Name == _autoincrement.Name && (bool)a.Value); } [ConditionalFact] @@ -38,7 +40,8 @@ public void Does_not_add_Autoincrement_for_OnAddOrUpdate_integer_property() var property = _modelBuilder.Entity().Property(e => e.IntProp).ValueGeneratedOnAddOrUpdate().Metadata; _modelBuilder.FinalizeModel(); - Assert.DoesNotContain(_provider.For(property), a => a.Name == _autoincrement.Name); + Assert.DoesNotContain(_provider.For(property.GetTableColumnMappings().Single().Column), + a => a.Name == _autoincrement.Name); } [ConditionalFact] @@ -47,7 +50,8 @@ public void Does_not_add_Autoincrement_for_OnUpdate_integer_property() var property = _modelBuilder.Entity().Property(e => e.IntProp).ValueGeneratedOnUpdate().Metadata; _modelBuilder.FinalizeModel(); - Assert.DoesNotContain(_provider.For(property), a => a.Name == _autoincrement.Name); + Assert.DoesNotContain(_provider.For(property.GetTableColumnMappings().Single().Column), + a => a.Name == _autoincrement.Name); } [ConditionalFact] @@ -56,7 +60,8 @@ public void Does_not_add_Autoincrement_for_Never_value_generated_integer_propert var property = _modelBuilder.Entity().Property(e => e.IntProp).ValueGeneratedNever().Metadata; _modelBuilder.FinalizeModel(); - Assert.DoesNotContain(_provider.For(property), a => a.Name == _autoincrement.Name); + Assert.DoesNotContain(_provider.For(property.GetTableColumnMappings().Single().Column), + a => a.Name == _autoincrement.Name); } [ConditionalFact] @@ -65,7 +70,8 @@ public void Does_not_add_Autoincrement_for_default_integer_property() var property = _modelBuilder.Entity().Property(e => e.IntProp).Metadata; _modelBuilder.FinalizeModel(); - Assert.DoesNotContain(_provider.For(property), a => a.Name == _autoincrement.Name); + Assert.DoesNotContain(_provider.For(property.GetTableColumnMappings().Single().Column), + a => a.Name == _autoincrement.Name); } [ConditionalFact] @@ -74,7 +80,8 @@ public void Does_not_add_Autoincrement_for_non_integer_OnAdd_property() var property = _modelBuilder.Entity().Property(e => e.StringProp).ValueGeneratedOnAdd().Metadata; _modelBuilder.FinalizeModel(); - Assert.DoesNotContain(_provider.For(property), a => a.Name == _autoincrement.Name); + Assert.DoesNotContain(_provider.For(property.GetTableColumnMappings().Single().Column), + a => a.Name == _autoincrement.Name); } private class Entity diff --git a/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs b/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs index 4ea0f8a2b87..3e27cd3708b 100644 --- a/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs +++ b/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs @@ -40,6 +40,8 @@ public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() private class FakeSequence : ISequence { + public object this[string name] => throw new NotImplementedException(); + public string Name => "SequenceName"; public string Schema => throw new NotImplementedException(); public long StartValue => throw new NotImplementedException(); @@ -49,6 +51,16 @@ private class FakeSequence : ISequence public Type ClrType => throw new NotImplementedException(); public IModel Model => throw new NotImplementedException(); public bool IsCyclic => throw new NotImplementedException(); + + public IAnnotation FindAnnotation(string name) + { + throw new NotImplementedException(); + } + + public IEnumerable GetAnnotations() + { + throw new NotImplementedException(); + } } } } diff --git a/test/EFCore.Tests/ApiConsistencyTestBase.cs b/test/EFCore.Tests/ApiConsistencyTestBase.cs index e9511a48c60..6291a5a7234 100644 --- a/test/EFCore.Tests/ApiConsistencyTestBase.cs +++ b/test/EFCore.Tests/ApiConsistencyTestBase.cs @@ -83,6 +83,24 @@ private string ValidateMetadata(KeyValuePair types) return $"{mutableType.Name} should derive from {readonlyType.Name}"; } + if (typeof(IAnnotation) != readonlyType) + { + if (!typeof(IAnnotatable).IsAssignableFrom(readonlyType)) + { + return $"{mutableType.Name} should derive from IAnnotatable"; + } + + if (!typeof(IMutableAnnotatable).IsAssignableFrom(mutableType)) + { + return $"{mutableType.Name} should derive from IMutableAnnotatable"; + } + + if (!typeof(IConventionAnnotatable).IsAssignableFrom(conventionType)) + { + return $"{mutableType.Name} should derive from IConventionAnnotatable"; + } + } + if (conventionBuilderType != null) { var builderProperty = conventionType.GetProperty("Builder"); diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilder.Other.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilder.Other.cs index 55412435f72..5b9781931f4 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilder.Other.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilder.Other.cs @@ -276,7 +276,7 @@ protected virtual void Mapping_ignores_ignored_array() b => b.Entity().Ignore(e => e.One)); Assert.Null(context.Model.FindEntityType(typeof(OneDee)).FindProperty("One")); - RunThrowDifferPipeline(context); + RunThroughDifferPipeline(context); } [ConditionalFact] @@ -299,7 +299,7 @@ protected virtual void Mapping_ignores_ignored_two_dimensional_array() b => b.Entity().Ignore(e => e.Two)); Assert.Null(context.Model.FindEntityType(typeof(TwoDee)).FindProperty("Two")); - RunThrowDifferPipeline(context); + RunThroughDifferPipeline(context); } [ConditionalFact] @@ -322,7 +322,7 @@ protected virtual void Mapping_ignores_ignored_three_dimensional_array() b => b.Entity().Ignore(e => e.Three)); Assert.Null(context.Model.FindEntityType(typeof(ThreeDee)).FindProperty("Three")); - RunThrowDifferPipeline(context); + RunThroughDifferPipeline(context); } protected class CustomModelBuildingContext : DbContext @@ -339,7 +339,7 @@ protected internal override void OnModelCreating(ModelBuilder modelBuilder) => _builder(modelBuilder); } - protected virtual void RunThrowDifferPipeline(DbContext context) + protected virtual void RunThroughDifferPipeline(DbContext context) { }