From 66bf55f337776e03088a67c92f967c82e56b0dca Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 23 Mar 2022 10:34:51 -0700 Subject: [PATCH] Add TPC support for data seeding Use row-based diffing for data seeding Fixes #22063 Fixes #12466 Fixes #27575 --- ...ntityFrameworkRelationalServicesBuilder.cs | 2 + .../Internal/TableBaseIdentityComparer.cs | 36 + .../Internal/MigrationsModelDiffer.cs | 788 +++++----- .../Migrations/MigrationsSqlGenerator.cs | 20 +- .../Storage/RelationalDatabase.cs | 12 +- .../Update/ColumnModification.cs | 44 +- .../Update/ColumnModificationParameters.cs | 52 + .../Update/IColumnModification.cs | 15 +- .../Update/IModificationCommand.cs | 7 - .../Update/IModificationCommandFactory.cs | 8 + .../Update/INonTrackedModificationCommand.cs | 34 + .../Update/Internal/ColumnAccessorsFactory.cs | 8 +- .../Update/Internal/CommandBatchPreparer.cs | 2 +- .../Update/Internal/IRowIdentityMap.cs | 53 + .../Update/Internal/IRowIdentityMapFactory.cs | 21 + .../Internal/ModificationCommandComparer.cs | 24 + .../Internal/ModificationCommandFactory.cs | 10 + .../Update/Internal/RowIdentityMap.cs | 115 ++ .../Update/Internal/RowIdentityMapFactory.cs | 34 + .../Update/ModificationCommand.cs | 24 +- .../Update/ModificationCommandParameters.cs | 26 - ...NonTrackedModificationCommandParameters.cs | 71 + .../Internal/SnapshotFactoryFactory.cs | 2 +- src/EFCore/Infrastructure/ModelValidator.cs | 6 +- .../Internal/ClrCollectionAccessorFactory.cs | 4 +- src/EFCore/Metadata/Internal/EntityType.cs | 31 +- .../Internal/PropertyBaseExtensions.cs | 4 +- src/EFCore/Storage/DatabaseDependencies.cs | 2 +- .../Design/MigrationScaffolderTest.cs | 3 +- .../Update/UpdateSqlGeneratorTestBase.cs | 16 +- .../Internal/MigrationsModelDifferTest.cs | 1335 ++++++++--------- .../Internal/MigrationsModelDifferTestBase.cs | 10 +- .../Update/ModificationCommandComparerTest.cs | 34 +- .../Update/ModificationCommandTest.cs | 36 +- .../ReaderModificationCommandBatchTest.cs | 41 +- ...rverModificationCommandBatchFactoryTest.cs | 10 +- .../SqlServerModificationCommandBatchTest.cs | 6 +- .../Infrastructure/ModelValidatorTest.cs | 2 +- 38 files changed, 1666 insertions(+), 1282 deletions(-) create mode 100644 src/EFCore.Relational/Metadata/Internal/TableBaseIdentityComparer.cs create mode 100644 src/EFCore.Relational/Update/INonTrackedModificationCommand.cs create mode 100644 src/EFCore.Relational/Update/Internal/IRowIdentityMap.cs create mode 100644 src/EFCore.Relational/Update/Internal/IRowIdentityMapFactory.cs create mode 100644 src/EFCore.Relational/Update/Internal/RowIdentityMap.cs create mode 100644 src/EFCore.Relational/Update/Internal/RowIdentityMapFactory.cs create mode 100644 src/EFCore.Relational/Update/NonTrackedModificationCommandParameters.cs diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs index 620e5fb6f2b..29184b47fd6 100644 --- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs +++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs @@ -47,6 +47,7 @@ public static readonly IDictionary RelationalServi { typeof(IRowKeyValueFactoryFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IRowForeignKeyValueFactoryFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IRowIndexValueFactoryFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, + { typeof(IRowIdentityMapFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IParameterNameGeneratorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IComparer), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IMigrationsIdGenerator), new ServiceCharacteristics(ServiceLifetime.Singleton) }, @@ -130,6 +131,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(); TryAdd(); + TryAdd(); TryAdd(); TryAdd(); TryAdd(); diff --git a/src/EFCore.Relational/Metadata/Internal/TableBaseIdentityComparer.cs b/src/EFCore.Relational/Metadata/Internal/TableBaseIdentityComparer.cs new file mode 100644 index 00000000000..3012545e100 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/TableBaseIdentityComparer.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +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 sealed class TableBaseIdentityComparer : IEqualityComparer +{ + private TableBaseIdentityComparer() + { + } + + /// + /// 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 TableBaseIdentityComparer Instance = new(); + + /// + public bool Equals(ITableBase? x, ITableBase? y) + => ReferenceEquals(x, y) + || (x is null + ? y is null + : y is not null && x.Name == y.Name && x.Schema == y.Schema); + + /// + public int GetHashCode(ITableBase obj) + => HashCode.Combine(obj.Name, obj.Schema); +} diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 67a6a39820c..a0b58b298da 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -1,10 +1,9 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; -using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; -using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Update.Internal; namespace Microsoft.EntityFrameworkCore.Migrations.Internal; @@ -43,11 +42,8 @@ public class MigrationsModelDiffer : IMigrationsModelDiffer typeof(AddForeignKeyOperation), typeof(CreateIndexOperation), typeof(AddCheckConstraintOperation) }; - private IUpdateAdapter? _sourceUpdateAdapter; - private IUpdateAdapter? _targetUpdateAdapter; - - private readonly Dictionary _sourceSharedIdentityEntryMaps = - new(); + private Dictionary? _sourceIdentityMaps; + private Dictionary? _targetIdentityMaps; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -57,17 +53,13 @@ public class MigrationsModelDiffer : IMigrationsModelDiffer /// public MigrationsModelDiffer( IRelationalTypeMappingSource typeMappingSource, - IMigrationsAnnotationProvider migrationsAnnotations, -#pragma warning disable EF1001 // Internal EF Core API usage. - IChangeDetector changeDetector, -#pragma warning restore EF1001 // Internal EF Core API usage. - IUpdateAdapterFactory updateAdapterFactory, + IMigrationsAnnotationProvider migrationsAnnotationProvider, + IRowIdentityMapFactory rowIdentityMapFactory, CommandBatchPreparerDependencies commandBatchPreparerDependencies) { TypeMappingSource = typeMappingSource; - MigrationsAnnotations = migrationsAnnotations; - ChangeDetector = changeDetector; - UpdateAdapterFactory = updateAdapterFactory; + MigrationsAnnotationProvider = migrationsAnnotationProvider; + RowIdentityMapFactory = rowIdentityMapFactory; CommandBatchPreparerDependencies = commandBatchPreparerDependencies; } @@ -85,7 +77,7 @@ public MigrationsModelDiffer( /// 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; } + protected virtual IMigrationsAnnotationProvider MigrationsAnnotationProvider { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -93,7 +85,7 @@ public MigrationsModelDiffer( /// 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 IUpdateAdapterFactory UpdateAdapterFactory { get; } + protected virtual IRowIdentityMapFactory RowIdentityMapFactory { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -103,16 +95,6 @@ public MigrationsModelDiffer( /// protected virtual CommandBatchPreparerDependencies CommandBatchPreparerDependencies { 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. - /// -#pragma warning disable EF1001 // Internal EF Core API usage. - protected virtual IChangeDetector ChangeDetector { get; } -#pragma warning restore EF1001 // Internal EF Core API usage. - /// /// 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 @@ -421,7 +403,7 @@ private IEnumerable DiffAnnotations( if (target == null) { - var sourceMigrationsAnnotationsForRemoved = MigrationsAnnotations.ForRemove(source).ToList(); + var sourceMigrationsAnnotationsForRemoved = MigrationsAnnotationProvider.ForRemove(source).ToList(); if (sourceMigrationsAnnotationsForRemoved.Count > 0) { var alterDatabaseOperation = new AlterDatabaseOperation(); @@ -592,7 +574,7 @@ protected virtual IEnumerable Diff( NewName = target.Name }; - renameTableOperation.AddAnnotations(MigrationsAnnotations.ForRename(source)); + renameTableOperation.AddAnnotations(MigrationsAnnotationProvider.ForRename(source)); yield return renameTableOperation; } @@ -692,7 +674,7 @@ protected virtual IEnumerable Remove( } var operation = new DropTableOperation { Schema = source.Schema, Name = source.Name }; - operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + operation.AddAnnotations(MigrationsAnnotationProvider.ForRemove(source)); diffContext.AddDrop(source, operation); @@ -980,7 +962,7 @@ protected virtual IEnumerable Diff( NewName = target.Name }; - renameColumnOperation.AddAnnotations(MigrationsAnnotations.ForRename(source)); + renameColumnOperation.AddAnnotations(MigrationsAnnotationProvider.ForRename(source)); yield return renameColumnOperation; } @@ -1103,7 +1085,7 @@ protected virtual IEnumerable Remove(IColumn source, DiffCon Table = table.Name, Name = source.Name }; - operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + operation.AddAnnotations(MigrationsAnnotationProvider.ForRemove(source)); diffContext.AddDrop(source, operation); @@ -1262,7 +1244,7 @@ protected virtual IEnumerable Remove( }; } - operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + operation.AddAnnotations(MigrationsAnnotationProvider.ForRemove(source)); yield return operation; } @@ -1345,7 +1327,6 @@ protected virtual IEnumerable Add(IForeignKeyConstraint targ protected virtual IEnumerable Remove(IForeignKeyConstraint source, DiffContext diffContext) { var sourceTable = source.Table; - if (sourceTable.IsExcludedFromMigrations) { yield break; @@ -1360,7 +1341,7 @@ protected virtual IEnumerable Remove(IForeignKeyConstraint s Table = sourceTable.Name, Name = source.Name }; - operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + operation.AddAnnotations(MigrationsAnnotationProvider.ForRemove(source)); yield return operation; } @@ -1427,7 +1408,7 @@ protected virtual IEnumerable Diff( NewName = targetName }; - renameIndexOperation.AddAnnotations(MigrationsAnnotations.ForRename(source)); + renameIndexOperation.AddAnnotations(MigrationsAnnotationProvider.ForRename(source)); yield return renameIndexOperation; } @@ -1462,7 +1443,7 @@ protected virtual IEnumerable Remove(ITableIndex source, Dif Schema = sourceTable.Schema, Table = sourceTable.Name }; - operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + operation.AddAnnotations(MigrationsAnnotationProvider.ForRemove(source)); yield return operation; } @@ -1531,7 +1512,7 @@ protected virtual IEnumerable Remove(ICheckConstraint source Schema = sourceEntityType.GetSchema(), Table = sourceEntityType.GetTableName()! }; - operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + operation.AddAnnotations(MigrationsAnnotationProvider.ForRemove(source)); yield return operation; } @@ -1585,7 +1566,7 @@ protected virtual IEnumerable Diff( NewName = target.Name }; - renameSequenceOperation.AddAnnotations(MigrationsAnnotations.ForRename(source)); + renameSequenceOperation.AddAnnotations(MigrationsAnnotationProvider.ForRename(source)); yield return renameSequenceOperation; } @@ -1646,7 +1627,7 @@ protected virtual IEnumerable Add(ISequence target, DiffCont protected virtual IEnumerable Remove(ISequence source, DiffContext diffContext) { var operation = new DropSequenceOperation { Schema = source.Schema, Name = source.Name }; - operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + operation.AddAnnotations(MigrationsAnnotationProvider.ForRemove(source)); yield return operation; } @@ -1682,409 +1663,412 @@ protected virtual void TrackData( { if (target == null) { - _targetUpdateAdapter = null; + _targetIdentityMaps = null; return; } - _targetUpdateAdapter = UpdateAdapterFactory.CreateStandalone(target.Model); - _targetUpdateAdapter.CascadeDeleteTiming = CascadeTiming.Never; + if (_targetIdentityMaps == null) + { + _targetIdentityMaps = new(TableBaseIdentityComparer.Instance); + } + else + { + _targetIdentityMaps.Clear(); + } foreach (var targetEntityType in target.Model.GetEntityTypes()) { - foreach (var targetSeed in targetEntityType.GetSeedData()) - { - var targetEntry = _targetUpdateAdapter.CreateEntry(targetSeed, targetEntityType); - if (targetEntry.ToEntityEntry().Entity is Dictionary targetBag) - { - targetBag.Remove((key, _, target) => !target!.ContainsKey(key), targetSeed); - } - - targetEntry.EntityState = EntityState.Added; - } + AddSeedData(targetEntityType, _targetIdentityMaps, EntityState.Added); } if (source == null) { - _sourceUpdateAdapter = null; + _sourceIdentityMaps = null; return; } - _sourceUpdateAdapter = UpdateAdapterFactory.CreateStandalone(source.Model); - _sourceUpdateAdapter.CascadeDeleteTiming = CascadeTiming.OnSaveChanges; + if (_sourceIdentityMaps == null) + { + _sourceIdentityMaps = new(TableBaseIdentityComparer.Instance); + } + else + { + _sourceIdentityMaps.Clear(); + } foreach (var sourceEntityType in source.Model.GetEntityTypes()) { - foreach (var sourceSeed in sourceEntityType.GetSeedData()) - { - _sourceUpdateAdapter - .CreateEntry(sourceSeed, sourceEntityType) - .EntityState = EntityState.Unchanged; - } + AddSeedData(sourceEntityType, _sourceIdentityMaps, EntityState.Deleted); } } - - /// - /// 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 Dictionary>? DiffData( - IRelationalModel? source, - IRelationalModel? target, - DiffContext diffContext) + + private void AddSeedData(IEntityType entityType, Dictionary identityMaps, EntityState initialState) { - if (source == null - || target == null) - { - return null; - } + var sensitiveLoggingEnabled = CommandBatchPreparerDependencies.LoggingOptions.IsSensitiveDataLoggingEnabled; - var keyMapping = new Dictionary>>(); - foreach (var sourceEntityType in source.Model.GetEntityTypes()) +#pragma warning disable EF1001 // Internal EF Core API usage. + foreach (var rawSeed in ((EntityType)entityType).GetRawSeedData()) { - foreach (var sourceTableMapping in sourceEntityType.GetTableMappings()) +#pragma warning restore EF1001 // Internal EF Core API usage. + Func getValue; + var type = rawSeed.GetType(); + if (entityType.ClrType.IsAssignableFrom(type)) { - var sourceTable = sourceTableMapping.Table; - var targetTable = diffContext.FindTarget(sourceTable); - if (targetTable?.PrimaryKey == null) - { - continue; - } - - foreach (var targetKey in targetTable.PrimaryKey.MappedKeys) + getValue = (property, seed) => { - var keyPropertiesMap = new List<(IProperty, ValueConverter?, ValueConverter?)>(); - foreach (var keyProperty in targetKey.Properties) +#pragma warning disable EF1001 // Internal EF Core API usage. + if (!property.TryGetMemberInfo(forMaterialization: false, forSet: false, out var memberInfo, out var _)) { - var targetColumn = targetTable.FindColumn(keyProperty); - var sourceColumn = diffContext.FindSource(targetColumn); - if (sourceColumn == null) - { - break; - } + return (null, false); + } +#pragma warning restore EF1001 // Internal EF Core API usage. - foreach (var sourceProperty in sourceColumn.PropertyMappings.Select(m => m.Property).Distinct()) - { - if (!sourceProperty.DeclaringEntityType.IsAssignableFrom(sourceEntityType)) + object? value = null; + switch (memberInfo) + { + case PropertyInfo propertyInfo: + if (property.IsIndexerProperty()) { - continue; + try + { + value = propertyInfo.GetValue(seed, new[] { property.Name }); + } + catch (Exception) + { + return (null, false); + } } - - 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)) + else { - continue; + value = propertyInfo.GetValue(seed); } - keyPropertiesMap.Add((sourceProperty, sourceConverter, targetConverter)); break; - } + case FieldInfo fieldInfo: + value = fieldInfo.GetValue(seed); + break; } - if (keyPropertiesMap.Count == targetKey.Properties.Count) - { - keyMapping.GetOrAddNew(sourceEntityType)[(targetKey, targetTable)] = keyPropertiesMap; - } - } + return (value, true); + }; } - } - - var changedTableMappings = new Dictionary>(); - foreach (var targetEntityType in target.Model.GetEntityTypes()) - { - var targetKey = targetEntityType.FindPrimaryKey(); - if (targetKey == null) + else { - continue; + // anonymous type + var anonymousProperties = type.GetMembersInHierarchy() + .OfType() + .ToDictionary(p => p.GetSimpleMemberName()); + + getValue = (property, seed) => + anonymousProperties.TryGetValue(property.Name, out var propertyInfo) ? (propertyInfo.GetValue(seed), true) : (null, false); } - ITable? firstSourceTable = null; - foreach (var targetTableMapping in targetEntityType.GetTableMappings()) + foreach (var mapping in entityType.GetTableMappings()) { - var targetTable = targetTableMapping.Table; - if (firstSourceTable == null) + INonTrackedModificationCommand command; + var table = mapping.Table; + var keyConstraint = table.PrimaryKey!; + if (!identityMaps.TryGetValue(table, out var identityMap)) { - firstSourceTable = diffContext.FindSource(targetTable); - - continue; + identityMap = RowIdentityMapFactory.Create(keyConstraint); + identityMaps.Add(table, identityMap); } - Check.DebugAssert(firstSourceTable != null, "mainSourceTable is null"); - - var newMapping = true; - var sourceTable = diffContext.FindSource(targetTable); - if (sourceTable != null) + var key = new object?[keyConstraint.Columns.Count]; + var keyFound = true; + for (var i = 0; i < key.Length; i++) { - foreach (var sourceEntityTypeMapping in sourceTable.EntityTypeMappings) + var columnMapping = keyConstraint.Columns[i].FindColumnMapping(entityType)!; + var property = columnMapping.Property; + var (value, hasValue) = getValue(property, rawSeed); + if (!hasValue) { - var sourceEntityType = sourceEntityTypeMapping.EntityType; - if (keyMapping.TryGetValue(sourceEntityType, out var targetKeyMap) - && targetKeyMap.ContainsKey((targetKey, targetTable)) - && sourceEntityType.GetTableMappings().First().Table == firstSourceTable) - { - newMapping = false; - } + keyFound = false; + break; } + + var valueConverter = columnMapping.TypeMapping.Converter; + key[i] = valueConverter == null + ? value + : valueConverter.ConvertToProvider(value); + } + + if (!keyFound) + { + continue; } - if (newMapping) + if (table.IsShared) { - if (!changedTableMappings.TryGetValue(targetEntityType, out var newTables)) + var existingCommand = identityMap.FindCommand(key); + if (existingCommand == null) { - newTables = new List(); - changedTableMappings[targetEntityType] = newTables; + existingCommand = CommandBatchPreparerDependencies.ModificationCommandFactory.CreateNonTrackedModificationCommand( + new NonTrackedModificationCommandParameters(table, sensitiveLoggingEnabled)); + identityMap.Add(key, existingCommand); } - newTables.Add(targetTable); + command = existingCommand; + } + else + { + command = CommandBatchPreparerDependencies.ModificationCommandFactory.CreateNonTrackedModificationCommand( + new NonTrackedModificationCommandParameters(table, sensitiveLoggingEnabled)); + identityMap.Add(key, command); } - } - } - foreach (var sourceEntityType in source.Model.GetEntityTypes()) - { - ITable? firstSourceTable = null; - if (keyMapping.TryGetValue(sourceEntityType, out var targetKeyMap)) - { - ITable? firstTargetTable = null; - foreach (var sourceTableMapping in sourceEntityType.GetTableMappings()) + command.EntityState = initialState; + + foreach (var columnMapping in mapping.ColumnMappings) { - var sourceTable = sourceTableMapping.Table; - if (firstSourceTable == null) + var property = columnMapping.Property; + var column = columnMapping.Column; + + if ((column.ComputedColumnSql != null) + || (property.ValueGenerated & ValueGenerated.OnUpdate) != 0) + { + continue; + } + + var writeValue = true; + var (value, hasValue) = getValue(property, rawSeed); + if (!hasValue) + { + value = property.ClrType.GetDefaultValue(); + } + + if (!hasValue + || Equals(value, property.ClrType.GetDefaultValue())) { - firstSourceTable = sourceTable; - firstTargetTable = diffContext.FindTarget(firstSourceTable); - if (firstTargetTable == null) + if (property.GetValueGeneratorFactory() != null + && property == property.DeclaringEntityType.FindDiscriminatorProperty()) { - break; + value = entityType.GetDiscriminatorValue()!; + } + else if ((property.ValueGenerated & ValueGenerated.OnAdd) != 0) + { + writeValue = false; } - - continue; } - var targetTable = diffContext.FindTarget(sourceTable); - var removedMapping = !(targetTable != null - && targetKeyMap.Keys.Any( - k => k.Item2 == targetTable - && k.Item1.DeclaringEntityType.GetTableMappings().First().Table == firstTargetTable)); + var valueConverter = columnMapping.TypeMapping.Converter; + value = valueConverter == null + ? value + : valueConverter.ConvertToProvider(value); - if (removedMapping - && diffContext.FindDrop(sourceTable) == null) + if (!writeValue) { - if (!changedTableMappings.TryGetValue(sourceEntityType, out var removedTables)) + if (column.DefaultValue != null) + { + value = column.DefaultValue; + } + else if (value == null + && !column.IsNullable) { - removedTables = new List(); - changedTableMappings[sourceEntityType] = removedTables; + value = column.ProviderClrType.GetDefaultValue(); } + } - removedTables.Add(sourceTable); + var existingColumnModification = command.ColumnModifications.FirstOrDefault(c => c.ColumnName == column.Name); + if (existingColumnModification != null) + { + Check.DebugAssert(Equals(existingColumnModification.Value, value), $"existing value {existingColumnModification.Value} is different from {value}"); + continue; } + + writeValue = writeValue + && initialState != EntityState.Deleted + && property.GetBeforeSaveBehavior() == PropertySaveBehavior.Save; + command.AddColumnModification( + new ColumnModificationParameters( + column, originalValue: value, value, property, columnMapping.TypeMapping, + read: false, write: writeValue, + key: property.IsPrimaryKey(), condition: false, + sensitiveLoggingEnabled, column.IsNullable)); } } - else - { - targetKeyMap = null; - firstSourceTable = sourceEntityType.GetTableMappings().FirstOrDefault()?.Table; - } + } + } - if (firstSourceTable == 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. + /// + protected virtual void DiffData( + IRelationalModel? source, + IRelationalModel? target, + DiffContext diffContext) + { + if (source == null + || target == null + || _sourceIdentityMaps == null + || _targetIdentityMaps == null) + { + return; + } + + var tableMapping = new Dictionary(); + foreach (var targetPair in _targetIdentityMaps) + { + var (targetTable, targetIdentityMap) = targetPair; + var targetKey = targetTable.PrimaryKey!; + var sourceTable = diffContext.FindSource(targetTable); + var sourceKey = sourceTable?.PrimaryKey; + if (sourceKey == null + || !_sourceIdentityMaps.TryGetValue(sourceTable!, out var sourceIdentityMap)) { continue; } - // If table sharing is being used find the main table of the principal entity type - var mainSourceEntityType = sourceEntityType; - var principalSourceTable = firstSourceTable; - while (firstSourceTable.GetRowInternalForeignKeys(mainSourceEntityType).Any()) + var mappingFound = true; + for (var i = 0; i < targetKey.Columns.Count; i++) { - mainSourceEntityType = principalSourceTable.EntityTypeMappings.First(m => m.IsSharedTablePrincipal).EntityType; - principalSourceTable = mainSourceEntityType.GetTableMappings().First().Table; + var keyColumn = targetKey.Columns[i]; + var sourceColumn = diffContext.FindSource(keyColumn); + if (sourceColumn == null + || sourceKey.Columns[i] != sourceColumn + || keyColumn.ProviderClrType != sourceColumn.ProviderClrType) + { + mappingFound = false; + break; + } } - foreach (var sourceSeed in sourceEntityType.GetSeedData()) + if (mappingFound + && targetKey.Columns.Count == sourceKey.Columns.Count) { - var sourceEntry = GetEntry(sourceSeed, sourceEntityType, _sourceUpdateAdapter!); + tableMapping.Add(targetTable, (sourceTable!, sourceIdentityMap)); + } + } - if (!_sourceSharedIdentityEntryMaps.TryGetValue(principalSourceTable, out var sourceTableEntryMappingMap)) + var unchangedColumns = new List(); + var overridenColumns = new List(); + foreach (var targetPair in _targetIdentityMaps) + { + var (targetTable, targetIdentityMap) = targetPair; + if (!tableMapping.TryGetValue(targetTable, out var sourcePair)) + { + continue; + } + + var (sourceTable, sourceIdentityMap) = sourcePair; + var key = targetTable.PrimaryKey!; + var keyValues = new object?[key.Columns.Count]; + foreach (var targetRow in targetIdentityMap.Rows) + { + for (var i = 0; i < keyValues.Length; i++) { - sourceTableEntryMappingMap = new SharedIdentityMap(_sourceUpdateAdapter!); - _sourceSharedIdentityEntryMaps.Add(principalSourceTable, sourceTableEntryMappingMap); + var modification = targetRow.ColumnModifications.First(m => m.ColumnName == key.Columns[i].Name); + keyValues[i] = modification.Value; } - var entryMapping = sourceTableEntryMappingMap.GetOrAddValue(sourceEntry, firstSourceTable); - entryMapping.SourceEntries.Add(sourceEntry); + var sourceRow = sourceIdentityMap.FindCommand(keyValues); + if (sourceRow == null) + { + if (sourceTable.IsExcludedFromMigrations + || targetTable.IsExcludedFromMigrations) + { + targetRow.EntityState = EntityState.Unchanged; + } + + continue; + } + + if (sourceTable.IsExcludedFromMigrations + || targetTable.IsExcludedFromMigrations) + { + targetRow.EntityState = EntityState.Unchanged; + sourceRow.EntityState = EntityState.Unchanged; + continue; + } - if (targetKeyMap == null) + if (diffContext.FindDrop(sourceTable) != null) { + sourceRow.EntityState = EntityState.Unchanged; continue; } - foreach (var targetKeyTuple in targetKeyMap) + var recreateRow = false; + unchangedColumns.Clear(); + overridenColumns.Clear(); + var anyColumnsModified = false; + foreach (var targetColumnModification in targetRow.ColumnModifications) { - var (targetKey, targetTable) = targetKeyTuple.Key; - var keyPropertiesMap = targetKeyTuple.Value; + var targetColumn = targetColumnModification.Column!; + var targetMapping = targetColumn.PropertyMappings.First(); + var targetProperty = targetMapping.Property; - var targetKeyValues = new object?[keyPropertiesMap.Count]; - for (var i = 0; i < keyPropertiesMap.Count; i++) + var sourceColumn = diffContext.FindSource(targetColumn); + if (sourceColumn == null) { - var (sourceProperty, sourceConverter, targetConverter) = keyPropertiesMap[i]; - var sourceValue = sourceEntry.GetCurrentValue(sourceProperty); - targetKeyValues[i] = targetKey.Properties[i].ClrType != sourceProperty.ClrType - ? sourceConverter != null - ? sourceConverter.ConvertToProvider(sourceValue) - : targetConverter!.ConvertFromProvider(sourceValue) - : sourceValue; - } + if (targetProperty.GetAfterSaveBehavior() != PropertySaveBehavior.Save + && (targetProperty.ValueGenerated & ValueGenerated.OnUpdate) == 0) + { + recreateRow = true; + break; + } - var entry = _targetUpdateAdapter!.TryGetEntry(targetKey, targetKeyValues); - if (entry == null) - { + anyColumnsModified = true; continue; } - if (entryMapping.TargetEntries.Add(entry)) + var sourceColumnModification = sourceRow.ColumnModifications.FirstOrDefault(m => m.ColumnName == sourceColumn.Name); + if (sourceColumnModification == null) { - if (entry.EntityState != EntityState.Added) + if (targetColumnModification.IsWrite) { - Check.DebugAssert(false, "All entries must be in added state at this point"); - continue; + anyColumnsModified = true; } - - foreach (var targetProperty in entry.EntityType.GetProperties()) - { - if (targetProperty.GetAfterSaveBehavior() == PropertySaveBehavior.Save) - { - entry.SetOriginalValue(targetProperty, targetProperty.ClrType.GetDefaultValue()); - } - } - - entry.EntityState = EntityState.Unchanged; + continue; } - if (entryMapping.RecreateRow) + var sourceValue = sourceColumnModification.OriginalValue; + var targetValue = targetColumnModification.Value; + var comparer = targetMapping.TypeMapping.ProviderValueComparer; + if (sourceColumn.ProviderClrType == targetColumn.ProviderClrType + && comparer.Equals(sourceValue, targetValue)) { + unchangedColumns.Add(targetColumnModification); continue; } - if (!changedTableMappings.TryGetValue(entry.EntityType, out var newMappings)) + if (!targetColumnModification.IsWrite) { - newMappings = null; + overridenColumns.Add(targetColumnModification); } - - foreach (var targetProperty in entry.EntityType.GetProperties()) + else if (targetProperty.GetAfterSaveBehavior() != PropertySaveBehavior.Save) { - if (targetProperty.ValueGenerated != ValueGenerated.Never - && targetProperty.ValueGenerated != ValueGenerated.OnAdd - && targetProperty.ValueGenerated != ValueGenerated.OnUpdateSometimes) - { - continue; - } - - var targetColumn = targetTable.FindColumn(targetProperty); - var sourceColumn = diffContext.FindSource(targetColumn); - var sourceProperty = sourceColumn?.PropertyMappings.Select(m => m.Property) - .FirstOrDefault(p => p.DeclaringEntityType.IsAssignableFrom(sourceEntityType)); - if (sourceProperty == null) - { - if (targetProperty.GetAfterSaveBehavior() != PropertySaveBehavior.Save - && (targetProperty.ValueGenerated & ValueGenerated.OnUpdate) == 0 - && (targetKeyMap.Count == 1 || entry.EntityType.Name == sourceEntityType.Name)) - { - entryMapping.RecreateRow = true; - break; - } - - continue; - } - - var sourceValue = sourceEntry.GetCurrentValue(sourceProperty); - var targetValue = entry.GetCurrentValue(targetProperty); - var comparer = targetProperty.GetValueComparer(); + recreateRow = true; + break; + } - var modelValuesChanged - = sourceProperty.ClrType.UnwrapNullableType() == targetProperty.ClrType.UnwrapNullableType() - && comparer.Equals(sourceValue, targetValue) == false; + anyColumnsModified = true; + } - if (!modelValuesChanged) + if (!recreateRow) + { + sourceRow.EntityState = EntityState.Unchanged; + if (anyColumnsModified) + { + targetRow.EntityState = EntityState.Modified; + foreach (var unchangedColumn in unchangedColumns) { - var sourceConverter = GetValueConverter(sourceProperty); - var targetConverter = GetValueConverter(targetProperty); - - var convertedSourceValue = sourceConverter == null - ? sourceValue - : sourceConverter.ConvertToProvider(sourceValue); - - var convertedTargetValue = targetConverter == null - ? targetValue - : targetConverter.ConvertToProvider(targetValue); - - var convertedType = sourceConverter?.ProviderClrType - ?? targetConverter?.ProviderClrType; - - if (convertedType != null - && !convertedType.IsNullableType()) - { - var defaultValue = convertedType.GetDefaultValue(); - convertedSourceValue ??= defaultValue; - convertedTargetValue ??= defaultValue; - } - - var storeValuesChanged = convertedSourceValue?.GetType().UnwrapNullableType() - != convertedTargetValue?.GetType().UnwrapNullableType(); - - if (!storeValuesChanged - && convertedType != null) - { - comparer = TypeMappingSource.FindMapping(convertedType)?.Comparer; - - storeValuesChanged = !comparer?.Equals(convertedSourceValue, convertedTargetValue) - ?? !Equals(convertedSourceValue, convertedTargetValue); - } - - if (!storeValuesChanged) - { - if (newMappings == null - || targetProperty.GetTableColumnMappings().Any(m => !newMappings.Contains(m.TableMapping.Table))) - { - entry.SetOriginalValue(targetProperty, entry.GetCurrentValue(targetProperty)); - } - - continue; - } + unchangedColumn.IsWrite = false; } - - if (targetProperty.GetAfterSaveBehavior() != PropertySaveBehavior.Save) + foreach (var overridenColumn in overridenColumns) { - entryMapping.RecreateRow = true; - break; + overridenColumn.IsWrite = true; } - - entry.SetPropertyModified(targetProperty); + } + else + { + targetRow.EntityState = EntityState.Unchanged; } } } } - - return changedTableMappings; - } - - private static IUpdateEntry GetEntry( - IDictionary sourceSeed, - IEntityType sourceEntityType, - IUpdateAdapter updateAdapter) - { - var key = sourceEntityType.FindPrimaryKey()!; - var keyValues = new object?[key.Properties.Count]; - for (var i = 0; i < keyValues.Length; i++) - { - keyValues[i] = sourceSeed[key.Properties[i].Name]; - } - - return updateAdapter.TryGetEntry(key, keyValues)!; } /// @@ -2099,66 +2083,13 @@ protected virtual IEnumerable GetDataOperations( DiffContext diffContext) { TrackData(source, target, diffContext); + + DiffData(source, target, diffContext); - var changedTableMappings = DiffData(source, target, diffContext); - - foreach (var sourceTableEntryMappingMap in _sourceSharedIdentityEntryMaps) - { - foreach (var entryMapping in sourceTableEntryMappingMap.Value.Values) - { - if (entryMapping.RecreateRow - || entryMapping.TargetEntries.Count == 0) - { - foreach (var sourceEntry in entryMapping.SourceEntries) - { - sourceEntry.EntityState = EntityState.Deleted; - _sourceUpdateAdapter!.CascadeDelete( - sourceEntry, - sourceEntry.EntityType.GetReferencingForeignKeys() - .Where( - fk => - { - var behavior = diffContext.FindTarget(fk)?.DeleteBehavior; - return behavior != null && behavior != DeleteBehavior.ClientNoAction; - })); - } - } - } - } - - var entriesWithRemovedMappings = new HashSet(); - foreach (var sourceTableEntryMappingMap in _sourceSharedIdentityEntryMaps) - { - foreach (var entryMapping in sourceTableEntryMappingMap.Value.Values) - { - if (entryMapping.SourceEntries.Any(e => e.EntityState == EntityState.Deleted)) - { - foreach (var targetEntry in entryMapping.TargetEntries) - { - targetEntry.EntityState = EntityState.Added; - } - - foreach (var sourceEntry in entryMapping.SourceEntries) - { - sourceEntry.EntityState = EntityState.Deleted; - } - } - else if (entryMapping.SourceEntries.Any(en => changedTableMappings!.ContainsKey(en.EntityType))) - { - foreach (var sourceEntry in entryMapping.SourceEntries) - { - entriesWithRemovedMappings.Add(sourceEntry); - sourceEntry.EntityState = EntityState.Deleted; - } - } - } - } - - _sourceSharedIdentityEntryMaps.Clear(); - - var dataOperations = GetDataOperations(forSource: true, changedTableMappings, entriesWithRemovedMappings, diffContext) - .Concat(GetDataOperations(forSource: false, changedTableMappings, entriesWithRemovedMappings, diffContext)); + var dataOperations = GetDataOperations(forSource: true, diffContext) + .Concat(GetDataOperations(forSource: false, diffContext)); + // This needs to be evaluated lazily foreach (var operation in dataOperations) { yield return operation; @@ -2167,40 +2098,39 @@ protected virtual IEnumerable GetDataOperations( private IEnumerable GetDataOperations( bool forSource, - Dictionary>? changedTableMappings, - HashSet entriesWithRemovedMappings, DiffContext diffContext) { - var updateAdapter = forSource ? _sourceUpdateAdapter : _targetUpdateAdapter; - if (updateAdapter == null) + var identityMaps = forSource ? _sourceIdentityMaps : _targetIdentityMaps; + if (identityMaps == null) { yield break; } - updateAdapter.DetectChanges(); - var entries = updateAdapter.GetEntriesToSave(); - if (entries == null - || entries.Count == 0) + if (identityMaps.Count == 0) { yield break; } - var model = updateAdapter.Model.GetRelationalModel(); - var commandBatches = new CommandBatchPreparer(CommandBatchPreparerDependencies) - .BatchCommands(entries, updateAdapter); + var commands = identityMaps.Values.SelectMany(m => m.Rows).Where(r => + { + return r.EntityState switch + { + EntityState.Added => true, + EntityState.Modified => true, + EntityState.Unchanged => false, + EntityState.Deleted => diffContext.FindDrop(r.Table!) == null, + _ => throw new InvalidOperationException($"Unexpected entity state: {r.EntityState}") + }; + }); + + var commandSets = new CommandBatchPreparer(CommandBatchPreparerDependencies) + .TopologicalSort(commands); - foreach (var (commandBatch, _) in commandBatches) + foreach (var commandSet in commandSets) { InsertDataOperation? batchInsertOperation = null; - foreach (var command in commandBatch.ModificationCommands) + foreach (var command in commandSet) { - var table = model.FindTable(command.TableName, command.Schema)!; - if (diffContext.FindDrop(table) != null - || table.IsExcludedFromMigrations) - { - continue; - } - switch (command.EntityState) { case EntityState.Added: @@ -2213,7 +2143,7 @@ private IEnumerable GetDataOperations( { batchInsertOperation.Values = AddToMultidimensionalArray( - command.ColumnModifications.Where(col => col.IsKey || col.IsWrite).Select(GetValue).ToList(), + command.ColumnModifications.Where(col => col.IsKey || col.IsWrite).Select(col => col.Value).ToList(), batchInsertOperation.Values); continue; } @@ -2234,7 +2164,7 @@ private IEnumerable GetDataOperations( Columns = command.ColumnModifications.Where(col => col.IsKey || col.IsWrite).Select(col => col.ColumnName) .ToArray(), Values = ToMultidimensionalArray( - command.ColumnModifications.Where(col => col.IsKey || col.IsWrite).Select(GetValue).ToList()) + command.ColumnModifications.Where(col => col.IsKey || col.IsWrite).Select(col => col.Value).ToList()) }; break; case EntityState.Modified: @@ -2250,24 +2180,16 @@ private IEnumerable GetDataOperations( break; } - if (command.Entries.Any( - en => changedTableMappings!.TryGetValue(en.EntityType, out var newTables) - && newTables.Any(t => t.Name == command.TableName && t.Schema == command.Schema))) - { - // If the entity type uses TPT add the rows to the new tables to which the entity has been mapped - goto case EntityState.Added; - } - yield return new UpdateDataOperation { 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).ToList()), + command.ColumnModifications.Where(col => col.IsKey).Select(col => col.Value).ToList()), Columns = command.ColumnModifications.Where(col => col.IsWrite).Select(col => col.ColumnName).ToArray(), Values = ToMultidimensionalArray( - command.ColumnModifications.Where(col => col.IsWrite).Select(GetValue).ToList()), + command.ColumnModifications.Where(col => col.IsWrite).Select(col => col.Value).ToList()), IsDestructiveChange = true }; break; @@ -2281,17 +2203,8 @@ private IEnumerable GetDataOperations( // There shouldn't be any deletes using the target model Check.DebugAssert(forSource, "Delete using the target model"); - // If the entity type used TPT delete the rows in the tables to which the entity is no longer mapped - if (command.Entries.Any(en => entriesWithRemovedMappings.Contains(en)) - && !command.Entries.Any( - en => changedTableMappings!.TryGetValue(en.EntityType, out var removedTables) - && removedTables.Any(t => t.Name == command.TableName && t.Schema == command.Schema))) - { - break; - } - var keyColumns = command.ColumnModifications.Where(col => col.IsKey) - .Select(c => table.FindColumn(c.ColumnName)!); + .Select(c => c.Column!); var anyKeyColumnDropped = keyColumns.Any(c => diffContext.FindDrop(c) != null); yield return new DeleteDataOperation @@ -2303,7 +2216,7 @@ private IEnumerable GetDataOperations( ? keyColumns.Select(col => col.StoreType).ToArray() : null, KeyValues = ToMultidimensionalArray( - command.ColumnModifications.Where(col => col.IsKey).Select(GetValue).ToArray()), + command.ColumnModifications.Where(col => col.IsKey).Select(col => col.Value).ToArray()), IsDestructiveChange = true }; @@ -2320,17 +2233,6 @@ private IEnumerable GetDataOperations( } } - private static object? GetValue(IColumnModification columnModification) - { - var converter = GetValueConverter(columnModification.Property!); - var value = columnModification.UseCurrentValue - ? columnModification.Value - : columnModification.OriginalValue; - return converter != null - ? converter.ConvertToProvider(value) - : value; - } - #endregion /// diff --git a/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs b/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs index 5528830b693..6057a7deeb3 100644 --- a/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs +++ b/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs @@ -888,8 +888,10 @@ protected virtual IEnumerable GenerateModification for (var i = 0; i < operation.Values.GetLength(0); i++) { - var modificationCommand = Dependencies.ModificationCommandFactory.CreateModificationCommand( - new ModificationCommandParameters(operation.Table, operation.Schema ?? model?.GetDefaultSchema(), SensitiveLoggingEnabled)); + var modificationCommand = Dependencies.ModificationCommandFactory.CreateNonTrackedModificationCommand( + new NonTrackedModificationCommandParameters(operation.Table, operation.Schema ?? model?.GetDefaultSchema(), SensitiveLoggingEnabled)); + modificationCommand.EntityState = EntityState.Added; + for (var j = 0; j < operation.Columns.Length; j++) { var name = operation.Columns[j]; @@ -977,8 +979,10 @@ protected virtual IEnumerable GenerateModification for (var i = 0; i < operation.KeyValues.GetLength(0); i++) { - var modificationCommand = Dependencies.ModificationCommandFactory.CreateModificationCommand( - new ModificationCommandParameters(operation.Table, operation.Schema, SensitiveLoggingEnabled)); + var modificationCommand = Dependencies.ModificationCommandFactory.CreateNonTrackedModificationCommand( + new NonTrackedModificationCommandParameters(operation.Table, operation.Schema, SensitiveLoggingEnabled)); + modificationCommand.EntityState = EntityState.Deleted; + for (var j = 0; j < operation.KeyColumns.Length; j++) { var name = operation.KeyColumns[j]; @@ -1091,8 +1095,10 @@ protected virtual IEnumerable GenerateModification for (var i = 0; i < operation.KeyValues.GetLength(0); i++) { - var modificationCommand = Dependencies.ModificationCommandFactory.CreateModificationCommand( - new ModificationCommandParameters(operation.Table, operation.Schema, SensitiveLoggingEnabled)); + var modificationCommand = Dependencies.ModificationCommandFactory.CreateNonTrackedModificationCommand( + new NonTrackedModificationCommandParameters(operation.Table, operation.Schema, SensitiveLoggingEnabled)); + modificationCommand.EntityState = EntityState.Modified; + for (var j = 0; j < operation.KeyColumns.Length; j++) { var name = operation.KeyColumns[j]; @@ -1127,7 +1133,7 @@ protected virtual IEnumerable GenerateModification modificationCommand.AddColumnModification( new ColumnModificationParameters( name, originalValue: null, value, propertyMapping?.Property, columnType, typeMapping, - read: false, write: true, key: true, condition: false, + read: false, write: true, key: false, condition: false, SensitiveLoggingEnabled, propertyMapping?.Column.IsNullable)); } diff --git a/src/EFCore.Relational/Storage/RelationalDatabase.cs b/src/EFCore.Relational/Storage/RelationalDatabase.cs index 5f24e3741af..af0d7ec223d 100644 --- a/src/EFCore.Relational/Storage/RelationalDatabase.cs +++ b/src/EFCore.Relational/Storage/RelationalDatabase.cs @@ -26,6 +26,8 @@ namespace Microsoft.EntityFrameworkCore.Storage; /// public class RelationalDatabase : Database { + private IUpdateAdapter? _updateAdapter; + /// /// Initializes a new instance of the class. /// @@ -39,6 +41,8 @@ public RelationalDatabase( RelationalDependencies = relationalDependencies; } + private IUpdateAdapter UpdateAdapter => _updateAdapter ??= Dependencies.UpdateAdapterFactory.Create(); + /// /// Relational provider-specific dependencies for this service. /// @@ -51,9 +55,7 @@ public RelationalDatabase( /// The number of state entries persisted to the database. public override int SaveChanges(IList entries) => RelationalDependencies.BatchExecutor.Execute( - RelationalDependencies.BatchPreparer.BatchCommands( - entries, - Dependencies.UpdateAdapterFactory.Create()), + RelationalDependencies.BatchPreparer.BatchCommands(entries, UpdateAdapter), RelationalDependencies.Connection); /// @@ -70,9 +72,7 @@ public override Task SaveChangesAsync( IList entries, CancellationToken cancellationToken = default) => RelationalDependencies.BatchExecutor.ExecuteAsync( - RelationalDependencies.BatchPreparer.BatchCommands( - entries, - Dependencies.UpdateAdapterFactory.Create()), + RelationalDependencies.BatchPreparer.BatchCommands(entries, UpdateAdapter), RelationalDependencies.Connection, cancellationToken); } diff --git a/src/EFCore.Relational/Update/ColumnModification.cs b/src/EFCore.Relational/Update/ColumnModification.cs index e6fec449e0f..29fd216dab1 100644 --- a/src/EFCore.Relational/Update/ColumnModification.cs +++ b/src/EFCore.Relational/Update/ColumnModification.cs @@ -26,7 +26,7 @@ public class ColumnModification : IColumnModification private string? _parameterName; private string? _originalParameterName; private readonly Func? _generateParameterName; - private readonly object? _originalValue; + private object? _originalValue; private object? _value; private readonly bool _sensitiveLoggingEnabled; private List? _sharedColumnModifications; @@ -37,6 +37,7 @@ public class ColumnModification : IColumnModification /// Creation parameters. public ColumnModification(in ColumnModificationParameters columnModificationParameters) { + Column = columnModificationParameters.Column; ColumnName = columnModificationParameters.ColumnName; _originalValue = columnModificationParameters.OriginalValue; _value = columnModificationParameters.Value; @@ -61,6 +62,9 @@ public ColumnModification(in ColumnModificationParameters columnModificationPara /// public virtual IProperty? Property { get; } + /// + public virtual IColumn? Column { get; } + /// public virtual RelationalTypeMapping? TypeMapping { get; } @@ -68,16 +72,16 @@ public ColumnModification(in ColumnModificationParameters columnModificationPara public virtual bool? IsNullable { get; } /// - public virtual bool IsRead { get; } + public virtual bool IsRead { get; set; } /// - public virtual bool IsWrite { get; } + public virtual bool IsWrite { get; set; } /// - public virtual bool IsCondition { get; } + public virtual bool IsCondition { get; set; } /// - public virtual bool IsKey { get; } + public virtual bool IsKey { get; set; } /// public virtual bool UseOriginalValueParameter @@ -114,11 +118,31 @@ public virtual string? OriginalParameterName /// public virtual object? OriginalValue - => Entry == null - ? _originalValue - : Entry.SharedIdentityEntry == null - ? Entry.GetOriginalValue(Property!) - : Entry.SharedIdentityEntry.GetOriginalValue(Property!); + { + get => Entry == null + ? _originalValue + : Entry.SharedIdentityEntry == null + ? Entry.GetOriginalValue(Property!) + : Entry.SharedIdentityEntry.GetOriginalValue(Property!); + set + { + if (Entry == null) + { + _originalValue = value; + } + else + { + Entry.SetOriginalValue(Property!, value); + if (_sharedColumnModifications != null) + { + foreach (var sharedModification in _sharedColumnModifications) + { + sharedModification.OriginalValue = value; + } + } + } + } + } /// public virtual object? Value diff --git a/src/EFCore.Relational/Update/ColumnModificationParameters.cs b/src/EFCore.Relational/Update/ColumnModificationParameters.cs index 61794cc2194..a3893206cd1 100644 --- a/src/EFCore.Relational/Update/ColumnModificationParameters.cs +++ b/src/EFCore.Relational/Update/ColumnModificationParameters.cs @@ -76,6 +76,11 @@ public readonly record struct ColumnModificationParameters /// Indicates whether the column is part of a primary or alternate key. /// public bool IsKey { get; init; } + + /// + /// The column. + /// + public IColumn? Column { get; init; } /// /// The name of the column. @@ -116,6 +121,7 @@ public ColumnModificationParameters( bool sensitiveLoggingEnabled, bool? isNullable = null) { + Column = null; ColumnName = columnName; OriginalValue = originalValue; Value = value; @@ -132,6 +138,51 @@ public ColumnModificationParameters( GenerateParameterName = null; Entry = null; } + + /// + /// Creates a new instance. + /// + /// The column. + /// The original value of the property mapped to this column. + /// The current value of the property mapped to this column. + /// The property that maps to the column. + /// The relational type mapping to be used for the command parameter. + /// Indicates whether a value must be read from the database for the column. + /// Indicates whether a value must be written to the database for the column. + /// Indicates whether the column part of a primary or alternate key. + /// Indicates whether the column is used in the WHERE clause when updating. + /// Indicates whether potentially sensitive data (e.g. database values) can be logged. + /// A value indicating whether the value could be null. + public ColumnModificationParameters( + IColumn column, + object? originalValue, + object? value, + IProperty? property, + RelationalTypeMapping? typeMapping, + bool read, + bool write, + bool key, + bool condition, + bool sensitiveLoggingEnabled, + bool? isNullable = null) + { + Column = column; + ColumnName = column.Name; + OriginalValue = originalValue; + Value = value; + Property = property; + ColumnType = column.StoreType; + TypeMapping = typeMapping; + IsRead = read; + IsWrite = write; + IsKey = key; + IsCondition = condition; + SensitiveLoggingEnabled = sensitiveLoggingEnabled; + IsNullable = isNullable; + + GenerateParameterName = null; + Entry = null; + } /// /// Creates a new instance. @@ -158,6 +209,7 @@ public ColumnModificationParameters( bool columnIsCondition, bool sensitiveLoggingEnabled) { + Column = column; ColumnName = column.Name; OriginalValue = null; Value = null; diff --git a/src/EFCore.Relational/Update/IColumnModification.cs b/src/EFCore.Relational/Update/IColumnModification.cs index 41b5e9380ce..b40b20f7ba4 100644 --- a/src/EFCore.Relational/Update/IColumnModification.cs +++ b/src/EFCore.Relational/Update/IColumnModification.cs @@ -30,6 +30,11 @@ public interface IColumnModification /// public IProperty? Property { get; } + /// + /// The column. + /// + public IColumn? Column { get; } + /// /// The relational type mapping for the column. /// @@ -43,22 +48,22 @@ public interface IColumnModification /// /// Indicates whether a value must be read from the database for the column. /// - public bool IsRead { get; } + public bool IsRead { get; set; } /// /// Indicates whether a value must be written to the database for the column. /// - public bool IsWrite { get; } + public bool IsWrite { get; set; } /// /// Indicates whether the column is used in the WHERE clause when updating. /// - public bool IsCondition { get; } + public bool IsCondition { get; set; } /// /// Indicates whether the column is part of a primary or alternate key. /// - public bool IsKey { get; } + public bool IsKey { get; set; } /// /// Indicates whether the original value of the property must be passed as a parameter to the SQL. @@ -110,7 +115,7 @@ public interface IColumnModification /// /// The original value of the property mapped to this column. /// - public object? OriginalValue { get; } + public object? OriginalValue { get; set; } /// /// Gets or sets the current value of the property mapped to this column. diff --git a/src/EFCore.Relational/Update/IModificationCommand.cs b/src/EFCore.Relational/Update/IModificationCommand.cs index 6103ecd1ff4..adcb0a36213 100644 --- a/src/EFCore.Relational/Update/IModificationCommand.cs +++ b/src/EFCore.Relational/Update/IModificationCommand.cs @@ -23,11 +23,4 @@ public interface IModificationCommand : IReadOnlyModificationCommand /// Entry object. /// Whether this is the main entry. Only one main entry can be added to a given command. public void AddEntry(IUpdateEntry entry, bool mainEntry); - - /// - /// Creates a new and add it to this command. - /// - /// Creation parameters. - /// The new instance. - IColumnModification AddColumnModification(in ColumnModificationParameters columnModificationParameters); } diff --git a/src/EFCore.Relational/Update/IModificationCommandFactory.cs b/src/EFCore.Relational/Update/IModificationCommandFactory.cs index b89d8831b38..b2cbf56e6af 100644 --- a/src/EFCore.Relational/Update/IModificationCommandFactory.cs +++ b/src/EFCore.Relational/Update/IModificationCommandFactory.cs @@ -31,4 +31,12 @@ public interface IModificationCommandFactory /// A new instance. IModificationCommand CreateModificationCommand( in ModificationCommandParameters modificationCommandParameters); + + /// + /// Creates a new database CUD command. + /// + /// The creation parameters. + /// A new instance. + INonTrackedModificationCommand CreateNonTrackedModificationCommand( + in NonTrackedModificationCommandParameters modificationCommandParameters); } diff --git a/src/EFCore.Relational/Update/INonTrackedModificationCommand.cs b/src/EFCore.Relational/Update/INonTrackedModificationCommand.cs new file mode 100644 index 00000000000..c847f492410 --- /dev/null +++ b/src/EFCore.Relational/Update/INonTrackedModificationCommand.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Update; + +/// +/// +/// Represents a mutable conceptual database command to insert/update/delete a row. +/// +/// +/// This type is typically used by database providers; it is generally not used in application code. +/// +/// +/// +/// See Implementation of database providers and extensions +/// for more information and examples. +/// +public interface INonTrackedModificationCommand : IReadOnlyModificationCommand +{ + /// + /// The that indicates whether the row will be + /// inserted (), + /// updated (), + /// or deleted ((). + /// + new public EntityState EntityState { get; set; } + + /// + /// Creates a new and add it to this command. + /// + /// Creation parameters. + /// The new instance. + IColumnModification AddColumnModification(in ColumnModificationParameters columnModificationParameters); +} diff --git a/src/EFCore.Relational/Update/Internal/ColumnAccessorsFactory.cs b/src/EFCore.Relational/Update/Internal/ColumnAccessorsFactory.cs index 6cf9b492358..7373c309dda 100644 --- a/src/EFCore.Relational/Update/Internal/ColumnAccessorsFactory.cs +++ b/src/EFCore.Relational/Update/Internal/ColumnAccessorsFactory.cs @@ -71,7 +71,9 @@ private static ColumnAccessors CreateGeneric(IColumn column) var modification = c.ColumnModifications.FirstOrDefault(m => m.ColumnName == column.Name); return modification == null ? (default(TColumn)!, false) - : ((TColumn)modification.Value!, true); + : modification.Value == null + ? (default(TColumn)!, false) + : ((TColumn)modification.Value!, true); }; private static Func CreateOriginalValueGetter(IColumn column) @@ -112,6 +114,8 @@ private static ColumnAccessors CreateGeneric(IColumn column) var modification = c.ColumnModifications.FirstOrDefault(m => m.ColumnName == column.Name); return modification == null ? (default(TColumn)!, false) - : ((TColumn)modification.OriginalValue!, true); + : modification.OriginalValue == null + ? (default(TColumn)!, false) + : ((TColumn)modification.OriginalValue!, true); }; } diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs index bb6e7d6dd68..b297ad3c306 100644 --- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs +++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs @@ -274,7 +274,7 @@ private static void AddUnchangedSharingEntries( /// 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 IReadOnlyList> TopologicalSort( + public virtual IReadOnlyList> TopologicalSort( IEnumerable commands) { _modificationCommandGraph.Clear(); diff --git a/src/EFCore.Relational/Update/Internal/IRowIdentityMap.cs b/src/EFCore.Relational/Update/Internal/IRowIdentityMap.cs new file mode 100644 index 00000000000..da7b6edb358 --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/IRowIdentityMap.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Update.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 interface IRowIdentityMap +{ + /// + /// 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. + /// + IEnumerable Rows { 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. + /// + INonTrackedModificationCommand? FindCommand(object?[] keyValues); + + /// + /// 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. + /// + void Add(object?[] keyValues, INonTrackedModificationCommand command); + + /// + /// 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. + /// + void Remove(INonTrackedModificationCommand command); + + /// + /// 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. + /// + void Clear(); +} diff --git a/src/EFCore.Relational/Update/Internal/IRowIdentityMapFactory.cs b/src/EFCore.Relational/Update/Internal/IRowIdentityMapFactory.cs new file mode 100644 index 00000000000..346354050ea --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/IRowIdentityMapFactory.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Update.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 interface IRowIdentityMapFactory +{ + /// + /// 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. + /// + IRowIdentityMap Create(IUniqueConstraint key); +} diff --git a/src/EFCore.Relational/Update/Internal/ModificationCommandComparer.cs b/src/EFCore.Relational/Update/Internal/ModificationCommandComparer.cs index 489c82bfcde..7293e380fc4 100644 --- a/src/EFCore.Relational/Update/Internal/ModificationCommandComparer.cs +++ b/src/EFCore.Relational/Update/Internal/ModificationCommandComparer.cs @@ -83,6 +83,30 @@ public virtual int Compare(IReadOnlyModificationCommand? x, IReadOnlyModificatio } } } + else + { + var xKey = x.Table?.PrimaryKey; + var yKey = y.Table?.PrimaryKey; + if (xKey != null && yKey != null) + { + for (var i = 0; i < xKey.Columns.Count; i++) + { + var xKeyColumn = xKey.Columns[i]; + + if (x.ColumnModifications.First(m => m.ColumnName == xKeyColumn.Name).Value is not IComparable xModification + || y.ColumnModifications.First(m => m.ColumnName == xKeyColumn.Name).Value is not IComparable yModification) + { + continue; + } + + result = xModification.CompareTo(yModification); + if (result != 0) + { + return result; + } + } + } + } return result; } diff --git a/src/EFCore.Relational/Update/Internal/ModificationCommandFactory.cs b/src/EFCore.Relational/Update/Internal/ModificationCommandFactory.cs index 547945ebe16..dc3901a4667 100644 --- a/src/EFCore.Relational/Update/Internal/ModificationCommandFactory.cs +++ b/src/EFCore.Relational/Update/Internal/ModificationCommandFactory.cs @@ -20,4 +20,14 @@ public class ModificationCommandFactory : IModificationCommandFactory public virtual IModificationCommand CreateModificationCommand( in ModificationCommandParameters modificationCommandParameters) => new ModificationCommand(modificationCommandParameters); + + /// + /// 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 INonTrackedModificationCommand CreateNonTrackedModificationCommand( + in NonTrackedModificationCommandParameters modificationCommandParameters) + => new ModificationCommand(modificationCommandParameters); } diff --git a/src/EFCore.Relational/Update/Internal/RowIdentityMap.cs b/src/EFCore.Relational/Update/Internal/RowIdentityMap.cs new file mode 100644 index 00000000000..39ecf660f1f --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/RowIdentityMap.cs @@ -0,0 +1,115 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class RowIdentityMap : IRowIdentityMap + where TKey : notnull +{ + private readonly IUniqueConstraint _key; + private readonly Dictionary _identityMap; + private readonly IRowKeyValueFactory _principalKeyValueFactory; + + /// + /// 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 RowIdentityMap(IUniqueConstraint key) + { + _key = key; + _principalKeyValueFactory = (IRowKeyValueFactory)((UniqueConstraint)_key).GetRowKeyValueFactory(); + _identityMap = new Dictionary(_principalKeyValueFactory.EqualityComparer); + } + + /// + /// 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 Rows => _identityMap.Values; + + /// + /// 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 INonTrackedModificationCommand? FindCommand(object?[] keyValues) + { + var key = _principalKeyValueFactory.CreateKeyValue(keyValues); + return key != null && _identityMap.TryGetValue(key, out var command) ? command : 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 void Add(object?[] keyValues, INonTrackedModificationCommand command) + => Add(_principalKeyValueFactory.CreateKeyValue(keyValues), command); + + /// + /// 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 void Add(TKey key, INonTrackedModificationCommand command) + { +#if DEBUG + if (_identityMap.TryGetValue(key, out var existingCommand)) + { + Check.DebugAssert(existingCommand == command, $"Command with key {key} already added"); + } +#endif + + _identityMap[key] = command; + } + + /// + /// 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 void Remove(INonTrackedModificationCommand command) + => Remove(_principalKeyValueFactory.CreateKeyValue(command), command); + + /// + /// 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. + /// + private void Remove(TKey key, INonTrackedModificationCommand command) + { + if (_identityMap.TryGetValue(key, out var existingEntry) + && existingEntry == command) + { + _identityMap.Remove(key); + } + } + + /// + /// 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 void Clear() + { + _identityMap.Clear(); + } +} diff --git a/src/EFCore.Relational/Update/Internal/RowIdentityMapFactory.cs b/src/EFCore.Relational/Update/Internal/RowIdentityMapFactory.cs new file mode 100644 index 00000000000..763d8c73f85 --- /dev/null +++ b/src/EFCore.Relational/Update/Internal/RowIdentityMapFactory.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Update.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class RowIdentityMapFactory : IRowIdentityMapFactory +{ + /// + /// 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 IRowIdentityMap Create(IUniqueConstraint key) + => (IRowIdentityMap)_createMethod + .MakeGenericMethod(key.Columns.Count == 1 ? key.Columns.First().ProviderClrType : typeof(object[])) + .Invoke(null, new object[] { key })!; + + private readonly static MethodInfo _createMethod = typeof(RowIdentityMapFactory).GetTypeInfo() + .GetDeclaredMethod(nameof(CreateRowIdentityMap))!; + + [UsedImplicitly] + private static IRowIdentityMap CreateRowIdentityMap(IUniqueConstraint key) + where TKey : notnull + => new RowIdentityMap(key); +} diff --git a/src/EFCore.Relational/Update/ModificationCommand.cs b/src/EFCore.Relational/Update/ModificationCommand.cs index 280eec7b7b4..549c5ca0459 100644 --- a/src/EFCore.Relational/Update/ModificationCommand.cs +++ b/src/EFCore.Relational/Update/ModificationCommand.cs @@ -17,7 +17,7 @@ namespace Microsoft.EntityFrameworkCore.Update; /// See Implementation of database providers and extensions /// for more information and examples. /// -public class ModificationCommand : IModificationCommand +public class ModificationCommand : IModificationCommand, INonTrackedModificationCommand { private readonly Func? _generateParameterName; private readonly bool _sensitiveLoggingEnabled; @@ -26,6 +26,7 @@ public class ModificationCommand : IModificationCommand private List? _columnModifications; private bool _requiresResultPropagation; private bool _mainEntryAdded; + private EntityState _entityState; private readonly IDiagnosticsLogger? _logger; /// @@ -44,6 +45,19 @@ public ModificationCommand(in ModificationCommandParameters modificationCommandP EntityState = EntityState.Modified; } + /// + /// Initializes a new instance. + /// + /// Creation parameters. + public ModificationCommand(in NonTrackedModificationCommandParameters modificationCommandParameters) + { + Table = modificationCommandParameters.Table; + TableName = modificationCommandParameters.TableName; + Schema = modificationCommandParameters.Schema; + _sensitiveLoggingEnabled = modificationCommandParameters.SensitiveLoggingEnabled; + EntityState = EntityState.Modified; + } + /// public virtual ITable? Table { get; } @@ -58,7 +72,11 @@ public virtual IReadOnlyList Entries => _entries; /// - public virtual EntityState EntityState { get; private set; } + public virtual EntityState EntityState + { + get => _entityState; + set => _entityState = value; + } /// /// Indicates whether the database will return values for some mapped properties @@ -137,7 +155,7 @@ public virtual void AddEntry(IUpdateEntry entry, bool mainEntry) _mainEntryAdded = true; _entries.Insert(0, entry); - EntityState = entry.SharedIdentityEntry == null + _entityState = entry.SharedIdentityEntry == null ? entry.EntityState : entry.SharedIdentityEntry.EntityType == entry.EntityType || entry.SharedIdentityEntry.EntityType.GetTableMappings() diff --git a/src/EFCore.Relational/Update/ModificationCommandParameters.cs b/src/EFCore.Relational/Update/ModificationCommandParameters.cs index d8bb902882d..14ec7358281 100644 --- a/src/EFCore.Relational/Update/ModificationCommandParameters.cs +++ b/src/EFCore.Relational/Update/ModificationCommandParameters.cs @@ -17,32 +17,6 @@ namespace Microsoft.EntityFrameworkCore.Update; /// public readonly record struct ModificationCommandParameters { - /// - /// Creates a new instance. - /// - /// The name of the table containing the data to be modified. - /// The schema containing the table, or to use the default schema. - /// Indicates whether potentially sensitive data (e.g. database values) can be logged. - /// An for . - /// A delegate to generate parameter names. - /// An for . - public ModificationCommandParameters( - string tableName, - string? schemaName, - bool sensitiveLoggingEnabled, - IComparer? comparer = null, - Func? generateParameterName = null, - IDiagnosticsLogger? logger = null) - { - Table = null; - TableName = tableName; - Schema = schemaName; - GenerateParameterName = generateParameterName; - SensitiveLoggingEnabled = sensitiveLoggingEnabled; - Comparer = comparer; - Logger = logger; - } - /// /// Creates a new instance. /// diff --git a/src/EFCore.Relational/Update/NonTrackedModificationCommandParameters.cs b/src/EFCore.Relational/Update/NonTrackedModificationCommandParameters.cs new file mode 100644 index 00000000000..d82d77adcc1 --- /dev/null +++ b/src/EFCore.Relational/Update/NonTrackedModificationCommandParameters.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Update; + +/// +/// +/// Parameters for creating a instance. +/// +/// +/// This type is typically used by database providers; it is generally not used in application code. +/// +/// +/// +/// See Implementation of database providers and extensions +/// for more information and examples. +/// +public readonly record struct NonTrackedModificationCommandParameters +{ + /// + /// Creates a new instance. + /// + /// The name of the table containing the data to be modified. + /// The schema containing the table, or to use the default schema. + /// Indicates whether potentially sensitive data (e.g. database values) can be logged. + public NonTrackedModificationCommandParameters( + string tableName, + string? schemaName, + bool sensitiveLoggingEnabled) + { + Table = null; + TableName = tableName; + Schema = schemaName; + SensitiveLoggingEnabled = sensitiveLoggingEnabled; + } + + /// + /// Creates a new instance. + /// + /// The table containing the data to be modified. + /// Indicates whether potentially sensitive data (e.g. database values) can be logged. + public NonTrackedModificationCommandParameters( + ITable table, + bool sensitiveLoggingEnabled) + { + Table = table; + TableName = table.Name; + Schema = table.Schema; + SensitiveLoggingEnabled = sensitiveLoggingEnabled; + } + + /// + /// The name of the table containing the data to be modified. + /// + public string TableName { get; init; } + + /// + /// The schema containing the table, or to use the default schema. + /// + public string? Schema { get; init; } + + /// + /// The table containing the data to be modified. + /// + public ITable? Table { get; init; } + + /// + /// Indicates whether potentially sensitive data (e.g. database values) can be logged. + /// + public bool SensitiveLoggingEnabled { get; init; } +} diff --git a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs index e60e46461df..f93cc8a1e3c 100644 --- a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs @@ -156,7 +156,7 @@ protected virtual Expression CreateSnapshotExpression( return UseEntityVariable && entityVariable != null - ? (Expression)Expression.Block( + ? Expression.Block( new List { entityVariable }, new List { diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 300063f0831..cdc344f2ff8 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -807,7 +807,7 @@ protected virtual void ValidateFieldMapping( foreach (var propertyBase in properties) { if (!propertyBase.TryGetMemberInfo( - forConstruction: true, + forMaterialization: true, forSet: true, memberInfo: out _, errorMessage: out var errorMessage)) @@ -816,7 +816,7 @@ protected virtual void ValidateFieldMapping( } if (!propertyBase.TryGetMemberInfo( - forConstruction: false, + forMaterialization: false, forSet: true, memberInfo: out _, errorMessage: out errorMessage)) @@ -825,7 +825,7 @@ protected virtual void ValidateFieldMapping( } if (!propertyBase.TryGetMemberInfo( - forConstruction: false, + forMaterialization: false, forSet: false, memberInfo: out _, errorMessage: out errorMessage)) diff --git a/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs b/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs index 10763a85b30..889cf109bc3 100644 --- a/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs +++ b/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs @@ -118,8 +118,8 @@ private static IClrCollectionAccessor CreateGeneric GetDerivedServiceProperties() var data = new List>(); var valueConverters = new Dictionary(StringComparer.Ordinal); - var properties = GetProperties() - .Concat(GetNavigations()) - .Concat(GetSkipNavigations()) - .ToDictionary(p => p.Name); foreach (var rawSeed in _data) { var seed = new Dictionary(StringComparer.Ordinal); @@ -3048,8 +3044,16 @@ public virtual IEnumerable GetDerivedServiceProperties() if (ClrType.IsAssignableFrom(type)) { // non-anonymous type - foreach (var propertyBase in properties.Values) + var properties = GetProperties() + .Concat(GetNavigations()) + .Concat(GetSkipNavigations()); + foreach (var propertyBase in properties) { + if (propertyBase.IsShadowProperty()) + { + continue; + } + ValueConverter? valueConverter = null; if (providerValues && propertyBase is IProperty property @@ -3059,7 +3063,7 @@ public virtual IEnumerable GetDerivedServiceProperties() valueConverters[propertyBase.Name] = valueConverter; } - propertyBase.TryGetMemberInfo(forConstruction: false, forSet: false, out var memberInfo, out _); + var memberInfo = propertyBase.GetMemberInfo(forMaterialization: false, forSet: false); object? value = null; switch (memberInfo) @@ -3097,6 +3101,10 @@ public virtual IEnumerable GetDerivedServiceProperties() else { // anonymous type + var properties = GetProperties() + .Concat(GetNavigations()) + .Concat(GetSkipNavigations()) + .ToDictionary(p => p.Name); foreach (var memberInfo in type.GetMembersInHierarchy()) { if (!properties.TryGetValue(memberInfo.GetSimpleMemberName(), out var propertyBase)) @@ -3129,6 +3137,17 @@ public virtual IEnumerable GetDerivedServiceProperties() return data; } + /// + /// 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 GetRawSeedData() + => _data == null + ? Enumerable.Empty() + : _data; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs b/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs index a0c55d0dbfc..1f83077aee1 100644 --- a/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs +++ b/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs @@ -108,7 +108,7 @@ public static bool IsIndexerProperty(this PropertyBase propertyBase) /// public static bool TryGetMemberInfo( this IPropertyBase propertyBase, - bool forConstruction, + bool forMaterialization, bool forSet, out MemberInfo? memberInfo, out string? errorMessage) @@ -128,7 +128,7 @@ public static bool TryGetMemberInfo( var mode = propertyBase.GetPropertyAccessMode(); - if (forConstruction) + if (forMaterialization) { switch (mode) { diff --git a/src/EFCore/Storage/DatabaseDependencies.cs b/src/EFCore/Storage/DatabaseDependencies.cs index 07373a2bef2..6a353c9a789 100644 --- a/src/EFCore/Storage/DatabaseDependencies.cs +++ b/src/EFCore/Storage/DatabaseDependencies.cs @@ -59,7 +59,7 @@ public DatabaseDependencies( public IQueryCompilationContextFactory QueryCompilationContextFactory { get; init; } /// - /// Factory for creating model data tracker. + /// Factory for creating update adapters. /// public IUpdateAdapterFactory UpdateAdapterFactory { get; init; } } diff --git a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs index f77b3cd890c..9e878820428 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs @@ -86,8 +86,7 @@ var migrationAssembly TestServiceFactory.Instance.Create()), new MigrationsAnnotationProvider( new MigrationsAnnotationProviderDependencies()), - services.GetRequiredService(), - services.GetRequiredService(), + services.GetRequiredService(), services.GetRequiredService()), idGenerator, new MigrationsCodeGeneratorSelector( diff --git a/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs index d0544ef6232..f29e8ad8e23 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs @@ -215,7 +215,7 @@ public virtual void GenerateNextSequenceValueOperation_correctly_handles_schemas protected abstract string RowsAffected { get; } - protected virtual IModificationCommandFactory CreateMutableModificationCommandFactory() + protected virtual IModificationCommandFactory CreateModificationCommandFactory() => new ModificationCommandFactory(); protected virtual string Identity @@ -236,7 +236,7 @@ protected virtual string SchemaPrefix protected virtual string GetIdentityWhereCondition(string columnName) => OpenDelimiter + columnName + CloseDelimiter + " = " + Identity; - protected IModificationCommand CreateInsertCommand(bool identityKey = true, bool isComputed = true, bool defaultsOnly = false) + protected INonTrackedModificationCommand CreateInsertCommand(bool identityKey = true, bool isComputed = true, bool defaultsOnly = false) { var model = GetDuckModel(); var stateManager = TestHelpers.CreateContextServices(model).GetRequiredService(); @@ -278,7 +278,7 @@ protected IModificationCommand CreateInsertCommand(bool identityKey = true, bool return CreateModificationCommand("Ducks", Schema, entry, columnModifications, false); } - protected IModificationCommand CreateUpdateCommand(bool isComputed = true, bool concurrencyToken = true) + protected INonTrackedModificationCommand CreateUpdateCommand(bool isComputed = true, bool concurrencyToken = true) { var model = GetDuckModel(); var stateManager = TestHelpers.CreateContextServices(model).GetRequiredService(); @@ -315,7 +315,7 @@ protected IModificationCommand CreateUpdateCommand(bool isComputed = true, bool return CreateModificationCommand("Ducks", Schema, entry, columnModifications, false); } - protected IModificationCommand CreateDeleteCommand(bool concurrencyToken = true) + protected INonTrackedModificationCommand CreateDeleteCommand(bool concurrencyToken = true) { var model = GetDuckModel(); var stateManager = TestHelpers.CreateContextServices(model).GetRequiredService(); @@ -358,20 +358,18 @@ protected class Duck public byte[] ConcurrencyToken { get; set; } } - private IModificationCommand CreateModificationCommand( + private INonTrackedModificationCommand CreateModificationCommand( string name, string schema, InternalEntityEntry entry, IReadOnlyList columnModifications, bool sensitiveLoggingEnabled) { - var modificationCommandParameters = new ModificationCommandParameters( + var modificationCommandParameters = new NonTrackedModificationCommandParameters( name, schema, sensitiveLoggingEnabled); - var modificationCommand = CreateMutableModificationCommandFactory().CreateModificationCommand( + var modificationCommand = CreateModificationCommandFactory().CreateNonTrackedModificationCommand( modificationCommandParameters); - modificationCommand.AddEntry(entry, mainEntry: true); - foreach (var columnModification in columnModifications) { modificationCommand.AddColumnModification(columnModification); diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index 9daaca35001..ffc154affbc 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -3280,18 +3280,34 @@ public void Alter_primary_key_column_order_with_seed_data() x => x.HasKey("RavenId", "Id")), operations => { - Assert.Equal(2, operations.Count); + Assert.Equal(4, operations.Count); var dropOperation = Assert.IsType(operations[0]); Assert.Equal("dbo", dropOperation.Schema); Assert.Equal("Raven", dropOperation.Table); Assert.Equal("PK_Raven", dropOperation.Name); - var addOperation = Assert.IsType(operations[1]); + var deleteDataOperation = Assert.IsType(operations[1]); + Assert.Null(deleteDataOperation.KeyColumnTypes); + Assert.Equal(new[] { "Id", "RavenId" }, deleteDataOperation.KeyColumns); + AssertMultidimensionalArray( + deleteDataOperation.KeyValues, + v => Assert.Equal(42, v), + v => Assert.Equal("42", v)); + + var addOperation = Assert.IsType(operations[2]); Assert.Equal("dbo", addOperation.Schema); Assert.Equal("Raven", addOperation.Table); Assert.Equal("PK_Raven", addOperation.Name); Assert.Equal(new[] { "RavenId", "Id" }, addOperation.Columns); + + var insertDataOperation = Assert.IsType(operations[3]); + Assert.Equal("Raven", insertDataOperation.Table); + Assert.Equal(new[] { "Id", "RavenId" }, insertDataOperation.Columns); + AssertMultidimensionalArray( + insertDataOperation.Values, + v => Assert.Equal(42, v), + v => Assert.Equal("42", v)); }, skipSourceConventions: true); @@ -5821,6 +5837,20 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() v => Assert.Null(v)); }, o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Table); + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(11, v), + v => Assert.Null(v), + v => Assert.Equal(12, v), + v => Assert.Null(v), + v => Assert.Equal(13, v), + v => Assert.Null(v)); + }, + o => { var operation = Assert.IsType(o); Assert.Equal("Dogs", operation.Table); @@ -5833,6 +5863,15 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() v => Assert.Equal(31, v)); }, o => + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Table); + Assert.Equal(new[] { "Id" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(31, v)); + }, + o => { var operation = Assert.IsType(o); Assert.Equal("Dogs", operation.Table); @@ -6002,6 +6041,7 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() Assert.Equal("PreyId", operation.Name); Assert.Equal("Animal", operation.Table); Assert.Equal(typeof(int), operation.ClrType); + Assert.True(operation.IsNullable); }, o => { @@ -6013,11 +6053,12 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() operation.KeyValues, v => Assert.Equal(11, v)); - Assert.Equal(new[] { "Discriminator" }, operation.Columns); + Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal("Cat", v)); + v => Assert.Equal("Cat", v), + v => Assert.Null(v)); }, o => { @@ -6027,13 +6068,14 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(13, v)); + v => Assert.Equal(12, v)); - Assert.Equal(new[] { "Discriminator", "MouseId" }, operation.Columns); + Assert.Equal(new[] { "Discriminator", "MouseId", "PreyId" }, operation.Columns); Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, v => Assert.Equal("Cat", v), + v => Assert.Equal(32, v), v => Assert.Null(v)); }, o => @@ -6044,14 +6086,15 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(21, v)); + v => Assert.Equal(13, v)); - Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); + Assert.Equal(new[] { "Discriminator", "MouseId", "PreyId" }, operation.Columns); Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal("Dog", v), - v => Assert.Equal(31, v)); + v => Assert.Equal("Cat", v), + v => Assert.Null(v), + v => Assert.Null(v)); }, o => { @@ -6061,24 +6104,14 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(31, v)); + v => Assert.Equal(21, v)); - Assert.Equal(new[] { "Discriminator" }, operation.Columns); + Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal("Mouse", v)); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id", "Discriminator", "MouseId" }, operation.Columns); - AssertMultidimensionalArray( - operation.Values, - v => Assert.Equal(32, v), - v => Assert.Equal("Mouse", v), - v => Assert.Null(v)); + v => Assert.Equal("Dog", v), + v => Assert.Equal(31, v)); }, o => { @@ -6088,13 +6121,13 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(12, v)); + v => Assert.Equal(22, v)); - Assert.Equal(new[] { "Discriminator", "MouseId" }, operation.Columns); + Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal("Cat", v), + v => Assert.Equal("Dog", v), v => Assert.Equal(32, v)); }, o => @@ -6105,14 +6138,24 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(22, v)); + v => Assert.Equal(31, v)); - Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); + Assert.Equal(new[] { "Discriminator" }, operation.Columns); Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal("Dog", v), - v => Assert.Equal(32, v)); + v => Assert.Equal("Mouse", v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id", "Discriminator", "MouseId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(32, v), + v => Assert.Equal("Mouse", v), + v => Assert.Null(v)); }, o => { @@ -6444,6 +6487,20 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() v => Assert.Null(v)); }, o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Table); + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(11, v), + v => Assert.Null(v), + v => Assert.Equal(12, v), + v => Assert.Null(v), + v => Assert.Equal(13, v), + v => Assert.Null(v)); + }, + o => { var operation = Assert.IsType(o); Assert.Equal("Dogs", operation.Table); @@ -6456,6 +6513,15 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() v => Assert.Equal(31, v)); }, o => + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Table); + Assert.Equal(new[] { "Id" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(31, v)); + }, + o => { var operation = Assert.IsType(o); Assert.Equal("Dogs", operation.Table); @@ -6561,65 +6627,65 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() o => { var operation = Assert.IsType(o); - Assert.Equal("Dogs", operation.Table); - + Assert.Equal("Animal", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(21, v)); + v => Assert.Equal(11, v)); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Dogs", operation.Table); - + Assert.Equal("Animal", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(22, v)); + v => Assert.Equal(12, v)); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Dogs", operation.Table); - + Assert.Equal("Animal", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(23, v)); + v => Assert.Equal(13, v)); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(11, v)); + v => Assert.Equal(21, v)); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(12, v)); + v => Assert.Equal(22, v)); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(13, v)); + v => Assert.Equal(23, v)); }, o => { @@ -6655,22 +6721,22 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() { var operation = Assert.IsType(o); Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id" }, operation.KeyColumns); Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(33, v)); + v => Assert.Equal(31, v)); }, o => { var operation = Assert.IsType(o); Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(31, v)); + v => Assert.Equal(33, v)); }, o => { @@ -6767,8 +6833,7 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); })); - // Seeding not supported yet - //[ConditionalFact] + [ConditionalFact] public void Change_TPH_to_TPC_with_FKs_and_seed_data() => Execute( modelBuilder => @@ -6898,6 +6963,66 @@ public void Change_TPH_to_TPC_with_FKs_and_seed_data() Assert.Equal("Animal", operation.Table); }, o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(11, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(12, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(13, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(21, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(22, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(31, v)); + }, + o => { var operation = Assert.IsType(o); Assert.Equal("Animal", operation.Table); @@ -7029,100 +7154,92 @@ public void Change_TPH_to_TPC_with_FKs_and_seed_data() { var operation = Assert.IsType(o); Assert.Equal("Cats", operation.Table); - Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + Assert.Equal(new[] { "Id", "MouseId", "PreyId" }, operation.Columns); Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, v => Assert.Equal(12, v), - v => Assert.Null(v)); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("Cats", operation.Table); - Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); - Assert.Null(operation.ColumnTypes); - AssertMultidimensionalArray( - operation.Values, + v => Assert.Null(v), + v => Assert.Null(v), v => Assert.Equal(13, v), - v => Assert.Equal(32, v)); + v => Assert.Equal(32, v), + v => Assert.Null(v)); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "Id", "MouseId", "PreyId" }, operation.Columns); AssertMultidimensionalArray( operation.Values, + v => Assert.Equal(21, v), + v => Assert.Null(v), + v => Assert.Equal(31, v), + v => Assert.Equal(22, v), + v => Assert.Null(v), + v => Assert.Equal(33, v), v => Assert.Equal(23, v), + v => Assert.Null(v), v => Assert.Null(v)); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); + Assert.Equal("Mice", operation.Table); Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); AssertMultidimensionalArray( operation.Values, + v => Assert.Equal(31, v), + v => Assert.Null(v), v => Assert.Equal(33, v), v => Assert.Null(v)); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Dogs", operation.Table); - - Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + Assert.Equal("Cats", operation.Table); + Assert.Equal(new[] { "Id", "MouseId", "PreyId" }, operation.Columns); Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal(21, v), - v => Assert.Equal(31, v)); + v => Assert.Equal(11, v), + v => Assert.Equal(31, v), + v => Assert.Null(v)); }, o => { - var operation = Assert.IsType(o); + var operation = Assert.IsType(o); + Assert.Equal("IX_Dogs_MouseId", operation.Name); Assert.Equal("Dogs", operation.Table); - - Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); - Assert.Null(operation.ColumnTypes); - AssertMultidimensionalArray( - operation.Values, - v => Assert.Equal(22, v), - v => Assert.Equal(33, v)); + Assert.Equal(new[] { "MouseId" }, operation.Columns); }, o => { - var operation = Assert.IsType(o); + var operation = Assert.IsType(o); + Assert.Equal("IX_Dogs_PreyId", operation.Name); Assert.Equal("Dogs", operation.Table); - Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); - AssertMultidimensionalArray( - operation.Values, - v => Assert.Equal(23, v), - v => Assert.Null(v)); + Assert.Equal(new[] { "PreyId" }, operation.Columns); }, o => { - var operation = Assert.IsType(o); - Assert.Equal("Mice", operation.Table); - Assert.Equal(new[] { "Id" }, operation.Columns); - AssertMultidimensionalArray( - operation.Values, - v => Assert.Equal(33, v)); + var operation = Assert.IsType(o); + Assert.Equal("IX_Cats_MouseId", operation.Name); + Assert.Equal("Cats", operation.Table); + Assert.Equal(new[] { "MouseId" }, operation.Columns); }, o => { var operation = Assert.IsType(o); - Assert.Equal("IX_Dogs_PreyId", operation.Name); - Assert.Equal("Dogs", operation.Table); + Assert.Equal("IX_Cats_PreyId", operation.Name); + Assert.Equal("Cats", operation.Table); Assert.Equal(new[] { "PreyId" }, operation.Columns); }, o => { var operation = Assert.IsType(o); - Assert.Equal("IX_Cats_PreyId", operation.Name); - Assert.Equal("Cats", operation.Table); - Assert.Equal(new[] { "PreyId" }, operation.Columns); + Assert.Equal("IX_Mice_MouseId", operation.Name); + Assert.Equal("Mice", operation.Table); + Assert.Equal(new[] { "MouseId" }, operation.Columns); }, o => { @@ -7137,20 +7254,10 @@ public void Change_TPH_to_TPC_with_FKs_and_seed_data() o => { var operation = Assert.IsType(o); - Assert.Equal("FK_Dogs_Animal", operation.Name); - Assert.Equal("Dogs", operation.Table); - Assert.Equal("Animal", operation.PrincipalTable); - Assert.Equal(new[] { "Id" }, operation.Columns); - Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); - Assert.Equal(ReferentialAction.Cascade, operation.OnDelete); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); + Assert.Equal("FK_Dogs_Mice_MouseId", operation.Name); Assert.Equal("Dogs", operation.Table); - Assert.Equal("Animal", operation.PrincipalTable); - Assert.Equal(new[] { "PreyId" }, operation.Columns); + Assert.Equal("Mice", operation.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, operation.Columns); Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); }), @@ -7165,13 +7272,7 @@ public void Change_TPH_to_TPC_with_FKs_and_seed_data() o => { var operation = Assert.IsType(o); - Assert.Equal("FK_Dogs_Animal", operation.Name); - Assert.Equal("Dogs", operation.Table); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); + Assert.Equal("FK_Dogs_Mice_MouseId", operation.Name); Assert.Equal("Dogs", operation.Table); }, o => @@ -7185,6 +7286,12 @@ public void Change_TPH_to_TPC_with_FKs_and_seed_data() Assert.Equal("Mice", operation.Name); }, o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Dogs_MouseId", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => { var operation = Assert.IsType(o); Assert.Equal("IX_Dogs_PreyId", operation.Name); @@ -7225,31 +7332,9 @@ public void Change_TPH_to_TPC_with_FKs_and_seed_data() }, o => { - var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(23, v)); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(33, v)); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("Discriminator", operation.Name); - Assert.Equal("Animal", operation.Table); - Assert.Equal(typeof(string), operation.ClrType); + var operation = Assert.IsType(o); + Assert.Equal("MouseId", operation.Name); + Assert.Equal("Dogs", operation.Table); }, o => { @@ -7267,113 +7352,52 @@ public void Change_TPH_to_TPC_with_FKs_and_seed_data() }, o => { - var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(11, v)); - - Assert.Equal(new[] { "Discriminator" }, operation.Columns); - Assert.Null(operation.ColumnTypes); - AssertMultidimensionalArray( - operation.Values, - v => Assert.Equal("Cat", v)); - }, - o => - { - var operation = Assert.IsType(o); + var operation = Assert.IsType(o); Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(13, v)); - - Assert.Equal(new[] { "Discriminator", "MouseId" }, operation.Columns); - Assert.Null(operation.ColumnTypes); + Assert.Equal(new[] { "Id", "Discriminator", "MouseId", "PreyId" }, operation.Columns); AssertMultidimensionalArray( operation.Values, + v => Assert.Equal(13, v), v => Assert.Equal("Cat", v), + v => Assert.Null(v), v => Assert.Null(v)); }, o => - { - var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(21, v)); - - Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); - Assert.Null(operation.ColumnTypes); - AssertMultidimensionalArray( - operation.Values, - v => Assert.Equal("Dog", v), - v => Assert.Equal(31, v)); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(31, v)); - - Assert.Equal(new[] { "Discriminator" }, operation.Columns); - Assert.Null(operation.ColumnTypes); - AssertMultidimensionalArray( - operation.Values, - v => Assert.Equal("Mouse", v)); - }, - o => { var operation = Assert.IsType(o); Assert.Equal("Animal", operation.Table); Assert.Equal(new[] { "Id", "Discriminator", "MouseId" }, operation.Columns); AssertMultidimensionalArray( operation.Values, + v => Assert.Equal(31, v), + v => Assert.Equal("Mouse", v), + v => Assert.Null(v), v => Assert.Equal(32, v), v => Assert.Equal("Mouse", v), v => Assert.Null(v)); }, o => { - var operation = Assert.IsType(o); + var operation = Assert.IsType(o); Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(12, v)); - - Assert.Equal(new[] { "Discriminator", "MouseId" }, operation.Columns); - Assert.Null(operation.ColumnTypes); + Assert.Equal(new[] { "Id", "Discriminator", "MouseId", "PreyId" }, operation.Columns); AssertMultidimensionalArray( operation.Values, + v => Assert.Equal(11, v), v => Assert.Equal("Cat", v), - v => Assert.Equal(32, v)); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(22, v)); - - Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); - Assert.Null(operation.ColumnTypes); - AssertMultidimensionalArray( - operation.Values, + v => Assert.Equal(31, v), + v => Assert.Null(v), + v => Assert.Equal(12, v), + v => Assert.Equal("Cat", v), + v => Assert.Equal(32, v), + v => Assert.Null(v), + v => Assert.Equal(21, v), + v => Assert.Equal("Dog", v), + v => Assert.Null(v), + v => Assert.Equal(31, v), + v => Assert.Equal(22, v), v => Assert.Equal("Dog", v), + v => Assert.Null(v), v => Assert.Equal(32, v)); }, o => @@ -7404,8 +7428,7 @@ public void Change_TPH_to_TPC_with_FKs_and_seed_data() Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); })); - // Seeding not supported yet - //[ConditionalFact] + [ConditionalFact] public void Change_TPT_to_TPC_with_FKs_and_seed_data() => Execute( modelBuilder => @@ -7522,20 +7545,52 @@ public void Change_TPT_to_TPC_with_FKs_and_seed_data() o => { var operation = Assert.IsType(o); - Assert.Equal("FK_Animal_Animal_MouseId", operation.Name); - Assert.Equal("Animal", operation.Table); + Assert.Equal("FK_Cats_Animal_Id", operation.Name); + Assert.Equal("Cats", operation.Table); }, o => { var operation = Assert.IsType(o); - Assert.Equal("FK_Animal_Animal_PreyId", operation.Name); + Assert.Equal("FK_Cats_Animal_PreyId", operation.Name); + Assert.Equal("Cats", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal_Id", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Mice_Animal_Id", operation.Name); + Assert.Equal("Mice", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(11, v)); }, o => { - var operation = Assert.IsType(o); - Assert.Equal("IX_Animal_PreyId", operation.Name); + var operation = Assert.IsType(o); Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(12, v)); }, o => { @@ -7545,143 +7600,96 @@ public void Change_TPT_to_TPC_with_FKs_and_seed_data() Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(32, v)); + v => Assert.Equal(13, v)); }, o => { - var operation = Assert.IsType(o); - Assert.Equal("PreyId", operation.Name); + var operation = Assert.IsType(o); Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(21, v)); }, o => { - var operation = Assert.IsType(o); - Assert.Equal("Cats", operation.Name); - Assert.Collection( - operation.Columns, - c => - { - Assert.Equal("Id", c.Name); - Assert.Equal("default_int_mapping", c.ColumnType); - Assert.Equal("Cats", c.Table); - Assert.False(c.IsNullable); - Assert.False(c.IsRowVersion); - Assert.Null(c.IsUnicode); - Assert.Null(c.IsFixedLength); - Assert.Null(c.MaxLength); - Assert.Null(c.Precision); - Assert.Null(c.Scale); - Assert.Null(c.DefaultValue); - Assert.Null(c.DefaultValueSql); - Assert.Null(c.ComputedColumnSql); - Assert.Null(c.IsStored); - Assert.Null(c.Comment); - Assert.Null(c.Collation); - }, - c => - { - Assert.Equal("PreyId", c.Name); - Assert.Equal("default_int_mapping", c.ColumnType); - Assert.Equal("Cats", c.Table); - Assert.True(c.IsNullable); - Assert.False(c.IsRowVersion); - Assert.Null(c.IsUnicode); - Assert.Null(c.IsFixedLength); - Assert.Null(c.MaxLength); - Assert.Null(c.Precision); - Assert.Null(c.Scale); - Assert.Null(c.DefaultValue); - Assert.Null(c.DefaultValueSql); - Assert.Null(c.ComputedColumnSql); - Assert.Null(c.IsStored); - Assert.Null(c.Comment); - Assert.Null(c.Collation); - }); - - var pk = operation.PrimaryKey; - Assert.Equal("PK_Cats", pk.Name); - Assert.Equal("Cats", pk.Table); - Assert.Equal(new[] { "Id" }, pk.Columns); - - Assert.Collection( - operation.ForeignKeys, - fk => - { - Assert.Equal("FK_Cats_Animal_Id", fk.Name); - Assert.Equal("Cats", fk.Table); - Assert.Equal("Animal", fk.PrincipalTable); - Assert.Equal(new[] { "Id" }, fk.Columns); - Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); - Assert.Equal(ReferentialAction.Cascade, fk.OnDelete); - }, - fk => - { - Assert.Equal("FK_Cats_Animal_PreyId", fk.Name); - Assert.Equal("Cats", fk.Table); - Assert.Equal("Animal", fk.PrincipalTable); - Assert.Equal(new[] { "PreyId" }, fk.Columns); - Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); - Assert.Equal(ReferentialAction.NoAction, fk.OnDelete); - }); - - Assert.Empty(operation.UniqueConstraints); - Assert.Null(operation.Comment); - Assert.Empty(operation.CheckConstraints); + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(22, v)); }, o => { - var operation = Assert.IsType(o); - Assert.Equal("Mice", operation.Name); - Assert.Collection( - operation.Columns, - c => - { - Assert.Equal("Id", c.Name); - Assert.Equal("default_int_mapping", c.ColumnType); - Assert.Equal("Mice", c.Table); - Assert.False(c.IsNullable); - Assert.False(c.IsRowVersion); - Assert.Null(c.IsUnicode); - Assert.Null(c.IsFixedLength); - Assert.Null(c.MaxLength); - Assert.Null(c.Precision); - Assert.Null(c.Scale); - Assert.Null(c.DefaultValue); - Assert.Null(c.DefaultValueSql); - Assert.Null(c.ComputedColumnSql); - Assert.Null(c.IsStored); - Assert.Null(c.Comment); - Assert.Null(c.Collation); - }); - - var pk = operation.PrimaryKey; - Assert.Equal("PK_Mice", pk.Name); - Assert.Equal("Mice", pk.Table); - Assert.Equal(new[] { "Id" }, pk.Columns); - - var fk = operation.ForeignKeys.Single(); - Assert.Equal("FK_Mice_Animal_Id", fk.Name); - Assert.Equal("Mice", fk.Table); - Assert.Equal("Animal", fk.PrincipalTable); - Assert.Equal(new[] { "Id" }, fk.Columns); - Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); - Assert.Equal(ReferentialAction.Cascade, fk.OnDelete); - - Assert.Empty(operation.UniqueConstraints); - Assert.Null(operation.Comment); - Assert.Empty(operation.CheckConstraints); + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(31, v)); }, o => { - var operation = Assert.IsType(o); - Assert.Equal("Discriminator", operation.Name); + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); Assert.Equal("Animal", operation.Table); - Assert.Equal(typeof(string), operation.ClrType); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("MouseId", operation.Name); + Assert.Equal("Mice", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("MouseId", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("MouseId", operation.Name); + Assert.Equal("Cats", operation.Table); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); + Assert.Equal("Cats", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(11, v)); + + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(31, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( @@ -7697,7 +7705,7 @@ public void Change_TPT_to_TPC_with_FKs_and_seed_data() o => { var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); + Assert.Equal("Cats", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( @@ -7712,86 +7720,102 @@ public void Change_TPT_to_TPC_with_FKs_and_seed_data() }, o => { - var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(21, v)); + + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal(23, v), v => Assert.Null(v)); }, o => { - var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(22, v)); + + Assert.Equal(new[] { "MouseId", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal(33, v), - v => Assert.Null(v)); + v => Assert.Null(v), + v => Assert.Equal(33, v)); }, o => { var operation = Assert.IsType(o); Assert.Equal("Dogs", operation.Table); - Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + Assert.Equal(new[] { "Id", "MouseId", "PreyId" }, operation.Columns); Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal(21, v), - v => Assert.Equal(31, v)); + v => Assert.Equal(23, v), + v => Assert.Null(v), + v => Assert.Null(v)); }, o => { - var operation = Assert.IsType(o); - Assert.Equal("Dogs", operation.Table); + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(31, v)); - Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + Assert.Equal(new[] { "MouseId" }, operation.Columns); Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal(22, v), - v => Assert.Equal(33, v)); + v => Assert.Null(v)); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Dogs", operation.Table); - Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + Assert.Equal("Mice", operation.Table); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal(23, v), + v => Assert.Equal(33, v), v => Assert.Null(v)); }, o => { - var operation = Assert.IsType(o); + var operation = Assert.IsType(o); + Assert.Equal("IX_Mice_MouseId", operation.Name); Assert.Equal("Mice", operation.Table); - Assert.Equal(new[] { "Id" }, operation.Columns); - AssertMultidimensionalArray( - operation.Values, - v => Assert.Equal(33, v)); + Assert.Equal(new[] { "MouseId" }, operation.Columns); }, o => { var operation = Assert.IsType(o); - Assert.Equal("IX_Dogs_PreyId", operation.Name); + Assert.Equal("IX_Dogs_MouseId", operation.Name); Assert.Equal("Dogs", operation.Table); - Assert.Equal(new[] { "PreyId" }, operation.Columns); + Assert.Equal(new[] { "MouseId" }, operation.Columns); }, o => { var operation = Assert.IsType(o); - Assert.Equal("IX_Cats_PreyId", operation.Name); + Assert.Equal("IX_Cats_MouseId", operation.Name); Assert.Equal("Cats", operation.Table); - Assert.Equal(new[] { "PreyId" }, operation.Columns); + Assert.Equal(new[] { "MouseId" }, operation.Columns); }, o => { var operation = Assert.IsType(o); - Assert.Equal("FK_Animal_Mice_MouseId", operation.Name); - Assert.Equal("Animal", operation.Table); + Assert.Equal("FK_Cats_Mice_MouseId", operation.Name); + Assert.Equal("Cats", operation.Table); Assert.Equal("Mice", operation.PrincipalTable); Assert.Equal(new[] { "MouseId" }, operation.Columns); Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); @@ -7800,20 +7824,20 @@ public void Change_TPT_to_TPC_with_FKs_and_seed_data() o => { var operation = Assert.IsType(o); - Assert.Equal("FK_Dogs_Animal", operation.Name); + Assert.Equal("FK_Dogs_Mice_MouseId", operation.Name); Assert.Equal("Dogs", operation.Table); - Assert.Equal("Animal", operation.PrincipalTable); - Assert.Equal(new[] { "Id" }, operation.Columns); + Assert.Equal("Mice", operation.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, operation.Columns); Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); - Assert.Equal(ReferentialAction.Cascade, operation.OnDelete); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); }, o => { var operation = Assert.IsType(o); - Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); - Assert.Equal("Dogs", operation.Table); - Assert.Equal("Animal", operation.PrincipalTable); - Assert.Equal(new[] { "PreyId" }, operation.Columns); + Assert.Equal("FK_Mice_Mice_MouseId", operation.Name); + Assert.Equal("Mice", operation.Table); + Assert.Equal("Mice", operation.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, operation.Columns); Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); }), @@ -7822,58 +7846,38 @@ public void Change_TPT_to_TPC_with_FKs_and_seed_data() o => { var operation = Assert.IsType(o); - Assert.Equal("FK_Animal_Mice_MouseId", operation.Name); - Assert.Equal("Animal", operation.Table); + Assert.Equal("FK_Cats_Mice_MouseId", operation.Name); + Assert.Equal("Cats", operation.Table); }, o => { var operation = Assert.IsType(o); - Assert.Equal("FK_Dogs_Animal", operation.Name); + Assert.Equal("FK_Dogs_Mice_MouseId", operation.Name); Assert.Equal("Dogs", operation.Table); }, o => { var operation = Assert.IsType(o); - Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); - Assert.Equal("Dogs", operation.Table); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("Cats", operation.Name); + Assert.Equal("FK_Mice_Mice_MouseId", operation.Name); + Assert.Equal("Mice", operation.Table); }, o => { - var operation = Assert.IsType(o); - Assert.Equal("Mice", operation.Name); + var operation = Assert.IsType(o); + Assert.Equal("IX_Mice_MouseId", operation.Name); + Assert.Equal("Mice", operation.Table); }, o => { var operation = Assert.IsType(o); - Assert.Equal("IX_Dogs_PreyId", operation.Name); + Assert.Equal("IX_Dogs_MouseId", operation.Name); Assert.Equal("Dogs", operation.Table); }, o => { - var operation = Assert.IsType(o); - Assert.Equal("Dogs", operation.Table); - - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(21, v)); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("Dogs", operation.Table); - - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(22, v)); + var operation = Assert.IsType(o); + Assert.Equal("IX_Cats_MouseId", operation.Name); + Assert.Equal("Cats", operation.Table); }, o => { @@ -7889,17 +7893,7 @@ public void Change_TPT_to_TPC_with_FKs_and_seed_data() o => { var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(23, v)); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); + Assert.Equal("Mice", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); Assert.Null(operation.KeyColumnTypes); @@ -7909,162 +7903,128 @@ public void Change_TPT_to_TPC_with_FKs_and_seed_data() }, o => { - var operation = Assert.IsType(o); - Assert.Equal("Discriminator", operation.Name); - Assert.Equal("Animal", operation.Table); - Assert.Equal(typeof(string), operation.ClrType); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("Discriminator", operation.Name); - Assert.Equal("Animal", operation.Table); - Assert.Equal(typeof(string), operation.ClrType); + var operation = Assert.IsType(o); + Assert.Equal("MouseId", operation.Name); + Assert.Equal("Mice", operation.Table); }, o => { - var operation = Assert.IsType(o); - Assert.Equal("PreyId", operation.Name); - Assert.Equal("Animal", operation.Table); - Assert.Equal(typeof(int), operation.ClrType); + var operation = Assert.IsType(o); + Assert.Equal("MouseId", operation.Name); + Assert.Equal("Dogs", operation.Table); }, o => { - var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(11, v)); - - //Assert.Equal(new[] { "Discriminator" }, operation.Columns); - //Assert.Null(operation.ColumnTypes); - //AssertMultidimensionalArray( - // operation.Values, - // v => Assert.Equal("Cat", v)); + var operation = Assert.IsType(o); + Assert.Equal("MouseId", operation.Name); + Assert.Equal("Cats", operation.Table); }, o => { - var operation = Assert.IsType(o); + var operation = Assert.IsType(o); Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(13, v)); - - Assert.Equal(new[] { "MouseId" }, operation.Columns); - Assert.Null(operation.ColumnTypes); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); AssertMultidimensionalArray( operation.Values, + v => Assert.Equal(11, v), + v => Assert.Equal(31, v), + v => Assert.Equal(13, v), + v => Assert.Null(v), + v => Assert.Equal(21, v), + v => Assert.Null(v), + v => Assert.Equal(22, v), + v => Assert.Null(v), + v => Assert.Equal(31, v), + v => Assert.Null(v), + v => Assert.Equal(32, v), v => Assert.Null(v)); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(21, v)); - - Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); - Assert.Null(operation.ColumnTypes); - AssertMultidimensionalArray( - operation.Values, - v => Assert.Equal("Dog", v), - v => Assert.Equal(31, v)); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); + Assert.Equal("Dogs", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, - v => Assert.Equal(31, v)); + v => Assert.Equal(22, v)); - Assert.Equal(new[] { "Discriminator" }, operation.Columns); + Assert.Equal(new[] { "PreyId" }, operation.Columns); Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal("Mouse", v)); - }, - o => - { - var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id", "Discriminator", "MouseId" }, operation.Columns); - AssertMultidimensionalArray( - operation.Values, - v => Assert.Equal(32, v), - v => Assert.Equal("Mouse", v), - v => Assert.Null(v)); + v => Assert.Equal(32, v)); }, o => - { - var operation = Assert.IsType(o); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(12, v)); - - Assert.Equal(new[] { "Discriminator", "MouseId" }, operation.Columns); - Assert.Null(operation.ColumnTypes); + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Table); + Assert.Equal(new[] { "Id" }, operation.Columns); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal("Cat", v), v => Assert.Equal(32, v)); }, o => { - var operation = Assert.IsType(o); + var operation = Assert.IsType(o); Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "Id" }, operation.KeyColumns); - Assert.Null(operation.KeyColumnTypes); - AssertMultidimensionalArray( - operation.KeyValues, - v => Assert.Equal(22, v)); - - Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); - Assert.Null(operation.ColumnTypes); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal("Dog", v), + v => Assert.Equal(12, v), v => Assert.Equal(32, v)); }, o => { - var operation = Assert.IsType(o); - Assert.Equal("IX_Animal_PreyId", operation.Name); - Assert.Equal("Animal", operation.Table); - Assert.Equal(new[] { "PreyId" }, operation.Columns); + var operation = Assert.IsType(o); + Assert.Equal("FK_Cats_Animal_Id", operation.Name); + Assert.Equal("Cats", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "Id" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, operation.OnDelete); }, o => { var operation = Assert.IsType(o); - Assert.Equal("FK_Animal_Animal_MouseId", operation.Name); - Assert.Equal("Animal", operation.Table); + Assert.Equal("FK_Cats_Animal_PreyId", operation.Name); + Assert.Equal("Cats", operation.Table); Assert.Equal("Animal", operation.PrincipalTable); - Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Equal(new[] { "PreyId" }, operation.Columns); Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); }, o => { var operation = Assert.IsType(o); - Assert.Equal("FK_Animal_Animal_PreyId", operation.Name); - Assert.Equal("Animal", operation.Table); + Assert.Equal("FK_Dogs_Animal_Id", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "Id" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, operation.OnDelete); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); Assert.Equal("Animal", operation.PrincipalTable); Assert.Equal(new[] { "PreyId" }, operation.Columns); Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Mice_Animal_Id", operation.Name); + Assert.Equal("Mice", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "Id" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, operation.OnDelete); })); + [ConditionalFact] public void Add_foreign_key_on_base_type() => Execute( @@ -8468,25 +8428,6 @@ private class Shark : Animal { } - [ConditionalFact] // See #2802 - public void Diff_IProperty_compares_values_not_references() - => Execute( - source => source.Entity( - "Stork", - x => - { - x.Property("Id"); - x.Property("Value").HasDefaultValue(true); - }), - target => target.Entity( - "Stork", - x => - { - x.Property("Id"); - x.Property("Value").HasDefaultValue(true); - }), - Assert.Empty); - [ConditionalFact] public void Add_column_to_renamed_table() => Execute( @@ -9573,6 +9514,7 @@ public virtual SomeOwnedEntity OwnedEntity protected class SomeOwnedEntity { } + [ConditionalFact] public void SeedData_and_PK_rename() @@ -9963,7 +9905,7 @@ public void SeedData_nonkey_refactoring_value_conversion_to_value_type() { x.Property("Value1"); x.HasData( - new { Id = 42 }); + new { Id = 42, Value1 = new DateTime() }); }), target => target.Entity( "EntityWithOneProperty", @@ -9973,7 +9915,7 @@ public void SeedData_nonkey_refactoring_value_conversion_to_value_type() .IsRequired() .HasConversion(e => new DateTime(), e => new byte[0]); x.HasData( - new { Id = 42 }); + new { Id = 42, Value1 = new byte[0] }); }), Assert.Empty, Assert.Empty); @@ -10133,6 +10075,61 @@ public void SeedData_change_enum_conversion() v => Assert.Equal((int)SomeEnum.NonDefault, v)); })); + [ConditionalFact] + public void SeedData_change_with_default() + => Execute( + common => common.Entity( + "EntityWithEnumProperty", + x => + { + x.ToTable("EntityWithEnumProperty", "schema"); + x.Property("Id"); + x.HasKey("Id"); + x.Property("Enum").HasDefaultValue(SomeEnum.Default); + }), + source => source.Entity( + "EntityWithEnumProperty", + x => + { + x.HasData( + new { Id = 1, Enum = SomeEnum.NonDefault }); + }), + target => target.Entity( + "EntityWithEnumProperty", + x => + { + x.HasData( + new { Id = 1, Enum = SomeEnum.Default }); + }), + upOps => Assert.Collection( + upOps, + o => + { + var m = Assert.IsType(o); + Assert.Equal("EntityWithEnumProperty", m.Table); + Assert.Equal("schema", m.Schema); + AssertMultidimensionalArray( + m.KeyValues, + v => Assert.Equal(1, v)); + AssertMultidimensionalArray( + m.Values, + v => Assert.Equal((int)SomeEnum.Default, v)); + }), + downOps => Assert.Collection( + downOps, + o => + { + var m = Assert.IsType(o); + Assert.Equal("EntityWithEnumProperty", m.Table); + Assert.Equal("schema", m.Schema); + AssertMultidimensionalArray( + m.KeyValues, + v => Assert.Equal(1, v)); + AssertMultidimensionalArray( + m.Values, + v => Assert.Equal((int)SomeEnum.NonDefault, v)); + })); + [ConditionalFact] public void SeedData_no_change_enum_key() => Execute( @@ -10392,114 +10389,108 @@ public void SeedData_with_timestamp_column() ComputedValueSql = 42 }); //Added }), - upOps => - { - Assert.Collection( - upOps, - o => - { - var m = Assert.IsType(o); - Assert.Null(m.KeyColumnTypes); - AssertMultidimensionalArray( - m.KeyValues, - v => Assert.Equal(21, v)); - }, - o => - { - var m = Assert.IsType(o); - Assert.Null(m.KeyColumnTypes); - AssertMultidimensionalArray( - m.KeyValues, - v => Assert.Equal(11, v)); - AssertMultidimensionalArray( - m.Values, - v => Assert.Equal("Modified", v)); - }, - o => - { - var m = Assert.IsType(o); - Assert.Null(m.KeyColumnTypes); - AssertMultidimensionalArray( - m.KeyValues, - v => Assert.Equal(12, v)); - AssertMultidimensionalArray( - m.Values, - v => Assert.Equal(6, v), - v => Assert.Equal(6, v), - v => Assert.Equal("Modified", v)); - }, - o => - { - var m = Assert.IsType(o); - Assert.Null(m.ColumnTypes); - AssertMultidimensionalArray( - m.Values, - v => Assert.Equal(31, v), - v => Assert.Equal("Added", v)); - }, - o => - { - var m = Assert.IsType(o); - Assert.Null(m.ColumnTypes); - AssertMultidimensionalArray( - m.Values, - v => Assert.Equal(32, v), - v => Assert.Equal(42, v), - v => Assert.Equal(42, v), - v => Assert.Equal("DefaultValuesProvided", v)); - }); - }, - downOps => - { - Assert.Collection( - downOps, - o => - { - var m = Assert.IsType(o); - Assert.Null(m.KeyColumnTypes); - AssertMultidimensionalArray( - m.KeyValues, - v => Assert.Equal(31, v)); - }, - o => - { - var m = Assert.IsType(o); - Assert.Null(m.KeyColumnTypes); - AssertMultidimensionalArray( - m.KeyValues, - v => Assert.Equal(32, v)); - }, - o => - { - var m = Assert.IsType(o); - AssertMultidimensionalArray( - m.KeyValues, - v => Assert.Equal(11, v)); - AssertMultidimensionalArray( - m.Values, - v => Assert.Equal("Value", v)); - }, - o => - { - var m = Assert.IsType(o); - AssertMultidimensionalArray( - m.KeyValues, - v => Assert.Equal(12, v)); - AssertMultidimensionalArray( - m.Values, - v => Assert.Equal(5, v), - v => Assert.Equal(5, v), - v => Assert.Equal("Value", v)); - }, - o => - { - var m = Assert.IsType(o); - AssertMultidimensionalArray( - m.Values, - v => Assert.Equal(21, v), - v => Assert.Equal("Deleted", v)); - }); - }); + upOps => Assert.Collection( + upOps, + o => + { + var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); + AssertMultidimensionalArray( + m.KeyValues, + v => Assert.Equal(21, v)); + }, + o => + { + var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); + AssertMultidimensionalArray( + m.KeyValues, + v => Assert.Equal(11, v)); + AssertMultidimensionalArray( + m.Values, + v => Assert.Equal("Modified", v)); + }, + o => + { + var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); + AssertMultidimensionalArray( + m.KeyValues, + v => Assert.Equal(12, v)); + AssertMultidimensionalArray( + m.Values, + v => Assert.Equal(6, v), + v => Assert.Equal(6, v), + v => Assert.Equal("Modified", v)); + }, + o => + { + var m = Assert.IsType(o); + Assert.Null(m.ColumnTypes); + AssertMultidimensionalArray( + m.Values, + v => Assert.Equal(31, v), + v => Assert.Equal("Added", v)); + }, + o => + { + var m = Assert.IsType(o); + Assert.Null(m.ColumnTypes); + AssertMultidimensionalArray( + m.Values, + v => Assert.Equal(32, v), + v => Assert.Equal(42, v), + v => Assert.Equal(42, v), + v => Assert.Equal("DefaultValuesProvided", v)); + }), + downOps => Assert.Collection( + downOps, + o => + { + var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); + AssertMultidimensionalArray( + m.KeyValues, + v => Assert.Equal(31, v)); + }, + o => + { + var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); + AssertMultidimensionalArray( + m.KeyValues, + v => Assert.Equal(32, v)); + }, + o => + { + var m = Assert.IsType(o); + AssertMultidimensionalArray( + m.KeyValues, + v => Assert.Equal(11, v)); + AssertMultidimensionalArray( + m.Values, + v => Assert.Equal("Value", v)); + }, + o => + { + var m = Assert.IsType(o); + AssertMultidimensionalArray( + m.KeyValues, + v => Assert.Equal(12, v)); + AssertMultidimensionalArray( + m.Values, + v => Assert.Equal(5, v), + v => Assert.Equal(5, v), + v => Assert.Equal("Value", v)); + }, + o => + { + var m = Assert.IsType(o); + AssertMultidimensionalArray( + m.Values, + v => Assert.Equal(21, v), + v => Assert.Equal("Deleted", v)); + })); [ConditionalFact] public void SeedData_with_shadow_navigation_properties() @@ -10676,16 +10667,6 @@ private void SeedData_with_navigation_properties(Action buildTarge v => Assert.Equal("nowitexists.blog", v)); }, o => - { - var m = Assert.IsType(o); - Assert.Equal("Post", m.Table); - AssertMultidimensionalArray( - m.Values, - v => Assert.Equal(546, v), - v => Assert.Equal(32, v), - v => Assert.Equal("New Post", v)); - }, - o => { var m = Assert.IsType(o); Assert.Equal("Post", m.Table); @@ -10696,6 +10677,16 @@ private void SeedData_with_navigation_properties(Action buildTarge m.Values, v => Assert.Equal(38, v), v => Assert.Equal("Updated Title", v)); + }, + o => + { + var m = Assert.IsType(o); + Assert.Equal("Post", m.Table); + AssertMultidimensionalArray( + m.Values, + v => Assert.Equal(546, v), + v => Assert.Equal(32, v), + v => Assert.Equal("New Post", v)); }), downOps => Assert.Collection( downOps, diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs index 4fe5686ccc7..e7335a4f562 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs @@ -114,16 +114,12 @@ protected virtual TestHelpers.TestModelBuilder CreateModelBuilder(bool skipConve => TestHelpers.CreateConventionBuilder(configure: skipConventions ? c => c.RemoveAllConventions() : null); protected virtual MigrationsModelDiffer CreateModelDiffer(DbContextOptions options) - { - var context = TestHelpers.CreateContext(options); - return new MigrationsModelDiffer( + => new MigrationsModelDiffer( new TestRelationalTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()), new MigrationsAnnotationProvider( new MigrationsAnnotationProviderDependencies()), - context.GetService(), - context.GetService(), - context.GetService()); - } + TestServiceFactory.Instance.Create(), + TestHelpers.CreateContext(options).GetService()); } diff --git a/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs b/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs index 1752bd372d9..7640d68012c 100644 --- a/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs +++ b/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs @@ -13,13 +13,15 @@ public class ModificationCommandComparerTest public void Compare_returns_0_only_for_commands_that_are_equal() { var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); - var model = modelBuilder.Model; - var entityType = model.AddEntityType(typeof(object)); + var entityType = modelBuilder.Model.AddEntityType(typeof(object)); var key = entityType.AddProperty("Id", typeof(int)); entityType.SetPrimaryKey(key); + var model = modelBuilder.FinalizeModel(); + var table = model.GetRelationalModel().Tables.Single(); + var optionsBuilder = new DbContextOptionsBuilder() - .UseModel(modelBuilder.FinalizeModel()) + .UseModel(model) .UseInMemoryDatabase(Guid.NewGuid().ToString()) .UseInternalServiceProvider(InMemoryFixture.DefaultServiceProvider); @@ -31,21 +33,21 @@ public void Compare_returns_0_only_for_commands_that_are_equal() entry1[(IProperty)key] = 1; entry1.SetEntityState(EntityState.Added); var modificationCommandAdded = modificationCommandSource.CreateModificationCommand( - new ModificationCommandParameters("A", null, false, null, new ParameterNameGenerator().GenerateNext)); + new ModificationCommandParameters(table, false, null, new ParameterNameGenerator().GenerateNext)); modificationCommandAdded.AddEntry(entry1, true); var entry2 = stateManager.GetOrCreateEntry(new object()); entry2[(IProperty)key] = 2; entry2.SetEntityState(EntityState.Modified); var modificationCommandModified = modificationCommandSource.CreateModificationCommand( - new ModificationCommandParameters("A", null, false, null, new ParameterNameGenerator().GenerateNext)); + new ModificationCommandParameters(table, false, null, new ParameterNameGenerator().GenerateNext)); modificationCommandModified.AddEntry(entry2, true); var entry3 = stateManager.GetOrCreateEntry(new object()); entry3[(IProperty)key] = 3; entry3.SetEntityState(EntityState.Deleted); var modificationCommandDeleted = modificationCommandSource.CreateModificationCommand( - new ModificationCommandParameters("A", null, false, null, new ParameterNameGenerator().GenerateNext)); + new ModificationCommandParameters(table, false, null, new ParameterNameGenerator().GenerateNext)); modificationCommandDeleted.AddEntry(entry3, true); var mCC = new ModificationCommandComparer(); @@ -161,16 +163,18 @@ public void Compare_returns_0_only_for_entries_that_have_same_key_values() private void Compare_returns_0_only_for_entries_that_have_same_key_values_generic(T value1, T value2) { var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); - var model = modelBuilder.Model; - var entityType = model.AddEntityType(typeof(object)); + var entityType = modelBuilder.Model.AddEntityType(typeof(object)); var keyProperty = entityType.AddProperty("Id", typeof(T)); keyProperty.IsNullable = false; entityType.SetPrimaryKey(keyProperty); + var model = modelBuilder.FinalizeModel(); + var table = model.GetRelationalModel().Tables.Single(); + var optionsBuilder = new DbContextOptionsBuilder() .UseInternalServiceProvider(InMemoryFixture.DefaultServiceProvider) - .UseModel(modelBuilder.FinalizeModel()) + .UseModel(model) .UseInMemoryDatabase(Guid.NewGuid().ToString()); var stateManager = new DbContext(optionsBuilder.Options).GetService(); @@ -181,18 +185,18 @@ private void Compare_returns_0_only_for_entries_that_have_same_key_values_generi entry1[(IProperty)keyProperty] = value1; entry1.SetEntityState(EntityState.Modified); var modificationCommand1 = modificationCommandSource.CreateModificationCommand( - new ModificationCommandParameters("A", null, false, null, new ParameterNameGenerator().GenerateNext)); + new ModificationCommandParameters(table, false, null, new ParameterNameGenerator().GenerateNext)); modificationCommand1.AddEntry(entry1, true); var entry2 = stateManager.GetOrCreateEntry(new object()); entry2[(IProperty)keyProperty] = value2; entry2.SetEntityState(EntityState.Modified); var modificationCommand2 = modificationCommandSource.CreateModificationCommand( - new ModificationCommandParameters("A", null, false, null, new ParameterNameGenerator().GenerateNext)); + new ModificationCommandParameters(table, false, null, new ParameterNameGenerator().GenerateNext)); modificationCommand2.AddEntry(entry2, true); var modificationCommand3 = modificationCommandSource.CreateModificationCommand( - new ModificationCommandParameters("A", null, false, null, new ParameterNameGenerator().GenerateNext)); + new ModificationCommandParameters(table, false, null, new ParameterNameGenerator().GenerateNext)); modificationCommand3.AddEntry(entry1, true); var mCC = new ModificationCommandComparer(); @@ -210,12 +214,12 @@ private enum FlagsEnum Second = 1 << 2 } - private static IModificationCommand CreateModificationCommand( + private static INonTrackedModificationCommand CreateModificationCommand( string name, string schema, bool sensitiveLoggingEnabled) - => CreateModificationCommandSource().CreateModificationCommand( - new ModificationCommandParameters(name, schema, sensitiveLoggingEnabled)); + => CreateModificationCommandSource().CreateNonTrackedModificationCommand( + new NonTrackedModificationCommandParameters(name, schema, sensitiveLoggingEnabled)); private static ModificationCommandFactory CreateModificationCommandSource() => new(); diff --git a/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs b/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs index ba29ddddd42..0496f67b195 100644 --- a/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs +++ b/test/EFCore.Relational.Tests/Update/ModificationCommandTest.cs @@ -16,7 +16,7 @@ public void ModificationCommand_initialized_correctly_for_added_entities_with_te var entry = CreateEntry(EntityState.Added, generateKeyValues: true); entry.SetTemporaryValue(entry.EntityType.FindPrimaryKey().Properties[0], -1); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, false, null); command.AddEntry(entry, true); Assert.Equal("T1", command.TableName); @@ -60,7 +60,7 @@ public void ModificationCommand_initialized_correctly_for_added_entities_with_no { var entry = CreateEntry(EntityState.Added, generateKeyValues: true); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, false, null); command.AddEntry(entry, true); Assert.Equal("T1", command.TableName); @@ -104,7 +104,7 @@ public void ModificationCommand_initialized_correctly_for_added_entities_with_ex { var entry = CreateEntry(EntityState.Added); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, false, null); command.AddEntry(entry, true); Assert.Equal("T1", command.TableName); @@ -148,7 +148,7 @@ public void ModificationCommand_initialized_correctly_for_modified_entities_with { var entry = CreateEntry(EntityState.Modified, generateKeyValues: true); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, false, null); command.AddEntry(entry, true); Assert.Equal("T1", command.TableName); @@ -192,7 +192,7 @@ public void ModificationCommand_initialized_correctly_for_modified_entities_with { var entry = CreateEntry(EntityState.Modified); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, false, null); command.AddEntry(entry, true); Assert.Equal("T1", command.TableName); @@ -236,7 +236,7 @@ public void ModificationCommand_initialized_correctly_for_modified_entities_with { var entry = CreateEntry(EntityState.Modified, computeNonKeyValue: true); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, false, null); command.AddEntry(entry, true); Assert.Equal("T1", command.TableName); @@ -280,7 +280,7 @@ public void ModificationCommand_initialized_correctly_for_deleted_entities() { var entry = CreateEntry(EntityState.Deleted); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, false, null); command.AddEntry(entry, true); Assert.Equal("T1", command.TableName); @@ -304,7 +304,7 @@ public void ModificationCommand_initialized_correctly_for_deleted_entities_with_ { var entry = CreateEntry(EntityState.Deleted, computeNonKeyValue: true); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, false, null); command.AddEntry(entry, true); Assert.Equal("T1", command.TableName); @@ -350,7 +350,7 @@ public void ModificationCommand_throws_for_unchanged_entities(bool sensitive) { var entry = CreateEntry(EntityState.Unchanged); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, sensitive, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, sensitive, null); Assert.Equal( sensitive @@ -366,7 +366,7 @@ public void ModificationCommand_throws_for_unknown_entities(bool sensitive) { var entry = CreateEntry(EntityState.Detached); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, sensitive, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, sensitive, null); Assert.Equal( sensitive @@ -381,7 +381,7 @@ public void RequiresResultPropagation_false_for_Delete_operation() var entry = CreateEntry( EntityState.Deleted, generateKeyValues: true, computeNonKeyValue: true); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, false, null); command.AddEntry(entry, true); Assert.False(command.RequiresResultPropagation); @@ -393,7 +393,7 @@ public void RequiresResultPropagation_true_for_Insert_operation_if_store_generat var entry = CreateEntry( EntityState.Added, generateKeyValues: true, computeNonKeyValue: true); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, false, null); command.AddEntry(entry, true); Assert.True(command.RequiresResultPropagation); @@ -404,7 +404,7 @@ public void RequiresResultPropagation_false_for_Insert_operation_if_no_store_gen { var entry = CreateEntry(EntityState.Added); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, false, null); command.AddEntry(entry, true); Assert.False(command.RequiresResultPropagation); @@ -416,7 +416,7 @@ public void RequiresResultPropagation_true_for_Update_operation_if_non_key_store var entry = CreateEntry( EntityState.Modified, generateKeyValues: true, computeNonKeyValue: true); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, false, null); command.AddEntry(entry, true); Assert.True(command.RequiresResultPropagation); @@ -427,7 +427,7 @@ public void RequiresResultPropagation_false_for_Update_operation_if_no_non_key_s { var entry = CreateEntry(EntityState.Modified, generateKeyValues: true); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, false, null); command.AddEntry(entry, true); Assert.False(command.RequiresResultPropagation); @@ -486,15 +486,13 @@ private static InternalEntityEntry CreateEntry( } private static IModificationCommand CreateModificationCommand( - string tableName, - string schemaName, + ITable table, Func generateParameterName, bool sensitiveLoggingEnabled, IComparer comparer) => new ModificationCommandFactory().CreateModificationCommand( new ModificationCommandParameters( - tableName, - schemaName, + table, sensitiveLoggingEnabled, comparer, generateParameterName)); diff --git a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs index 2551ec6d605..d683ed364a2 100644 --- a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs +++ b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs @@ -135,7 +135,7 @@ public void UpdateCommandText_compiles_inserts() { var entry = CreateEntry(EntityState.Added); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); var batch = new ModificationCommandBatchFake(); @@ -151,7 +151,7 @@ public void UpdateCommandText_compiles_updates() { var entry = CreateEntry(EntityState.Modified, generateKeyValues: true); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); var batch = new ModificationCommandBatchFake(); @@ -167,7 +167,7 @@ public void UpdateCommandText_compiles_deletes() { var entry = CreateEntry(EntityState.Deleted); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); var batch = new ModificationCommandBatchFake(); @@ -184,9 +184,9 @@ public void UpdateCommandText_compiles_multiple_commands() var entry = CreateEntry(EntityState.Added); var parameterNameGenerator = new ParameterNameGenerator(); - var command1 = CreateModificationCommand("T1", null, parameterNameGenerator.GenerateNext, true, null); + var command1 = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, parameterNameGenerator.GenerateNext, true, null); command1.AddEntry(entry, true); - var command2 = CreateModificationCommand("T1", null, parameterNameGenerator.GenerateNext, true, null); + var command2 = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, parameterNameGenerator.GenerateNext, true, null); command2.AddEntry(entry, true); var batch = new ModificationCommandBatchFake(); @@ -203,7 +203,7 @@ public async Task ExecuteAsync_executes_batch_commands_and_consumes_reader() { var entry = CreateEntry(EntityState.Added, generateKeyValues: true); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); var dbDataReader = CreateFakeDataReader(); @@ -226,7 +226,7 @@ public async Task ExecuteAsync_saves_store_generated_values() var entry = CreateEntry(EntityState.Added, generateKeyValues: true); entry.SetTemporaryValue(entry.EntityType.FindPrimaryKey().Properties[0], -1); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); var connection = CreateConnection( @@ -250,7 +250,7 @@ public async Task ExecuteAsync_saves_store_generated_values_on_non_key_columns() EntityState.Added, generateKeyValues: true, computeNonKeyValue: true); entry.SetTemporaryValue(entry.EntityType.FindPrimaryKey().Properties[0], -1); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); var connection = CreateConnection( @@ -273,7 +273,7 @@ public async Task ExecuteAsync_saves_store_generated_values_when_updating() var entry = CreateEntry( EntityState.Modified, generateKeyValues: true, overrideKeyValues: true, computeNonKeyValue: true); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); var connection = CreateConnection( @@ -296,7 +296,7 @@ public async Task Exception_not_thrown_for_more_than_one_row_returned_for_single var entry = CreateEntry(EntityState.Added, generateKeyValues: true); entry.SetTemporaryValue(entry.EntityType.FindPrimaryKey().Properties[0], -1); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); var connection = CreateConnection( @@ -320,7 +320,7 @@ public async Task Exception_thrown_if_rows_returned_for_command_without_store_ge { var entry = CreateEntry(EntityState.Modified); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); var connection = CreateConnection( @@ -346,7 +346,7 @@ public async Task Exception_thrown_if_no_rows_returned_for_command_with_store_ge var entry = CreateEntry(EntityState.Added, generateKeyValues: true); entry.SetTemporaryValue(entry.EntityType.FindPrimaryKey().Properties[0], -1); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); var connection = CreateConnection( @@ -370,7 +370,7 @@ public async Task DbException_is_wrapped_with_DbUpdateException(bool async) { var entry = CreateEntry(EntityState.Added, generateKeyValues: true); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); var originalException = new FakeDbException(); @@ -398,7 +398,7 @@ public async Task OperationCanceledException_is_not_wrapped_with_DbUpdateExcepti { var entry = CreateEntry(EntityState.Added, generateKeyValues: true); - var command = CreateModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, true, null); + var command = CreateModificationCommand(entry.EntityType.GetTableMappings().Single().Table, new ParameterNameGenerator().GenerateNext, true, null); command.AddEntry(entry, true); var originalException = new OperationCanceledException(); @@ -761,15 +761,13 @@ public static IDbContextOptions CreateOptions(RelationalOptionsExtension options } private static IModificationCommand CreateModificationCommand( - string table, - string schema, + ITable table, Func generateParameterName, bool sensitiveLoggingEnabled, IComparer comparer) { var modificationCommandParameters = new ModificationCommandParameters( table, - schema, sensitiveLoggingEnabled, comparer, generateParameterName, @@ -777,17 +775,14 @@ private static IModificationCommand CreateModificationCommand( return CreateModificationCommandSource().CreateModificationCommand(modificationCommandParameters); } - private static IModificationCommand CreateModificationCommand( + private static INonTrackedModificationCommand CreateModificationCommand( string name, string schema, bool sensitiveLoggingEnabled, IReadOnlyList columnModifications) { - var modificationCommandParameters = new ModificationCommandParameters( - name, schema, sensitiveLoggingEnabled); - - var modificationCommand = CreateModificationCommandSource().CreateModificationCommand( - modificationCommandParameters); + var modificationCommand = CreateModificationCommandSource().CreateNonTrackedModificationCommand( + new NonTrackedModificationCommandParameters(name, schema, sensitiveLoggingEnabled)); if (columnModifications != null) { diff --git a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs index e172e013bbc..56addf9b24a 100644 --- a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs +++ b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs @@ -91,16 +91,14 @@ private class FakeDbContext : DbContext { } - private static IModificationCommand CreateModificationCommand( + private static INonTrackedModificationCommand CreateModificationCommand( string name, string schema, bool sensitiveLoggingEnabled) { - var modificationCommandParameters = new ModificationCommandParameters( - name, schema, sensitiveLoggingEnabled); - - var modificationCommand = new ModificationCommandFactory().CreateModificationCommand( - modificationCommandParameters); + var modificationCommand = new ModificationCommandFactory().CreateNonTrackedModificationCommand( + new NonTrackedModificationCommandParameters( + name, schema, sensitiveLoggingEnabled)); return modificationCommand; } diff --git a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs index 3d6fca78ffa..238ef5fd8ae 100644 --- a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs +++ b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs @@ -91,12 +91,12 @@ private static SqlServerTypeMappingSource CreateTypeMappingSource() TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()); - private static IModificationCommand CreateModificationCommand( + private static INonTrackedModificationCommand CreateModificationCommand( string name, string schema, bool sensitiveLoggingEnabled) - => new ModificationCommandFactory().CreateModificationCommand( - new ModificationCommandParameters(name, schema, sensitiveLoggingEnabled)); + => new ModificationCommandFactory().CreateNonTrackedModificationCommand( + new NonTrackedModificationCommandParameters(name, schema, sensitiveLoggingEnabled)); private class TestSqlServerModificationCommandBatch : SqlServerModificationCommandBatch { diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index 60eefb6bbfa..f1ad61a8268 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -1342,7 +1342,7 @@ public virtual void Detects_collection_navigations_in_seeds(bool sensitiveDataLo new[] { new SampleEntity { Id = 2 } }) }); }); - + VerifyError( sensitiveDataLoggingEnabled ? CoreStrings.SeedDatumNavigationSensitive(