diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs index cdd3def5a02..a7310420356 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs @@ -357,6 +357,7 @@ public static EntityTypeBuilder HasCheckConstraint( { if (constraint.Sql == sql) { + ((CheckConstraint)constraint).UpdateConfigurationSource(ConfigurationSource.Explicit); return entityTypeBuilder; } diff --git a/src/EFCore.Relational/Extensions/RelationalModelBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalModelBuilderExtensions.cs index 211bd1bb98c..1f1b622efa7 100644 --- a/src/EFCore.Relational/Extensions/RelationalModelBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalModelBuilderExtensions.cs @@ -309,7 +309,7 @@ public static IConventionDbFunctionBuilder HasDbFunction( var dbFunction = modelBuilder.Metadata.FindDbFunction(methodInfo); if (dbFunction == null) { - dbFunction = modelBuilder.Metadata.AddDbFunction(methodInfo); + dbFunction = modelBuilder.Metadata.AddDbFunction(methodInfo, fromDataAnnotation); } else { @@ -357,7 +357,7 @@ public static IConventionModelBuilder HasDefaultSchema( { if (modelBuilder.CanSetDefaultSchema(schema, fromDataAnnotation)) { - modelBuilder.Metadata.SetDefaultSchema(schema); + modelBuilder.Metadata.SetDefaultSchema(schema, fromDataAnnotation); return modelBuilder; } @@ -400,7 +400,7 @@ public static IConventionModelBuilder HasMaxIdentifierLength( { if (modelBuilder.CanSetMaxIdentifierLength(length, fromDataAnnotation)) { - modelBuilder.Metadata.SetMaxIdentifierLength(length); + modelBuilder.Metadata.SetMaxIdentifierLength(length, fromDataAnnotation); return modelBuilder; } diff --git a/src/EFCore.Relational/Extensions/RelationalOwnedNavigationBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalOwnedNavigationBuilderExtensions.cs index 6eed0078b5e..bdc637d7269 100644 --- a/src/EFCore.Relational/Extensions/RelationalOwnedNavigationBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalOwnedNavigationBuilderExtensions.cs @@ -2,7 +2,9 @@ // 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; using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; // ReSharper disable once CheckNamespace @@ -29,13 +31,14 @@ public static OwnedNavigationBuilder HasCheckConstraint( Check.NotEmpty(name, nameof(name)); Check.NullButNotEmpty(sql, nameof(sql)); - var entityType = ownedNavigationBuilder.Metadata.DeclaringEntityType; + var entityType = ownedNavigationBuilder.OwnedEntityType; var constraint = entityType.FindCheckConstraint(name); if (constraint != null) { if (constraint.Sql == sql) { + ((CheckConstraint)constraint).UpdateConfigurationSource(ConfigurationSource.Explicit); return ownedNavigationBuilder; } diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index e5f525a8734..b4ff89d78a9 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -295,16 +295,6 @@ public static IEnumerable GetTableColumnMappings([NotNull] this (IEnumerable)property[RelationalAnnotationNames.TableColumnMappings] ?? Enumerable.Empty(); - /// - /// Returns the view or table columns to which the property is mapped. - /// - /// The property. - /// The view or table columns to which the property is mapped. - public static IEnumerable GetViewOrTableColumnMappings([NotNull] this IProperty property) => - (IEnumerable)(property[RelationalAnnotationNames.ViewColumnMappings] - ?? property[RelationalAnnotationNames.TableColumnMappings]) - ?? Enumerable.Empty(); - /// /// Returns the view columns to which the property is mapped. /// @@ -314,6 +304,28 @@ public static IEnumerable GetViewColumnMappings([NotNull] th (IEnumerable)property[RelationalAnnotationNames.ViewColumnMappings] ?? Enumerable.Empty(); + /// + /// Returns the table column corresponding to this property if it's mapped to the given table. + /// + /// The property. + /// The target table name. + /// The target table schema. + /// The table column to which the property is mapped. + public static IColumn FindTableColumn([NotNull] this IProperty property, [NotNull] string tableName, [CanBeNull] string schema) + => property.GetTableColumnMappings().Select(m => m.Column) + .FirstOrDefault(c => c.Table.Name == tableName && c.Table.Schema == schema); + + /// + /// Returns the view column corresponding to this property if it's mapped to the given table. + /// + /// The property. + /// The target table name. + /// The target table schema. + /// The table column to which the property is mapped. + public static IViewColumn FindViewColumn([NotNull] this IProperty property, [NotNull] string viewName, [CanBeNull] string schema) + => property.GetViewColumnMappings().Select(m => m.Column) + .FirstOrDefault(c => c.View.Name == viewName && c.View.Schema == schema); + /// /// Returns the SQL expression that is used as the default value for the column this property is mapped to. /// diff --git a/src/EFCore.Relational/Metadata/Internal/Sequence.cs b/src/EFCore.Relational/Metadata/Internal/Sequence.cs index a48257f3067..e4b336119f3 100644 --- a/src/EFCore.Relational/Metadata/Internal/Sequence.cs +++ b/src/EFCore.Relational/Metadata/Internal/Sequence.cs @@ -108,9 +108,7 @@ public Sequence( _name = name; _schema = schema; _configurationSource = configurationSource; -#pragma warning disable EF1001 // Internal EF Core API usage. - Builder = new InternalSequenceBuilder(this, ((Model)model).Builder.ModelBuilder); -#pragma warning restore EF1001 // Internal EF Core API usage. + Builder = new InternalSequenceBuilder(this, ((IConventionModel)model).Builder); } /// @@ -137,9 +135,7 @@ public Sequence([NotNull] IModel model, [NotNull] string annotationName) _maxValue = data.MaxValue; _clrType = data.ClrType; _isCyclic = data.IsCyclic; -#pragma warning disable EF1001 // Internal EF Core API usage. - Builder = new InternalSequenceBuilder(this, ((Model)model).Builder.ModelBuilder); -#pragma warning restore EF1001 // Internal EF Core API usage. + Builder = new InternalSequenceBuilder(this, ((IConventionModel)model).Builder); } /// diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 006c7180c25..d955d282378 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -1728,8 +1728,7 @@ protected virtual void DiffData( } } - var targetKeys = target.EntityTypeMappings - .SelectMany(m => m.EntityType.GetDeclaredKeys()).Where(k => k.IsPrimaryKey()).ToList(); + var targetKeys = target.PrimaryKey?.MappedKeys.ToList() ?? new List(); var keyMapping = new Dictionary>>(); foreach (var sourceMapping in source.EntityTypeMappings) @@ -1792,7 +1791,6 @@ protected virtual void DiffData( if (!keyMapping.TryGetValue(sourceEntityType, out var targetKeyMap)) { - entryMapping.RecreateRow = true; continue; } @@ -2021,20 +2019,25 @@ protected virtual IEnumerable GetDataOperations([NotNull] Di foreach (var commandBatch in commandBatches) { InsertDataOperation batchInsertOperation = null; - foreach (var c in commandBatch.ModificationCommands) + foreach (var command in commandBatch.ModificationCommands) { - if (c.EntityState == EntityState.Added) + if (diffContext.FindDrop(model.FindTable(command.TableName, command.Schema)) != null) + { + continue; + } + + if (command.EntityState == EntityState.Added) { if (batchInsertOperation != null) { - if (batchInsertOperation.Table == c.TableName - && batchInsertOperation.Schema == c.Schema + if (batchInsertOperation.Table == command.TableName + && batchInsertOperation.Schema == command.Schema && batchInsertOperation.Columns.SequenceEqual( - c.ColumnModifications.Where(col => col.IsKey || col.IsWrite).Select(col => col.ColumnName))) + command.ColumnModifications.Where(col => col.IsKey || col.IsWrite).Select(col => col.ColumnName))) { batchInsertOperation.Values = AddToMultidimensionalArray( - c.ColumnModifications.Where(col => col.IsKey || col.IsWrite).Select(GetValue).ToList(), + command.ColumnModifications.Where(col => col.IsKey || col.IsWrite).Select(GetValue).ToList(), batchInsertOperation.Values); continue; } @@ -2044,15 +2047,15 @@ protected virtual IEnumerable GetDataOperations([NotNull] Di batchInsertOperation = new InsertDataOperation { - Schema = c.Schema, - Table = c.TableName, - Columns = c.ColumnModifications.Where(col => col.IsKey || col.IsWrite).Select(col => col.ColumnName) + Schema = command.Schema, + Table = command.TableName, + Columns = command.ColumnModifications.Where(col => col.IsKey || col.IsWrite).Select(col => col.ColumnName) .ToArray(), Values = ToMultidimensionalArray( - c.ColumnModifications.Where(col => col.IsKey || col.IsWrite).Select(GetValue).ToList()) + command.ColumnModifications.Where(col => col.IsKey || col.IsWrite).Select(GetValue).ToList()) }; } - else if (c.EntityState == EntityState.Modified) + else if (command.EntityState == EntityState.Modified) { if (batchInsertOperation != null) { @@ -2062,14 +2065,14 @@ protected virtual IEnumerable GetDataOperations([NotNull] Di yield return new UpdateDataOperation { - Schema = c.Schema, - Table = c.TableName, - KeyColumns = c.ColumnModifications.Where(col => col.IsKey).Select(col => col.ColumnName).ToArray(), + Schema = command.Schema, + Table = command.TableName, + KeyColumns = command.ColumnModifications.Where(col => col.IsKey).Select(col => col.ColumnName).ToArray(), KeyValues = ToMultidimensionalArray( - c.ColumnModifications.Where(col => col.IsKey).Select(GetValue).ToList()), - Columns = c.ColumnModifications.Where(col => col.IsWrite).Select(col => col.ColumnName).ToArray(), + command.ColumnModifications.Where(col => col.IsKey).Select(GetValue).ToList()), + Columns = command.ColumnModifications.Where(col => col.IsWrite).Select(col => col.ColumnName).ToArray(), Values = ToMultidimensionalArray( - c.ColumnModifications.Where(col => col.IsWrite).Select(GetValue).ToList()) + command.ColumnModifications.Where(col => col.IsWrite).Select(GetValue).ToList()) }; } else @@ -2080,17 +2083,14 @@ protected virtual IEnumerable GetDataOperations([NotNull] Di batchInsertOperation = null; } - if (diffContext.FindDrop(model.FindTable(c.TableName, c.Schema)) == null) + yield return new DeleteDataOperation { - yield return new DeleteDataOperation - { - Schema = c.Schema, - Table = c.TableName, - KeyColumns = c.ColumnModifications.Where(col => col.IsKey).Select(col => col.ColumnName).ToArray(), - KeyValues = ToMultidimensionalArray( - c.ColumnModifications.Where(col => col.IsKey).Select(GetValue).ToArray()) - }; - } + Schema = command.Schema, + Table = command.TableName, + KeyColumns = command.ColumnModifications.Where(col => col.IsKey).Select(col => col.ColumnName).ToArray(), + KeyValues = ToMultidimensionalArray( + command.ColumnModifications.Where(col => col.IsKey).Select(GetValue).ToArray()) + }; } } diff --git a/src/EFCore.Relational/Update/ColumnModification.cs b/src/EFCore.Relational/Update/ColumnModification.cs index 9651452c6ce..8ebd6ab8b29 100644 --- a/src/EFCore.Relational/Update/ColumnModification.cs +++ b/src/EFCore.Relational/Update/ColumnModification.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -37,25 +38,24 @@ public class ColumnModification /// /// The that represents the entity that is being modified. /// The property that maps to the column. + /// The column to be modified. /// A delegate for generating parameter names for the update SQL. /// Indicates whether or not a value must be read from the database for the column. /// Indicates whether or not a value must be written to the database for the column. /// Indicates whether or not the column part of a primary or alternate key. /// Indicates whether or not the column is used in the WHERE clause when updating. - /// Indicates whether or not the column is acting as an optimistic concurrency token. /// Indicates whether or not potentially sensitive data (e.g. database values) can be logged. public ColumnModification( [NotNull] IUpdateEntry entry, [NotNull] IProperty property, + [NotNull] IColumn column, [NotNull] Func generateParameterName, bool isRead, bool isWrite, bool isKey, bool isCondition, - bool isConcurrencyToken, bool sensitiveLoggingEnabled) - : this( - Check.NotNull(property, nameof(property)).GetColumnName(), + : this(Check.NotNull(column, nameof(column)).Name, originalValue: null, value: null, property: property, @@ -71,11 +71,45 @@ public ColumnModification( Check.NotNull(generateParameterName, nameof(generateParameterName)); Entry = entry; - IsConcurrencyToken = isConcurrencyToken; _generateParameterName = generateParameterName; _useParameters = true; } + /// + /// Creates a new instance. + /// + /// The that represents the entity that is being modified. + /// The property that maps to the column. + /// A delegate for generating parameter names for the update SQL. + /// Indicates whether or not a value must be read from the database for the column. + /// Indicates whether or not a value must be written to the database for the column. + /// Indicates whether or not the column part of a primary or alternate key. + /// Indicates whether or not the column is used in the WHERE clause when updating. + /// Indicates whether or not the column is acting as an optimistic concurrency token. + /// Indicates whether or not potentially sensitive data (e.g. database values) can be logged. + [Obsolete("Use the constructor with column")] + public ColumnModification( + [NotNull] IUpdateEntry entry, + [NotNull] IProperty property, + [NotNull] Func generateParameterName, + bool isRead, + bool isWrite, + bool isKey, + bool isCondition, + bool isConcurrencyToken, + bool sensitiveLoggingEnabled) + : this(entry, + property, + Check.NotNull(property, nameof(property)).GetTableColumnMappings().First().Column, + generateParameterName, + isRead: isRead, + isWrite: isWrite, + isKey: isKey, + isCondition: isCondition, + sensitiveLoggingEnabled: sensitiveLoggingEnabled) + { + } + /// /// Creates a new instance. /// @@ -115,6 +149,42 @@ public ColumnModification( _sensitiveLoggingEnabled = sensitiveLoggingEnabled; } + /// + /// Creates a new instance. + /// + /// The name of the column. + /// The original value of the property mapped to this column. + /// Gets or sets the current value of the property mapped to this column. + /// The property that maps to the column. + /// Indicates whether or not a value must be read from the database for the column. + /// Indicates whether or not a value must be written to the database for the column. + /// Indicates whether or not the column part of a primary or alternate key. + /// Indicates whether or not the column is used in the WHERE clause when updating. + /// Indicates whether or not potentially sensitive data (e.g. database values) can be logged. + [Obsolete("Use the constructor with columnType")] + public ColumnModification( + [NotNull] string columnName, + [CanBeNull] object originalValue, + [CanBeNull] object value, + [CanBeNull] IProperty property, + bool isRead, + bool isWrite, + bool isKey, + bool isCondition, + bool sensitiveLoggingEnabled) + : this(columnName, + originalValue: originalValue, + value: value, + property: property, + columnType: null, + isRead: isRead, + isWrite: isWrite, + isKey: isKey, + isCondition: isCondition, + sensitiveLoggingEnabled: sensitiveLoggingEnabled) + { + } + /// /// The that represents the entity that is being modified. /// @@ -140,9 +210,7 @@ public ColumnModification( /// public virtual bool IsCondition { get; } - /// - /// Indicates whether or not the column is acting as an optimistic concurrency token. - /// + [Obsolete] public virtual bool IsConcurrencyToken { get; } /// diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs index 03b22c6f298..dc8784bca6f 100644 --- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs +++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs @@ -475,33 +475,33 @@ private Dictionary> CreateKeyValuePred for (var i = 0; i < command.Entries.Count; i++) { var entry = command.Entries[i]; - // TODO: Perf: Consider only adding foreign keys defined on entity types involved in a modification foreach (var foreignKey in entry.EntityType.GetReferencingForeignKeys()) { - var keyValueIndexFactory = - _keyValueIndexFactorySource.GetKeyValueIndexFactory(foreignKey.PrincipalKey); - + var constraints = foreignKey.GetMappedConstraints() + .Where(c => c.PrincipalTable.Name == command.TableName && c.PrincipalTable.Schema == command.Schema); var candidateKeyValueColumnModifications = columnModifications.Where( - cm => - foreignKey.PrincipalKey.Properties.Contains(cm.Property) - && (cm.IsWrite || cm.IsRead)); + cm => (cm.IsWrite || cm.IsRead) && foreignKey.PrincipalKey.Properties.Contains(cm.Property)); - if (command.EntityState == EntityState.Added - || candidateKeyValueColumnModifications.Any()) + if (!constraints.Any() + || (command.EntityState == EntityState.Modified + && !candidateKeyValueColumnModifications.Any())) { - var principalKeyValue = - keyValueIndexFactory.CreatePrincipalKeyValue((InternalEntityEntry)entry, foreignKey); + continue; + } - if (principalKeyValue != null) - { - if (!predecessorsMap.TryGetValue(principalKeyValue, out var predecessorCommands)) - { - predecessorCommands = new List(); - predecessorsMap.Add(principalKeyValue, predecessorCommands); - } + var principalKeyValue = _keyValueIndexFactorySource + .GetKeyValueIndexFactory(foreignKey.PrincipalKey) + .CreatePrincipalKeyValue((InternalEntityEntry)entry, foreignKey); - predecessorCommands.Add(command); + if (principalKeyValue != null) + { + if (!predecessorsMap.TryGetValue(principalKeyValue, out var predecessorCommands)) + { + predecessorCommands = new List(); + predecessorsMap.Add(principalKeyValue, predecessorCommands); } + + predecessorCommands.Add(command); } } } @@ -514,29 +514,30 @@ private Dictionary> CreateKeyValuePred { foreach (var foreignKey in entry.EntityType.GetForeignKeys()) { - var keyValueIndexFactory = _keyValueIndexFactorySource.GetKeyValueIndexFactory(foreignKey.PrincipalKey); - - var currentForeignKey = foreignKey; + var constraints = foreignKey.GetMappedConstraints(); var foreignKeyValueColumnModifications = columnModifications.Where( - cm => - currentForeignKey.Properties.Contains(cm.Property) && (cm.IsWrite || cm.IsRead)); + cm => (cm.IsWrite || cm.IsRead) && foreignKey.Properties.Contains(cm.Property)); - if (command.EntityState == EntityState.Deleted - || foreignKeyValueColumnModifications.Any()) + if (!constraints.Any() + || (command.EntityState == EntityState.Modified + && !foreignKeyValueColumnModifications.Any())) { - var dependentKeyValue = - keyValueIndexFactory.CreateDependentKeyValueFromOriginalValues((InternalEntityEntry)entry, foreignKey); + continue; + } - if (dependentKeyValue != null) - { - if (!predecessorsMap.TryGetValue(dependentKeyValue, out var predecessorCommands)) - { - predecessorCommands = new List(); - predecessorsMap.Add(dependentKeyValue, predecessorCommands); - } + var dependentKeyValue = _keyValueIndexFactorySource + .GetKeyValueIndexFactory(foreignKey.PrincipalKey) + .CreateDependentKeyValueFromOriginalValues((InternalEntityEntry)entry, foreignKey); - predecessorCommands.Add(command); + if (dependentKeyValue != null) + { + if (!predecessorsMap.TryGetValue(dependentKeyValue, out var predecessorCommands)) + { + predecessorCommands = new List(); + predecessorsMap.Add(dependentKeyValue, predecessorCommands); } + + predecessorCommands.Add(command); } } } @@ -562,34 +563,49 @@ private void AddForeignKeyEdges( var entry = command.Entries[entryIndex]; foreach (var foreignKey in entry.EntityType.GetForeignKeys()) { - var keyValueIndexFactory = - _keyValueIndexFactorySource.GetKeyValueIndexFactory(foreignKey.PrincipalKey); - var dependentKeyValue = - keyValueIndexFactory.CreateDependentKeyValue((InternalEntityEntry)entry, foreignKey); + var constraints = foreignKey.GetMappedConstraints(); + + var foreignKeyValueColumnModifications = command.ColumnModifications.Where( + cm => (cm.IsWrite || cm.IsRead) && foreignKey.Properties.Contains(cm.Property)); + + if (!constraints.Any() + || (command.EntityState == EntityState.Modified + && !foreignKeyValueColumnModifications.Any())) + { + continue; + } + + var dependentKeyValue = _keyValueIndexFactorySource + .GetKeyValueIndexFactory(foreignKey.PrincipalKey) + .CreateDependentKeyValue((InternalEntityEntry)entry, foreignKey); if (dependentKeyValue == null) { continue; } AddMatchingPredecessorEdge( - predecessorsMap, dependentKeyValue, commandGraph, command, - foreignKey); + predecessorsMap, dependentKeyValue, commandGraph, command, foreignKey); } } break; case EntityState.Deleted: - // TODO: also examine modified entities here when principal key modification is supported // ReSharper disable once ForCanBeConvertedToForeach for (var entryIndex = 0; entryIndex < command.Entries.Count; entryIndex++) { var entry = command.Entries[entryIndex]; foreach (var foreignKey in entry.EntityType.GetReferencingForeignKeys()) { - var keyValueIndexFactory = - _keyValueIndexFactorySource.GetKeyValueIndexFactory(foreignKey.PrincipalKey); - var principalKeyValue = keyValueIndexFactory.CreatePrincipalKeyValueFromOriginalValues( - (InternalEntityEntry)entry, foreignKey); + var constraints = foreignKey.GetMappedConstraints() + .Where(c => c.PrincipalTable.Name == command.TableName && c.PrincipalTable.Schema == command.Schema); + if (!constraints.Any()) + { + continue; + } + + var principalKeyValue = _keyValueIndexFactorySource + .GetKeyValueIndexFactory(foreignKey.PrincipalKey) + .CreatePrincipalKeyValueFromOriginalValues((InternalEntityEntry)entry, foreignKey); if (principalKeyValue != null) { AddMatchingPredecessorEdge( @@ -636,11 +652,11 @@ private void AddUniqueValueEdges(Multigraph c for (var entryIndex = 0; entryIndex < command.Entries.Count; entryIndex++) { var entry = command.Entries[entryIndex]; - foreach (var index in entry.EntityType.GetIndexes().Where(i => i.IsUnique)) + foreach (var index in entry.EntityType.GetIndexes().Where(i => i.IsUnique && i.GetMappedTableIndexes().Any())) { if (command.EntityState != EntityState.Deleted) { - var indexColumnModifications = false; + var anyIndexColumnModifications = false; // ReSharper disable once ForCanBeConvertedToForeach // ReSharper disable once LoopCanBeConvertedToQuery for (var indexIndex = 0; indexIndex < command.ColumnModifications.Count; indexIndex++) @@ -649,12 +665,12 @@ private void AddUniqueValueEdges(Multigraph c if (index.Properties.Contains(cm.Property) && (cm.IsWrite || cm.IsRead)) { - indexColumnModifications = true; + anyIndexColumnModifications = true; break; } } - if (!indexColumnModifications) + if (!anyIndexColumnModifications) { continue; } @@ -691,24 +707,24 @@ private void AddUniqueValueEdges(Multigraph c { foreach (var entry in command.Entries) { - foreach (var index in entry.EntityType.GetIndexes().Where(i => i.IsUnique)) + foreach (var index in entry.EntityType.GetIndexes().Where(i => i.IsUnique && i.GetMappedTableIndexes().Any())) { - var indexColumnModifications = - command.ColumnModifications.Where( - cm => index.Properties.Contains(cm.Property) - && cm.IsWrite); + var indexColumnModifications = command.ColumnModifications + .Where(cm => index.Properties.Contains(cm.Property) && cm.IsWrite); - if (command.EntityState == EntityState.Added - || indexColumnModifications.Any()) + if (command.EntityState != EntityState.Added + && !indexColumnModifications.Any()) { - var valueFactory = index.GetNullableValueFactory(); - if (valueFactory.TryCreateFromCurrentValues(entry, out var indexValue) - && predecessorsMap.TryGetValue(index, out var predecessorCommands) - && predecessorCommands.TryGetValue(indexValue, out var predecessor) - && predecessor != command) - { - commandGraph.AddEdge(predecessor, command, index); - } + continue; + } + + var valueFactory = index.GetNullableValueFactory(); + if (valueFactory.TryCreateFromCurrentValues(entry, out var indexValue) + && predecessorsMap.TryGetValue(index, out var predecessorCommands) + && predecessorCommands.TryGetValue(indexValue, out var predecessor) + && predecessor != command) + { + commandGraph.AddEdge(predecessor, command, index); } } } diff --git a/src/EFCore.Relational/Update/ModificationCommand.cs b/src/EFCore.Relational/Update/ModificationCommand.cs index 4822cdd05df..76a76603f5a 100644 --- a/src/EFCore.Relational/Update/ModificationCommand.cs +++ b/src/EFCore.Relational/Update/ModificationCommand.cs @@ -254,12 +254,20 @@ private IReadOnlyList GenerateColumnModifications() foreach (var property in entry.EntityType.GetProperties()) { + var column = property.FindTableColumn(TableName, Schema); + if (column == null) + { + continue; + } + var isKey = property.IsPrimaryKey(); - var isConcurrencyToken = property.IsConcurrencyToken; - var isCondition = !adding && (isKey || isConcurrencyToken); + var isCondition = !adding && (isKey || property.IsConcurrencyToken); var readValue = entry.IsStoreGenerated(property); - var columnName = property.GetColumnName(); - var columnPropagator = sharedColumnMap?[columnName]; + ColumnValuePropagator columnPropagator = null; + if (sharedColumnMap != null) + { + columnPropagator = sharedColumnMap[column.Name]; + } var writeValue = false; if (!readValue) @@ -288,12 +296,12 @@ private IReadOnlyList GenerateColumnModifications() var columnModification = new ColumnModification( entry, property, + column, _generateParameterName, readValue, writeValue, isKey, isCondition, - isConcurrencyToken, _sensitiveLoggingEnabled); if (columnPropagator != null) @@ -316,11 +324,16 @@ private IReadOnlyList GenerateColumnModifications() return columnModifications; } - private static void InitializeSharedColumns(IUpdateEntry entry, bool updating, Dictionary columnMap) + private void InitializeSharedColumns(IUpdateEntry entry, bool updating, Dictionary columnMap) { foreach (var property in entry.EntityType.GetProperties()) { - var columnName = property.GetColumnName(); + var columnName = property.FindTableColumn(TableName, Schema)?.Name; + if (columnName == null) + { + continue; + } + if (!columnMap.TryGetValue(columnName, out var columnPropagator)) { columnPropagator = new ColumnValuePropagator(); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index a00793b83b8..f83a182e8a3 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -102,6 +102,7 @@ private static void AssertViews(IRelationalModel model) var orderDateColumn = orderDateMapping.Column; Assert.Same(orderDateColumn, ordersView.FindColumn("OrderDateView")); + Assert.Same(orderDateColumn, orderDate.FindViewColumn(ordersView.Name, ordersView.Schema)); Assert.Equal(new[] { orderDate, orderDetailsDate }, orderDateColumn.PropertyMappings.Select(m => m.Property)); Assert.Equal("OrderDateView", orderDateColumn.Name); Assert.Equal("default_datetime_mapping", orderDateColumn.Type); @@ -202,6 +203,7 @@ private static void AssertTables(IRelationalModel model) var orderDateColumn = orderDateMapping.Column; Assert.Same(orderDateColumn, ordersTable.FindColumn("OrderDate")); + Assert.Same(orderDateColumn, orderDate.FindTableColumn(ordersTable.Name, ordersTable.Schema)); Assert.Equal(new[] { orderDate, orderDetailsDate }, orderDateColumn.PropertyMappings.Select(m => m.Property)); Assert.Equal("OrderDate", orderDateColumn.Name); Assert.Equal("default_datetime_mapping", orderDateColumn.Type); diff --git a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs index 9e6c15f20b8..5d0a31bd6ed 100644 --- a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs +++ b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data.Common; +using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; @@ -313,8 +314,8 @@ public void CreateStoreCommand_creates_parameters_for_each_ModificationCommand() batch.AddCommand( new FakeModificationCommand( - "T", - "S", + "T1", + null, parameterNameGenerator.GenerateNext, true, new List @@ -322,14 +323,15 @@ public void CreateStoreCommand_creates_parameters_for_each_ModificationCommand() new ColumnModification( entry, property, + property.GetTableColumnMappings().Single().Column, parameterNameGenerator.GenerateNext, - false, true, false, false, false, true) + false, true, false, false, true) })); batch.AddCommand( new FakeModificationCommand( - "T", - "S", + "T1", + null, parameterNameGenerator.GenerateNext, true, new List @@ -337,8 +339,9 @@ public void CreateStoreCommand_creates_parameters_for_each_ModificationCommand() new ColumnModification( entry, property, + property.GetTableColumnMappings().Single().Column, parameterNameGenerator.GenerateNext, - false, true, false, false, false, true) + false, true, false, false, true) })); var storeCommand = batch.CreateStoreCommandBase(); @@ -363,8 +366,8 @@ public void PopulateParameters_creates_parameter_for_write_ModificationCommand() var parameterNameGenerator = new ParameterNameGenerator(); batch.AddCommand( new FakeModificationCommand( - "T", - "S", + "T1", + null, parameterNameGenerator.GenerateNext, true, new List @@ -372,8 +375,9 @@ public void PopulateParameters_creates_parameter_for_write_ModificationCommand() new ColumnModification( entry, property, + property.GetTableColumnMappings().Single().Column, parameterNameGenerator.GenerateNext, - isRead: false, isWrite: true, isKey: false, isCondition: false, isConcurrencyToken: false, + isRead: false, isWrite: true, isKey: false, isCondition: false, sensitiveLoggingEnabled: true) })); @@ -397,8 +401,8 @@ public void PopulateParameters_creates_parameter_for_condition_ModificationComma var parameterNameGenerator = new ParameterNameGenerator(); batch.AddCommand( new FakeModificationCommand( - "T", - "S", + "T1", + null, parameterNameGenerator.GenerateNext, true, new List @@ -406,8 +410,9 @@ public void PopulateParameters_creates_parameter_for_condition_ModificationComma new ColumnModification( entry, property, + property.GetTableColumnMappings().Single().Column, parameterNameGenerator.GenerateNext, - isRead: false, isWrite: false, isKey: false, isCondition: true, isConcurrencyToken: false, + isRead: false, isWrite: false, isKey: false, isCondition: true, sensitiveLoggingEnabled: true) })); @@ -431,8 +436,8 @@ public void PopulateParameters_creates_parameters_for_write_and_condition_Modifi var parameterNameGenerator = new ParameterNameGenerator(); batch.AddCommand( new FakeModificationCommand( - "T", - "S", + "T1", + null, parameterNameGenerator.GenerateNext, true, new List @@ -440,8 +445,9 @@ public void PopulateParameters_creates_parameters_for_write_and_condition_Modifi new ColumnModification( entry, property, + property.GetTableColumnMappings().Single().Column, parameterNameGenerator.GenerateNext, - isRead: false, isWrite: true, isKey: false, isCondition: true, isConcurrencyToken: false, + isRead: false, isWrite: true, isKey: false, isCondition: true, sensitiveLoggingEnabled: true) })); @@ -467,8 +473,8 @@ public void PopulateParameters_does_not_create_parameter_for_read_ModificationCo var parameterNameGenerator = new ParameterNameGenerator(); batch.AddCommand( new FakeModificationCommand( - "T", - "S", + "T1", + null, parameterNameGenerator.GenerateNext, true, new List @@ -476,8 +482,9 @@ public void PopulateParameters_does_not_create_parameter_for_read_ModificationCo new ColumnModification( entry, property, + property.GetTableColumnMappings().Single().Column, parameterNameGenerator.GenerateNext, - isRead: true, isWrite: false, isKey: false, isCondition: false, isConcurrencyToken: false, + isRead: true, isWrite: false, isKey: false, isCondition: false, sensitiveLoggingEnabled: true) })); @@ -494,32 +501,24 @@ private class T1 private static IModel BuildModel(bool generateKeyValues, bool computeNonKeyValue) { - IMutableModel model = new Model(); + var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); + var entityType = modelBuilder.Entity(); - var entityType = model.AddEntityType(typeof(T1)); - - var key = entityType.AddProperty("Id", typeof(int)); - key.ValueGenerated = generateKeyValues ? ValueGenerated.OnAdd : ValueGenerated.Never; - key.SetColumnName("Col1"); - entityType.SetPrimaryKey(key); - - var nonKey = entityType.AddProperty("Name", typeof(string)); - nonKey.SetColumnName("Col2"); - nonKey.ValueGenerated = computeNonKeyValue ? ValueGenerated.OnAddOrUpdate : ValueGenerated.Never; + entityType.Property(t => t.Id).HasColumnName("Col1"); + if (!generateKeyValues) + { + entityType.Property(t => t.Id).ValueGeneratedNever(); + } - GenerateMapping(key); - GenerateMapping(nonKey); + entityType.Property(t => t.Name).HasColumnName("Col2"); + if (computeNonKeyValue) + { + entityType.Property(t => t.Name).ValueGeneratedOnAddOrUpdate(); + } - return model.FinalizeModel(); + return modelBuilder.FinalizeModel(); } - private static void GenerateMapping(IMutableProperty property) - => property[CoreAnnotationNames.TypeMapping] = - new TestRelationalTypeMappingSource( - TestServiceFactory.Instance.Create(), - TestServiceFactory.Instance.Create()) - .FindMapping(property); - private static InternalEntityEntry CreateEntry( EntityState entityState, bool generateKeyValues = false, diff --git a/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTestBase.cs b/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTestBase.cs index 2f7b9506e7f..781f3cd4e28 100644 --- a/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTestBase.cs +++ b/test/EFCore.Relational.Tests/Update/UpdateSqlGeneratorTestBase.cs @@ -332,17 +332,20 @@ protected ModificationCommand CreateInsertCommand(bool identityKey = true, bool var columnModifications = new[] { new ColumnModification( - entry, idProperty, generator.GenerateNext, identityKey, !identityKey, true, false, false, true), + entry, idProperty, idProperty.GetTableColumnMappings().Single().Column, generator.GenerateNext, + identityKey, !identityKey, true, false, true), new ColumnModification( - entry, nameProperty, generator.GenerateNext, false, true, false, false, false, true), + entry, nameProperty, nameProperty.GetTableColumnMappings().Single().Column, generator.GenerateNext, + false, true, false, false, true), new ColumnModification( - entry, quacksProperty, generator.GenerateNext, false, true, false, false, false, true), + entry, quacksProperty, quacksProperty.GetTableColumnMappings().Single().Column, generator.GenerateNext, + false, true, false, false, true), new ColumnModification( - entry, computedProperty, generator.GenerateNext, isComputed, false, false, false, - true, true), + entry, computedProperty, computedProperty.GetTableColumnMappings().Single().Column, generator.GenerateNext, + isComputed, false, false, false, true), new ColumnModification( - entry, concurrencyProperty, generator.GenerateNext, false, true, false, false, - false, true) + entry, concurrencyProperty, concurrencyProperty.GetTableColumnMappings().Single().Column, generator.GenerateNext, + false, true, false, false, true) }; if (defaultsOnly) @@ -369,17 +372,20 @@ protected ModificationCommand CreateUpdateCommand(bool isComputed = true, bool c var columnModifications = new[] { new ColumnModification( - entry, idProperty, generator.GenerateNext, false, false, true, true, false, true), + entry, idProperty, idProperty.GetTableColumnMappings().Single().Column, generator.GenerateNext, + false, false, true, true, true), new ColumnModification( - entry, nameProperty, generator.GenerateNext, false, true, false, false, false, true), + entry, nameProperty, nameProperty.GetTableColumnMappings().Single().Column, generator.GenerateNext, + false, true, false, false, true), new ColumnModification( - entry, quacksProperty, generator.GenerateNext, false, true, false, false, false, true), + entry, quacksProperty, quacksProperty.GetTableColumnMappings().Single().Column, generator.GenerateNext, + false, true, false, false, true), new ColumnModification( - entry, computedProperty, generator.GenerateNext, isComputed, false, false, false, - false, true), + entry, computedProperty, computedProperty.GetTableColumnMappings().Single().Column, generator.GenerateNext, + isComputed, false, false, false, true), new ColumnModification( - entry, concurrencyProperty, generator.GenerateNext, false, true, false, - concurrencyToken, concurrencyToken, true) + entry, concurrencyProperty, concurrencyProperty.GetTableColumnMappings().Single().Column, generator.GenerateNext, + false, true, false, concurrencyToken, true) }; return new FakeModificationCommand( @@ -398,10 +404,11 @@ protected ModificationCommand CreateDeleteCommand(bool concurrencyToken = true) var columnModifications = new[] { new ColumnModification( - entry, idProperty, generator.GenerateNext, false, false, true, true, concurrencyToken, true), + entry, idProperty, idProperty.GetTableColumnMappings().Single().Column, generator.GenerateNext, + false, false, true, true, true), new ColumnModification( - entry, concurrencyProperty, generator.GenerateNext, false, false, false, - concurrencyToken, concurrencyToken, true) + entry, concurrencyProperty, concurrencyProperty.GetTableColumnMappings().Single().Column, generator.GenerateNext, + false, false, false, concurrencyToken, true) }; return new FakeModificationCommand( @@ -413,7 +420,7 @@ protected ModificationCommand CreateDeleteCommand(bool concurrencyToken = true) private IMutableEntityType GetDuckType() { var modelBuilder = TestHelpers.CreateConventionBuilder(); - modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); + modelBuilder.Entity().ToTable("Ducks", Schema).Property(e => e.Id).ValueGeneratedNever(); return modelBuilder.Model.FindEntityType(typeof(Duck)); }