diff --git a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs index 06401509b79..dd359245331 100644 --- a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs @@ -442,7 +442,7 @@ private void Create( mainBuilder .Append("var ").Append(overrideVariable).AppendLine(" = new RuntimeRelationalPropertyOverrides(").IncrementIndent() .Append(parameters.TargetName).AppendLine(",") - .Append(code.Literal(overrides.ColumnNameOverriden)).AppendLine(",") + .Append(code.Literal(overrides.ColumnNameOverridden)).AppendLine(",") .Append(code.UnknownLiteral(overrides.ColumnName)).AppendLine(");").DecrementIndent(); CreateAnnotations( diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index 0ccea516d6c..199e02d2bb1 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -33,7 +33,7 @@ public static string GetColumnBaseName(this IReadOnlyProperty property) public static string? GetColumnName(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { var overrides = RelationalPropertyOverrides.Find(property, storeObject); - if (overrides?.ColumnNameOverriden == true) + if (overrides?.ColumnNameOverridden == true) { return overrides.ColumnName; } 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/Conventions/RelationalRuntimeModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs index 4fde4f93489..3c8dd8dd923 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs @@ -307,7 +307,7 @@ private static RuntimeRelationalPropertyOverrides Create( RuntimeProperty runtimeProperty) => new( runtimeProperty, - propertyOverrides.ColumnNameOverriden, + propertyOverrides.ColumnNameOverridden, propertyOverrides.ColumnName); /// diff --git a/src/EFCore.Relational/Metadata/Internal/IRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/Internal/IRelationalPropertyOverrides.cs index 3d38771fa3b..7d11d6db339 100644 --- a/src/EFCore.Relational/Metadata/Internal/IRelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/Internal/IRelationalPropertyOverrides.cs @@ -33,5 +33,5 @@ public interface IRelationalPropertyOverrides : IAnnotatable /// 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. /// - bool ColumnNameOverriden { get; } + bool ColumnNameOverridden { get; } } diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs index a2b4ce59337..b6d60e29198 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs @@ -77,7 +77,7 @@ public virtual string? ColumnName /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool ColumnNameOverriden + public virtual bool ColumnNameOverridden => _columnNameConfigurationSource != null; /// 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/Metadata/RuntimeRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs index e770177fc72..835f880b28c 100644 --- a/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs @@ -17,15 +17,15 @@ public class RuntimeRelationalPropertyOverrides : AnnotatableBase, IRelationalPr /// Initializes a new instance of the class. /// /// The property for which the overrides are applied. - /// Whether the column name is overridden. + /// Whether the column name is overridden. /// The column name. public RuntimeRelationalPropertyOverrides( RuntimeProperty property, - bool columnNameOverriden, + bool columnNameOverridden, string? columnName) { Property = property; - if (columnNameOverriden) + if (columnNameOverridden) { SetAnnotation(RelationalAnnotationNames.ColumnName, columnName); } @@ -51,7 +51,7 @@ IProperty IRelationalPropertyOverrides.Property } /// - bool IRelationalPropertyOverrides.ColumnNameOverriden + bool IRelationalPropertyOverrides.ColumnNameOverridden { [DebuggerStepThrough] get => FindAnnotation(RelationalAnnotationNames.ColumnName) != null; diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 67a6a39820c..cf9a4c33d1e 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -1,10 +1,10 @@ -// 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 System.Globalization; 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 +43,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 +54,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 +78,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 +86,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 +96,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 +404,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 +575,7 @@ protected virtual IEnumerable Diff( NewName = target.Name }; - renameTableOperation.AddAnnotations(MigrationsAnnotations.ForRename(source)); + renameTableOperation.AddAnnotations(MigrationsAnnotationProvider.ForRename(source)); yield return renameTableOperation; } @@ -692,7 +675,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 +963,7 @@ protected virtual IEnumerable Diff( NewName = target.Name }; - renameColumnOperation.AddAnnotations(MigrationsAnnotations.ForRename(source)); + renameColumnOperation.AddAnnotations(MigrationsAnnotationProvider.ForRename(source)); yield return renameColumnOperation; } @@ -1103,7 +1086,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 +1245,7 @@ protected virtual IEnumerable Remove( }; } - operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + operation.AddAnnotations(MigrationsAnnotationProvider.ForRemove(source)); yield return operation; } @@ -1345,7 +1328,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 +1342,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 +1409,7 @@ protected virtual IEnumerable Diff( NewName = targetName }; - renameIndexOperation.AddAnnotations(MigrationsAnnotations.ForRename(source)); + renameIndexOperation.AddAnnotations(MigrationsAnnotationProvider.ForRename(source)); yield return renameIndexOperation; } @@ -1462,7 +1444,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 +1513,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 +1567,7 @@ protected virtual IEnumerable Diff( NewName = target.Name }; - renameSequenceOperation.AddAnnotations(MigrationsAnnotations.ForRename(source)); + renameSequenceOperation.AddAnnotations(MigrationsAnnotationProvider.ForRename(source)); yield return renameSequenceOperation; } @@ -1646,7 +1628,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 +1664,447 @@ 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)) + { + getValue = GetClrValue; + } + else + { + // 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); + } + + foreach (var mapping in entityType.GetTableMappings()) { - var sourceTable = sourceTableMapping.Table; - var targetTable = diffContext.FindTarget(sourceTable); - if (targetTable?.PrimaryKey == null) + INonTrackedModificationCommand command; + var table = mapping.Table; + var keyConstraint = table.PrimaryKey!; + if (!identityMaps.TryGetValue(table, out var identityMap)) + { + identityMap = RowIdentityMapFactory.Create(keyConstraint); + identityMaps.Add(table, identityMap); + } + + var key = new object?[keyConstraint.Columns.Count]; + var keyFound = true; + for (var i = 0; i < key.Length; i++) + { + var columnMapping = keyConstraint.Columns[i].FindColumnMapping(entityType)!; + var property = columnMapping.Property; + var (value, hasValue) = getValue(property, rawSeed); + if (!hasValue) + { + keyFound = false; + break; + } + + var valueConverter = columnMapping.TypeMapping.Converter; + key[i] = valueConverter == null + ? value + : valueConverter.ConvertToProvider(value); + } + + if (!keyFound) { continue; } - foreach (var targetKey in targetTable.PrimaryKey.MappedKeys) + if (identityMap.FindCommand(key) is { } existingCommand) { - var keyPropertiesMap = new List<(IProperty, ValueConverter?, ValueConverter?)>(); - foreach (var keyProperty in targetKey.Properties) + if (!table.IsShared) { - var targetColumn = targetTable.FindColumn(keyProperty); - var sourceColumn = diffContext.FindSource(targetColumn); - if (sourceColumn == null) + if (sensitiveLoggingEnabled) { - break; + throw new InvalidOperationException( + RelationalStrings.DuplicateSeedDataSensitive( + entityType.DisplayName(), + BuildValuesString(key), + table.SchemaQualifiedName)); } + + throw new InvalidOperationException( + RelationalStrings.DuplicateSeedData( + entityType.DisplayName(), + table.SchemaQualifiedName)); + } + + command = existingCommand; + } + else + { + command = CommandBatchPreparerDependencies.ModificationCommandFactory.CreateNonTrackedModificationCommand( + new NonTrackedModificationCommandParameters(table, sensitiveLoggingEnabled)); + command.EntityState = initialState; + + identityMap.Add(key, command); + } - foreach (var sourceProperty in sourceColumn.PropertyMappings.Select(m => m.Property).Distinct()) + foreach (var columnMapping in mapping.ColumnMappings) + { + var property = columnMapping.Property; + var column = columnMapping.Column; + + if ((column.ComputedColumnSql != null) + || property.ValueGenerated.HasFlag(ValueGenerated.OnUpdate)) + { + continue; + } + + var writeValue = true; + var (value, hasValue) = getValue(property, rawSeed); + if (!hasValue) + { + value = property.ClrType.GetDefaultValue(); + } + + if (!hasValue + || Equals(value, property.ClrType.GetDefaultValue())) + { + if (property.GetValueGeneratorFactory() != null + && property == property.DeclaringEntityType.FindDiscriminatorProperty()) { - if (!sourceProperty.DeclaringEntityType.IsAssignableFrom(sourceEntityType)) - { - continue; - } + value = entityType.GetDiscriminatorValue()!; + } + else if (property.ValueGenerated.HasFlag(ValueGenerated.OnAdd)) + { + writeValue = 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)) - { - continue; - } + var valueConverter = columnMapping.TypeMapping.Converter; + value = valueConverter == null + ? value + : valueConverter.ConvertToProvider(value); - keyPropertiesMap.Add((sourceProperty, sourceConverter, targetConverter)); - break; + if (!writeValue) + { + if (column.DefaultValue != null) + { + value = column.DefaultValue; + } + else if (value == null + && !column.IsNullable) + { + value = column.ProviderClrType.GetDefaultValue(); } } - if (keyPropertiesMap.Count == targetKey.Properties.Count) + var existingColumnModification = command.ColumnModifications.FirstOrDefault(c => c.ColumnName == column.Name); + if (existingColumnModification != null) { - keyMapping.GetOrAddNew(sourceEntityType)[(targetKey, targetTable)] = keyPropertiesMap; + if (!Equals(existingColumnModification.Value, value)) + { + if (sensitiveLoggingEnabled) + { + throw new InvalidOperationException( + RelationalStrings.ConflictingSeedValuesSensitive( + entityType.DisplayName(), + BuildValuesString(key), + table.SchemaQualifiedName, + existingColumnModification.ColumnName, + Convert.ToString(existingColumnModification.Value, CultureInfo.InvariantCulture), + Convert.ToString(value, CultureInfo.InvariantCulture))); + } + + throw new InvalidOperationException( + RelationalStrings.ConflictingSeedValues( + entityType.DisplayName(), + table.SchemaQualifiedName, + existingColumnModification.ColumnName)); + } + + 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)); } } } - var changedTableMappings = new Dictionary>(); - foreach (var targetEntityType in target.Model.GetEntityTypes()) + static (object?, bool) GetClrValue(IProperty property, object seed) { - var targetKey = targetEntityType.FindPrimaryKey(); - if (targetKey == null) +#pragma warning disable EF1001 // Internal EF Core API usage. + if (!property.TryGetMemberInfo(forMaterialization: false, forSet: false, out var memberInfo, out var _)) { - continue; + return (null, false); } +#pragma warning restore EF1001 // Internal EF Core API usage. - ITable? firstSourceTable = null; - foreach (var targetTableMapping in targetEntityType.GetTableMappings()) + object? value = null; + switch (memberInfo) { - var targetTable = targetTableMapping.Table; - if (firstSourceTable == null) - { - firstSourceTable = diffContext.FindSource(targetTable); - - continue; - } - - Check.DebugAssert(firstSourceTable != null, "mainSourceTable is null"); - - var newMapping = true; - var sourceTable = diffContext.FindSource(targetTable); - if (sourceTable != null) - { - foreach (var sourceEntityTypeMapping in sourceTable.EntityTypeMappings) + case PropertyInfo propertyInfo: + if (property.IsIndexerProperty()) { - var sourceEntityType = sourceEntityTypeMapping.EntityType; - if (keyMapping.TryGetValue(sourceEntityType, out var targetKeyMap) - && targetKeyMap.ContainsKey((targetKey, targetTable)) - && sourceEntityType.GetTableMappings().First().Table == firstSourceTable) + try { - newMapping = false; + value = propertyInfo.GetValue(seed, new[] { property.Name }); + } + catch (Exception) + { + return (null, false); } } - } - - if (newMapping) - { - if (!changedTableMappings.TryGetValue(targetEntityType, out var newTables)) + else { - newTables = new List(); - changedTableMappings[targetEntityType] = newTables; + value = propertyInfo.GetValue(seed); } - newTables.Add(targetTable); - } + break; + case FieldInfo fieldInfo: + value = fieldInfo.GetValue(seed); + break; } + + return (value, true); } + } - foreach (var sourceEntityType in source.Model.GetEntityTypes()) + /// + /// 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) { - ITable? firstSourceTable = null; - if (keyMapping.TryGetValue(sourceEntityType, out var targetKeyMap)) + return; + } + + var tableMapping = new Dictionary(); + var unchangedColumns = new List(); + var overridenColumns = new List(); + foreach (var targetPair in _targetIdentityMaps) + { + var (targetTable, targetIdentityMap) = targetPair; + if (!tableMapping.TryGetValue(targetTable, out var sourcePair)) { - ITable? firstTargetTable = null; - foreach (var sourceTableMapping in sourceEntityType.GetTableMappings()) + var targetKey = targetTable.PrimaryKey!; + var foundSourceTable = diffContext.FindSource(targetTable); + var sourceKey = foundSourceTable?.PrimaryKey; + if (sourceKey == null + || !_sourceIdentityMaps.TryGetValue(foundSourceTable!, out var foundSourceIdentityMap)) { - var sourceTable = sourceTableMapping.Table; - if (firstSourceTable == null) - { - firstSourceTable = sourceTable; - firstTargetTable = diffContext.FindTarget(firstSourceTable); - if (firstTargetTable == null) - { - break; - } - - continue; - } - - var targetTable = diffContext.FindTarget(sourceTable); - var removedMapping = !(targetTable != null - && targetKeyMap.Keys.Any( - k => k.Item2 == targetTable - && k.Item1.DeclaringEntityType.GetTableMappings().First().Table == firstTargetTable)); + tableMapping.Add(targetTable, null); + continue; + } - if (removedMapping - && diffContext.FindDrop(sourceTable) == null) + var mappingFound = true; + for (var i = 0; i < targetKey.Columns.Count; i++) + { + var keyColumn = targetKey.Columns[i]; + var sourceColumn = diffContext.FindSource(keyColumn); + if (sourceColumn == null + || sourceKey.Columns[i] != sourceColumn + || keyColumn.ProviderClrType != sourceColumn.ProviderClrType) { - if (!changedTableMappings.TryGetValue(sourceEntityType, out var removedTables)) - { - removedTables = new List(); - changedTableMappings[sourceEntityType] = removedTables; - } - - removedTables.Add(sourceTable); + mappingFound = false; + break; } } - } - else - { - targetKeyMap = null; - firstSourceTable = sourceEntityType.GetTableMappings().FirstOrDefault()?.Table; - } - if (firstSourceTable == null) - { - continue; - } + if (!mappingFound + || targetKey.Columns.Count != sourceKey.Columns.Count) + { + tableMapping.Add(targetTable, null); + 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()) + sourcePair = (foundSourceTable!, foundSourceIdentityMap); + tableMapping.Add(targetTable, sourcePair); + } + else if (sourcePair == null) { - mainSourceEntityType = principalSourceTable.EntityTypeMappings.First(m => m.IsSharedTablePrincipal).EntityType; - principalSourceTable = mainSourceEntityType.GetTableMappings().First().Table; + continue; } - foreach (var sourceSeed in sourceEntityType.GetSeedData()) + var (sourceTable, sourceIdentityMap) = sourcePair.Value; + var key = targetTable.PrimaryKey!; + var keyValues = new object?[key.Columns.Count]; + foreach (var targetRow in targetIdentityMap.Rows) { - var sourceEntry = GetEntry(sourceSeed, sourceEntityType, _sourceUpdateAdapter!); - - if (!_sourceSharedIdentityEntryMaps.TryGetValue(principalSourceTable, out var sourceTableEntryMappingMap)) + 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.HasFlag(ValueGenerated.OnUpdate)) + { + 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) - { - Check.DebugAssert(false, "All entries must be in added state at this point"); - continue; - } - - foreach (var targetProperty in entry.EntityType.GetProperties()) + if (targetColumnModification.IsWrite) { - if (targetProperty.GetAfterSaveBehavior() == PropertySaveBehavior.Save) - { - entry.SetOriginalValue(targetProperty, targetProperty.ClrType.GetDefaultValue()); - } + anyColumnsModified = true; } - - 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 +2119,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 +2134,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 +2179,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 +2200,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 +2216,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 +2239,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 +2252,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 +2269,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 /// @@ -2492,74 +2430,14 @@ private static IEntityType GetMainType(ITable table) return result; } - private sealed class EntryMapping - { - public HashSet SourceEntries { get; } = new(); - public HashSet TargetEntries { get; } = new(); - public bool RecreateRow { get; set; } - } - - private sealed class SharedIdentityMap - { - private readonly IUpdateAdapter _updateAdapter; - - private readonly Dictionary _entryValueMap - = new(); - - public SharedIdentityMap(IUpdateAdapter updateAdapter) - { - _updateAdapter = updateAdapter; - } - - /// - /// 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 IEnumerable Values - => _entryValueMap.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 EntryMapping GetOrAddValue(IUpdateEntry entry, ITable table) - { - var mainEntry = GetMainEntry(entry, table); - if (_entryValueMap.TryGetValue(mainEntry, out var entryMapping)) - { - return entryMapping; - } - - entryMapping = new EntryMapping(); - _entryValueMap.Add(mainEntry, entryMapping); - - return entryMapping; - } - - private IUpdateEntry GetMainEntry(IUpdateEntry entry, ITable table) - { - var entityType = entry.EntityType; - var foreignKeys = table.GetRowInternalForeignKeys(entityType); - foreach (var foreignKey in foreignKeys) - { - var principalEntry = _updateAdapter.FindPrincipal(entry, foreignKey); - if (principalEntry != null) - { - return GetMainEntry(principalEntry, table); - } - } - - var mainTable = entry.EntityType.GetTableMappings().First(m => m.IsSplitEntityTypePrincipal).Table; - - return mainTable != table - ? GetMainEntry(entry, mainTable) - : entry; - } - } + private static string BuildValuesString(object?[] values) + => "{" + + string.Join( + ", ", values.Select( + p => p == null + ? "" + : Convert.ToString(p, CultureInfo.InvariantCulture))) + + "}"; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to 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/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index f232e58e0fc..74fe34cf166 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -159,6 +159,22 @@ public static string ConflictingRowValuesSensitive(object? firstEntityType, obje GetString("ConflictingRowValuesSensitive", nameof(firstEntityType), nameof(secondEntityType), nameof(keyValue), nameof(firstConflictingValue), nameof(secondConflictingValue), nameof(column)), firstEntityType, secondEntityType, keyValue, firstConflictingValue, secondConflictingValue, column); + /// + /// A seed entity for entity type '{entityType}' has the same key value as another seed entity mapped to the same table '{table}', but have different values for the column '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values. + /// + public static string ConflictingSeedValues(object? entityType, object? table, object? column) + => string.Format( + GetString("ConflictingSeedValues", nameof(entityType), nameof(table), nameof(column)), + entityType, table, column); + + /// + /// A seed entity for entity type '{entityType}' has the same key value {keyValue} as another seed entity mapped to the same table '{table}', but have different values for the column '{column}' - '{firstValue}', '{secondValue}'. + /// + public static string ConflictingSeedValuesSensitive(object? entityType, object? keyValue, object? table, object? column, object? firstValue, object? secondValue) + => string.Format( + GetString("ConflictingSeedValuesSensitive", nameof(entityType), nameof(keyValue), nameof(table), nameof(column), nameof(firstValue), nameof(secondValue)), + entityType, keyValue, table, column, firstValue, secondValue); + /// /// {numSortOrderProperties} values were provided in CreateIndexOperations.IsDescending, but the operation has {numColumns} columns. /// @@ -557,6 +573,22 @@ public static string DuplicateKeyTableMismatch(object? keyProperties1, object? e GetString("DuplicateKeyTableMismatch", nameof(keyProperties1), nameof(entityType1), nameof(keyProperties2), nameof(entityType2), nameof(keyName), nameof(table1), nameof(table2)), keyProperties1, entityType1, keyProperties2, entityType2, keyName, table1, table2); + /// + /// A seed entity for entity type '{entityType}' has the same key value as another seed entity mapped to the same table '{table}'. Key values should be unique across seed entities. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values. + /// + public static string DuplicateSeedData(object? entityType, object? table) + => string.Format( + GetString("DuplicateSeedData", nameof(entityType), nameof(table)), + entityType, table); + + /// + /// A seed entity for entity type '{entityType}' has the same key value {keyValue} as another seed entity mapped to the same table '{table}'. Key values should be unique across seed entities. + /// + public static string DuplicateSeedDataSensitive(object? entityType, object? keyValue, object? table) + => string.Format( + GetString("DuplicateSeedDataSensitive", nameof(entityType), nameof(keyValue), nameof(table)), + entityType, keyValue, table); + /// /// The trigger '{trigger}' cannot be added to the entity type '{entityType}' because another trigger with the same name already exists on entity type '{conflictingEntityType}'. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index fa58fb988c7..e3f03543a00 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -172,6 +172,12 @@ Instances of entity types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row with the key value '{keyValue}', but have different property values '{firstConflictingValue}' and '{secondConflictingValue}' for the column '{column}'. + + A seed entity for entity type '{entityType}' has the same key value as another seed entity mapped to the same table '{table}', but have different values for the column '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values. + + + A seed entity for entity type '{entityType}' has the same key value {keyValue} as another seed entity mapped to the same table '{table}', but have different values for the column '{column}' - '{firstValue}', '{secondValue}'. + {numSortOrderProperties} values were provided in CreateIndexOperations.IsDescending, but the operation has {numColumns} columns. @@ -322,6 +328,12 @@ The keys {keyProperties1} on '{entityType1}' and {keyProperties2} on '{entityType2}' are both mapped to '{keyName}', but on different tables ('{table1}' and '{table2}'). + + A seed entity for entity type '{entityType}' has the same key value as another seed entity mapped to the same table '{table}'. Key values should be unique across seed entities. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values. + + + A seed entity for entity type '{entityType}' has the same key value {keyValue} as another seed entity mapped to the same table '{table}'. Key values should be unique across seed entities. + The trigger '{trigger}' cannot be added to the entity type '{entityType}' because another trigger with the same name already exists on entity type '{conflictingEntityType}'. @@ -888,4 +900,4 @@ 'VisitChildren' must be overridden in the class deriving from 'SqlExpression'. - + \ No newline at end of file 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/Storage/RelationalGeometryTypeMapping.cs b/src/EFCore.Relational/Storage/RelationalGeometryTypeMapping.cs index fd355cd9f60..8bd9dd44c51 100644 --- a/src/EFCore.Relational/Storage/RelationalGeometryTypeMapping.cs +++ b/src/EFCore.Relational/Storage/RelationalGeometryTypeMapping.cs @@ -27,6 +27,7 @@ protected RelationalGeometryTypeMapping( : base(CreateRelationalTypeMappingParameters(storeType)) { SpatialConverter = converter; + SetProviderValueComparer(); } /// @@ -40,6 +41,16 @@ protected RelationalGeometryTypeMapping( : base(parameters) { SpatialConverter = converter; + SetProviderValueComparer(); + } + + private void SetProviderValueComparer() + { + var providerType = Converter?.ProviderClrType ?? ClrType; + if (providerType.IsAssignableTo(typeof(TGeometry))) + { + ProviderValueComparer = (ValueComparer)Activator.CreateInstance(typeof(GeometryValueComparer<>).MakeGenericType(providerType))!; + } } /// diff --git a/src/EFCore.Relational/Storage/RelationalTypeMapping.cs b/src/EFCore.Relational/Storage/RelationalTypeMapping.cs index 00c7f278e84..f9ab4ca36a0 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMapping.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMapping.cs @@ -384,10 +384,14 @@ protected virtual string SqlLiteralFormatString /// A for the provider CLR type values. /// public virtual ValueComparer ProviderValueComparer - => NonCapturingLazyInitializer.EnsureInitialized( - ref _providerValueComparer, - this, - static c => ValueComparer.CreateDefault(c.Converter?.ProviderClrType ?? c.ClrType, favorStructuralComparisons: true)); + { + get => NonCapturingLazyInitializer.EnsureInitialized( + ref _providerValueComparer, + this, + static c => ValueComparer.CreateDefault(c.Converter?.ProviderClrType ?? c.ClrType, favorStructuralComparisons: true)); + + protected set => _providerValueComparer = value; + } /// /// Creates a copy of this mapping. diff --git a/src/EFCore.Relational/Update/ColumnModification.cs b/src/EFCore.Relational/Update/ColumnModification.cs index e6fec449e0f..6a59a13b3ba 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 + { + 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..20d2b660c8c --- /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 ((). + /// + public new 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..36bcdabf161 100644 --- a/src/EFCore.Relational/Update/Internal/ColumnAccessorsFactory.cs +++ b/src/EFCore.Relational/Update/Internal/ColumnAccessorsFactory.cs @@ -33,12 +33,12 @@ private static ColumnAccessors CreateGeneric(IColumn column) CreateCurrentValueGetter(column), CreateOriginalValueGetter(column)); - private static Func CreateCurrentValueGetter(IColumn column) + private static Func CreateCurrentValueGetter(IColumn column) => c => { if (c.Entries.Count > 0) { - var value = default(TColumn)!; + var value = default(TColumn); var valueFound = false; for (var i = 0; i < c.Entries.Count; i++) { @@ -70,8 +70,10 @@ 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); + ? (default, false) + : modification.Value == null + ? (default, 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/Internal/SimpleRowKeyValueFactory.cs b/src/EFCore.Relational/Update/Internal/SimpleRowKeyValueFactory.cs index 1fbcf7d7153..7ab03fb0832 100644 --- a/src/EFCore.Relational/Update/Internal/SimpleRowKeyValueFactory.cs +++ b/src/EFCore.Relational/Update/Internal/SimpleRowKeyValueFactory.cs @@ -50,7 +50,7 @@ public SimpleRowKeyValueFactory(IUniqueConstraint constraint) /// public virtual TKey CreateKeyValue(object?[] keyValues) { - var value = (TKey?)keyValues[0]; + var value = keyValues[0]; if (value == null) { throw new InvalidOperationException( @@ -59,7 +59,7 @@ public virtual TKey CreateKeyValue(object?[] keyValues) _column.Name)); } - return value; + return (TKey)value; } /// @@ -70,7 +70,7 @@ public virtual TKey CreateKeyValue(object?[] keyValues) /// public virtual TKey CreateKeyValue(IDictionary keyValues) { - var value = (TKey?)keyValues[_column.Name]; + var value = keyValues[_column.Name]; if (value == null) { throw new InvalidOperationException( @@ -79,7 +79,7 @@ public virtual TKey CreateKeyValue(IDictionary keyValues) _column.Name)); } - return value; + return (TKey)value; } /// 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() { return Enumerable.Empty>(); } - + + List? propertiesList = null; + Dictionary? propertiesMap = null; 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); data.Add(seed); var type = rawSeed.GetType(); + propertiesList ??= GetProperties() + .Concat(GetNavigations()) + .Concat(GetSkipNavigations()) + .ToList(); if (ClrType.IsAssignableFrom(type)) { // non-anonymous type - foreach (var propertyBase in properties.Values) + foreach (var propertyBase in propertiesList) { + if (propertyBase.IsShadowProperty()) + { + continue; + } + ValueConverter? valueConverter = null; if (providerValues && propertyBase is IProperty property @@ -3059,7 +3066,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,9 +3104,13 @@ public virtual IEnumerable GetDerivedServiceProperties() else { // anonymous type + propertiesMap ??= GetProperties() + .Concat(GetNavigations()) + .Concat(GetSkipNavigations()) + .ToDictionary(p => p.Name); foreach (var memberInfo in type.GetMembersInHierarchy()) { - if (!properties.TryGetValue(memberInfo.GetSimpleMemberName(), out var propertyBase)) + if (!propertiesMap.TryGetValue(memberInfo.GetSimpleMemberName(), out var propertyBase)) { continue; } @@ -3129,6 +3140,15 @@ 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 ?? Enumerable.Empty(); + /// /// 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/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index fefe9df1541..ccc8df0413b 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -724,7 +724,7 @@ private static bool IsCompatible(MemberInfo? newMemberInfo, Property existingPro var declaringType = (IMutableEntityType)existingProperty.DeclaringType; if (!newMemberInfo.DeclaringType!.IsAssignableFrom(declaringType.ClrType)) { - return existingMemberInfo.IsOverridenBy(newMemberInfo); + return existingMemberInfo.IsOverriddenBy(newMemberInfo); } IMutableEntityType? existingMemberDeclaringEntityType = null; @@ -733,7 +733,7 @@ private static bool IsCompatible(MemberInfo? newMemberInfo, Property existingPro if (newMemberInfo.DeclaringType == baseType.ClrType) { return existingMemberDeclaringEntityType != null - && existingMemberInfo.IsOverridenBy(newMemberInfo); + && existingMemberInfo.IsOverriddenBy(newMemberInfo); } if (existingMemberDeclaringEntityType == null @@ -744,7 +744,7 @@ private static bool IsCompatible(MemberInfo? newMemberInfo, Property existingPro } // newMemberInfo is declared on an unmapped base type, existingMemberInfo should be kept - return newMemberInfo.IsOverridenBy(existingMemberInfo); + return newMemberInfo.IsOverriddenBy(existingMemberInfo); } private bool CanRemoveProperty( @@ -852,7 +852,7 @@ public virtual IMutableNavigationBase Navigation(string navigationName) } } - if (existingProperty.GetIdentifyingMemberInfo()?.IsOverridenBy(memberInfo) == true) + if (existingProperty.GetIdentifyingMemberInfo()?.IsOverriddenBy(memberInfo) == true) { if (configurationSource.HasValue) { @@ -1003,7 +1003,7 @@ private bool CanAddServiceProperty(MemberInfo memberInfo, ConfigurationSource co && Metadata.FindServicePropertiesInHierarchy(propertyName).All( m => (configurationSource.Overrides(m.GetConfigurationSource()) && m.GetConfigurationSource() != ConfigurationSource.Explicit) - || memberInfo.IsOverridenBy(m.GetIdentifyingMemberInfo())); + || memberInfo.IsOverriddenBy(m.GetIdentifyingMemberInfo())); } private static InternalServicePropertyBuilder? DetachServiceProperty(ServiceProperty? serviceProperty) 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/src/Shared/MemberInfoExtensions.cs b/src/Shared/MemberInfoExtensions.cs index c96d296474f..dc1a64967ac 100644 --- a/src/Shared/MemberInfoExtensions.cs +++ b/src/Shared/MemberInfoExtensions.cs @@ -28,7 +28,7 @@ public static bool IsSameAs(this MemberInfo? propertyInfo, MemberInfo? otherProp || otherPropertyInfo.DeclaringType.GetTypeInfo().ImplementedInterfaces .Contains(propertyInfo.DeclaringType))))); - public static bool IsOverridenBy(this MemberInfo? propertyInfo, MemberInfo? otherPropertyInfo) + public static bool IsOverriddenBy(this MemberInfo? propertyInfo, MemberInfo? otherPropertyInfo) => propertyInfo == null ? otherPropertyInfo == null : (otherPropertyInfo != null 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.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs index 6053bf93ac0..a9ce9846793 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs @@ -5,6 +5,32 @@ namespace Microsoft.EntityFrameworkCore.Query; public class SimpleQueryInMemoryTest : SimpleQueryTestBase { + public async override Task Multiple_nested_reference_navigations(bool async) + { + var contextFactory = await InitializeAsync(); + using var context = contextFactory.CreateContext(); + var id = 1; + var staff = await context.Staff.FindAsync(3); + + Assert.Equal(1, staff.ManagerId); + + var query = context.Appraisals + .Include(ap => ap.Staff).ThenInclude(s => s.Manager) + .Include(ap => ap.Staff).ThenInclude(s => s.SecondaryManager) + .Where(ap => ap.Id == id); + + var appraisal = async + ? await query.SingleOrDefaultAsync() + : query.SingleOrDefault(); + + Assert.Equal(1, staff.ManagerId); // InMemory has different behavior + + Assert.NotNull(appraisal); + Assert.Same(staff, appraisal.Staff); + Assert.NotNull(appraisal.Staff.SecondaryManager); + Assert.Equal(2, appraisal.Staff.SecondaryManagerId); + } + protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; } diff --git a/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs index d0544ef6232..6a6add4cc3a 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs @@ -275,7 +275,7 @@ protected IModificationCommand CreateInsertCommand(bool identityKey = true, bool columnModifications = columnModifications.Where(c => !c.IsWrite).ToArray(); } - return CreateModificationCommand("Ducks", Schema, entry, columnModifications, false); + return CreateModificationCommand(entry, columnModifications, false); } protected IModificationCommand CreateUpdateCommand(bool isComputed = true, bool concurrencyToken = true) @@ -312,7 +312,7 @@ protected IModificationCommand CreateUpdateCommand(bool isComputed = true, bool concurrencyProperty.GetTableColumnMappings().Single().TypeMapping, false, true, false, concurrencyToken, true) }; - return CreateModificationCommand("Ducks", Schema, entry, columnModifications, false); + return CreateModificationCommand(entry, columnModifications, false); } protected IModificationCommand CreateDeleteCommand(bool concurrencyToken = true) @@ -337,7 +337,7 @@ protected IModificationCommand CreateDeleteCommand(bool concurrencyToken = true) concurrencyProperty.GetTableColumnMappings().Single().TypeMapping, false, false, false, concurrencyToken, true) }; - return CreateModificationCommand("Ducks", Schema, entry, columnModifications, false); + return CreateModificationCommand(entry, columnModifications, false); } protected abstract TestHelpers TestHelpers { get; } @@ -359,14 +359,12 @@ protected class Duck } private IModificationCommand CreateModificationCommand( - string name, - string schema, InternalEntityEntry entry, IReadOnlyList columnModifications, bool sensitiveLoggingEnabled) { var modificationCommandParameters = new ModificationCommandParameters( - name, schema, sensitiveLoggingEnabled); + entry.EntityType.GetTableMappings().Single().Table, sensitiveLoggingEnabled); var modificationCommand = CreateMutableModificationCommandFactory().CreateModificationCommand( modificationCommandParameters); @@ -374,7 +372,7 @@ private IModificationCommand CreateModificationCommand( foreach (var columnModification in columnModifications) { - modificationCommand.AddColumnModification(columnModification); + ((INonTrackedModificationCommand)modificationCommand).AddColumnModification(columnModification); } return modificationCommand; diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index 9daaca35001..d111d6e98e0 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -1723,6 +1723,127 @@ public void Add_column_with_seed_data() Assert.Equal("Name", operation.Name); })); + [ConditionalFact] + public void Throws_on_null_keys_in_seed_data() + => Assert.Equal(RelationalStrings.NullKeyValue( + "dbo.Firefly", + "Id"), + Assert.Throws(() => Execute( + common => common.Entity( + "Firefly", + x => + { + x.ToTable("Firefly", "dbo"); + x.Property("Id"); + x.HasData( + new { Id = (int?)null }); + }), + _ => { }, + _ => { }, + upOps => { }, + downOps => { })).Message); + + [ConditionalFact] + public void Throws_on_composite_null_keys_in_seed_data() + => Assert.Equal(RelationalStrings.NullKeyValue( + "dbo.Firefly", + "Id"), + Assert.Throws(() => Execute( + common => common.Entity( + "Firefly", + x => + { + x.ToTable("Firefly", "dbo"); + x.Property("Id"); + x.Property("Name"); + x.HasKey("Id", "Name"); + x.HasData( + new { Id = (int?)null, Name = "Firefly 1" }); + }), + _ => { }, + _ => { }, + upOps => { }, + downOps => { })).Message); + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public void Throws_on_duplicate_seed_data(bool enableSensitiveLogging) + => Assert.Equal(enableSensitiveLogging + ? RelationalStrings.DuplicateSeedDataSensitive( + "Firefly (Dictionary)", + "{42}", + "dbo.Firefly") + : RelationalStrings.DuplicateSeedData( + "Firefly (Dictionary)", + "dbo.Firefly"), + Assert.Throws(() => Execute( + common => common.Entity( + "Firefly", + x => + { + x.ToTable("Firefly", "dbo"); + x.Property("Id"); + x.HasData( + new { Id = 42 }, + new { Id = 42 }); + }), + _ => { }, + _ => { }, + upOps => { }, + downOps => { }, + _ => { }, + enableSensitiveLogging: enableSensitiveLogging)).Message); + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public void Throws_on_conflicting_seed_data(bool enableSensitiveLogging) + => Assert.Equal(enableSensitiveLogging + ? RelationalStrings.ConflictingSeedValuesSensitive( + "FireflyDetails (Dictionary)", + "{42}", + "Firefly", + "Name", + "1", + "2") + : RelationalStrings.ConflictingSeedValues( + "FireflyDetails (Dictionary)", + "Firefly", + "Name"), + Assert.Throws(() => Execute( + common => + { + common.Entity( + "Firefly", + x => + { + x.ToTable("Firefly"); + x.Property("Id"); + x.Property("Name"); + x.HasData( + new { Id = 42, Name = "1" }); + }); + + common.Entity( + "FireflyDetails", + x => + { + x.ToTable("Firefly"); + x.Property("Id"); + x.Property("Name"); + x.HasOne("Firefly", null).WithOne().HasForeignKey("FireflyDetails", "Id"); + x.HasData( + new { Id = 42, Name = "2" }); + }); + }, + _ => { }, + _ => { }, + upOps => { }, + downOps => { }, + _ => { }, + enableSensitiveLogging: enableSensitiveLogging)).Message); + [ConditionalFact] public void Add_column_with_order() => Execute( @@ -3280,18 +3401,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 +5958,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 +5984,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 +6162,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 +6174,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 +6189,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 +6207,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 +6225,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 +6242,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 +6259,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 +6608,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 +6634,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 +6748,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 +6842,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 +6954,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 +7084,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 +7275,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 +7375,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 +7393,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 +7407,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 +7453,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 +7473,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 +7549,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 +7666,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 +7721,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 +7826,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 +7841,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 +7945,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 +7967,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 +8014,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 +8024,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 +8549,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 +9635,7 @@ public virtual SomeOwnedEntity OwnedEntity protected class SomeOwnedEntity { } + [ConditionalFact] public void SeedData_and_PK_rename() @@ -9963,7 +10026,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 +10036,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 +10196,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 +10510,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 +10788,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 +10798,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..f9de90f00ce 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs @@ -40,7 +40,8 @@ protected void Execute( Action> assertActionUp, Action> assertActionDown, Action builderOptionsAction, - bool skipSourceConventions = false) + bool skipSourceConventions = false, + bool enableSensitiveLogging = true) { var sourceModelBuilder = CreateModelBuilder(skipSourceConventions); buildCommonAction(sourceModelBuilder); @@ -55,8 +56,12 @@ protected void Execute( var targetOptionsBuilder = TestHelpers .AddProviderOptions(new DbContextOptionsBuilder()) - .UseModel(targetModel) - .EnableSensitiveDataLogging(); + .UseModel(targetModel); + + if (enableSensitiveLogging) + { + targetOptionsBuilder = targetOptionsBuilder.EnableSensitiveDataLogging(); + } if (builderOptionsAction != null) { @@ -114,16 +119,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..d550bfe9a5d 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, + InternalEntityEntry entry, Func generateParameterName, bool sensitiveLoggingEnabled, IComparer comparer) => new ModificationCommandFactory().CreateModificationCommand( new ModificationCommandParameters( - tableName, - schemaName, + entry.EntityType.GetTableMappings().Single().Table, sensitiveLoggingEnabled, comparer, generateParameterName)); diff --git a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs index 2551ec6d605..eeff05f5bfb 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, 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, 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, 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, parameterNameGenerator.GenerateNext, true, null); command1.AddEntry(entry, true); - var command2 = CreateModificationCommand("T1", null, parameterNameGenerator.GenerateNext, true, null); + var command2 = CreateModificationCommand(entry, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, + InternalEntityEntry entry, Func generateParameterName, bool sensitiveLoggingEnabled, IComparer comparer) { var modificationCommandParameters = new ModificationCommandParameters( - table, - schema, + entry.EntityType.GetTableMappings().Single().Table, 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.Specification.Tests/Query/SimpleQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs index 2d36e06ce07..287d04ee106 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs @@ -32,12 +32,10 @@ public virtual async Task Multiple_nested_reference_navigations(bool async) ? await query.SingleOrDefaultAsync() : query.SingleOrDefault(); - Assert.Equal(1, staff.ManagerId); + Assert.Null(staff.ManagerId); // Overridden due to bad data, see #24368 Assert.NotNull(appraisal); Assert.Same(staff, appraisal.Staff); - Assert.NotNull(appraisal.Staff.Manager); - Assert.Equal(1, appraisal.Staff.ManagerId); Assert.NotNull(appraisal.Staff.SecondaryManager); Assert.Equal(2, appraisal.Staff.SecondaryManagerId); } 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 {