diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs index 307a4d473d7..a0bad312969 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs @@ -4,47 +4,52 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; -using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Update; namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal { public class CosmosDatabaseCreator : IDatabaseCreator { private readonly CosmosClientWrapper _cosmosClient; - private readonly StateManagerDependencies _stateManagerDependencies; + private readonly IModel _model; + private readonly IUpdateAdapterFactory _updateAdapterFactory; + private readonly IDatabase _database; public CosmosDatabaseCreator( CosmosClientWrapper cosmosClient, - StateManagerDependencies stateManagerDependencies) + IModel model, + IUpdateAdapterFactory updateAdapterFactory, + IDatabase database) { _cosmosClient = cosmosClient; - _stateManagerDependencies = stateManagerDependencies; + _model = model; + _updateAdapterFactory = updateAdapterFactory; + _database = database; } public bool EnsureCreated() { var created = _cosmosClient.CreateDatabaseIfNotExists(); - foreach (var entityType in _stateManagerDependencies.Model.GetEntityTypes()) + foreach (var entityType in _model.GetEntityTypes()) { created |= _cosmosClient.CreateContainerIfNotExists(entityType.Cosmos().ContainerName, "__partitionKey"); } if (created) { - var stateManager = new StateManager(_stateManagerDependencies); - foreach (var entityType in _stateManagerDependencies.Model.GetEntityTypes()) + var updateAdapter = _updateAdapterFactory.Create(); + foreach (var entityType in _model.GetEntityTypes()) { - foreach (var targetSeed in entityType.GetData()) + foreach (var targetSeed in entityType.GetSeedData()) { - var entry = stateManager.CreateEntry(targetSeed, entityType); - entry.SetEntityState(EntityState.Added); + var entry = updateAdapter.CreateEntry(targetSeed, entityType); + entry.EntityState = EntityState.Added; } } - stateManager.SaveChanges(acceptAllChangesOnSuccess: false); + _database.SaveChanges(updateAdapter.GetEntriesToSave()); } return created; @@ -53,24 +58,24 @@ public bool EnsureCreated() public async Task EnsureCreatedAsync(CancellationToken cancellationToken = default) { var created = await _cosmosClient.CreateDatabaseIfNotExistsAsync(cancellationToken); - foreach (var entityType in _stateManagerDependencies.Model.GetEntityTypes()) + foreach (var entityType in _model.GetEntityTypes()) { created |= await _cosmosClient.CreateContainerIfNotExistsAsync(entityType.Cosmos().ContainerName, "__partitionKey", cancellationToken); } if (created) { - var stateManager = new StateManager(_stateManagerDependencies); - foreach (var entityType in _stateManagerDependencies.Model.GetEntityTypes()) + var updateAdapter = _updateAdapterFactory.Create(); + foreach (var entityType in _model.GetEntityTypes()) { - foreach (var targetSeed in entityType.GetData()) + foreach (var targetSeed in entityType.GetSeedData()) { - var entry = stateManager.CreateEntry(targetSeed, entityType); - entry.SetEntityState(EntityState.Added); + var entry = updateAdapter.CreateEntry(targetSeed, entityType); + entry.EntityState = EntityState.Added; } } - await stateManager.SaveChangesAsync(acceptAllChangesOnSuccess: false); + await _database.SaveChangesAsync(updateAdapter.GetEntriesToSave(), cancellationToken); } return created; diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs index 03d1dc788ca..24266994adf 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs @@ -261,7 +261,7 @@ private IUpdateEntry GetRootDocument(InternalEntityEntry entry) { var stateManager = entry.StateManager; var ownership = entry.EntityType.FindOwnership(); - var principal = stateManager.GetPrincipal(entry, ownership); + var principal = stateManager.FindPrincipal(entry, ownership); if (principal == null) { if (_sensitiveLoggingEnabled) diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index c75bfeed251..6129a54dd91 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -201,7 +201,7 @@ protected virtual void GenerateEntityType( GenerateRelationships(builderName, entityType, stringBuilder); } - GenerateData(builderName, entityType.GetProperties(), entityType.GetData(providerValues: true), stringBuilder); + GenerateData(builderName, entityType.GetProperties(), entityType.GetSeedData(providerValues: true), stringBuilder); } stringBuilder diff --git a/src/EFCore.InMemory/Extensions/InMemoryServiceCollectionExtensions.cs b/src/EFCore.InMemory/Extensions/InMemoryServiceCollectionExtensions.cs index eb5932f39ee..b75886f6415 100644 --- a/src/EFCore.InMemory/Extensions/InMemoryServiceCollectionExtensions.cs +++ b/src/EFCore.InMemory/Extensions/InMemoryServiceCollectionExtensions.cs @@ -12,7 +12,6 @@ using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal; using Microsoft.EntityFrameworkCore.InMemory.ValueGeneration.Internal; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors; using Microsoft.EntityFrameworkCore.Storage; diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryContext.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryContext.cs index 08700fdbadf..cae6d97ad5a 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryContext.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryContext.cs @@ -25,6 +25,7 @@ public class InMemoryQueryContext : QueryContext /// public InMemoryQueryContext( [NotNull] QueryContextDependencies dependencies, + // Internal code: see #15096 [NotNull] Func queryBufferFactory, [NotNull] IInMemoryStore store) : base(dependencies, queryBufferFactory) diff --git a/src/EFCore.InMemory/Storage/Internal/IInMemoryDatabase.cs b/src/EFCore.InMemory/Storage/Internal/IInMemoryDatabase.cs index 41e43fbfc13..6969c373623 100644 --- a/src/EFCore.InMemory/Storage/Internal/IInMemoryDatabase.cs +++ b/src/EFCore.InMemory/Storage/Internal/IInMemoryDatabase.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; @@ -38,6 +36,6 @@ public interface IInMemoryDatabase : IDatabase /// 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 EnsureDatabaseCreated([NotNull] StateManagerDependencies stateManagerDependencies); + bool EnsureDatabaseCreated(); } } diff --git a/src/EFCore.InMemory/Storage/Internal/IInMemoryStore.cs b/src/EFCore.InMemory/Storage/Internal/IInMemoryStore.cs index 77a18c05cff..1f60b5d919c 100644 --- a/src/EFCore.InMemory/Storage/Internal/IInMemoryStore.cs +++ b/src/EFCore.InMemory/Storage/Internal/IInMemoryStore.cs @@ -26,7 +26,7 @@ public interface IInMemoryStore /// doing so can result in application failures when updating to a new Entity Framework Core release. /// bool EnsureCreated( - [NotNull] StateManagerDependencies stateManagerDependencies, + [NotNull] IUpdateAdapterFactory updateAdapterFactory, [NotNull] IDiagnosticsLogger updateLogger); /// diff --git a/src/EFCore.InMemory/Storage/Internal/InMemoryDatabase.cs b/src/EFCore.InMemory/Storage/Internal/InMemoryDatabase.cs index 5a07445403f..f85bc5ab8a1 100644 --- a/src/EFCore.InMemory/Storage/Internal/InMemoryDatabase.cs +++ b/src/EFCore.InMemory/Storage/Internal/InMemoryDatabase.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Query; @@ -36,6 +35,7 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal public class InMemoryDatabase : Database, IInMemoryDatabase { private readonly IInMemoryStore _store; + private readonly IUpdateAdapterFactory _updateAdapterFactory; private readonly IDiagnosticsLogger _updateLogger; /// @@ -48,14 +48,17 @@ public InMemoryDatabase( [NotNull] DatabaseDependencies dependencies, [NotNull] IInMemoryStoreCache storeCache, [NotNull] IDbContextOptions options, + [NotNull] IUpdateAdapterFactory updateAdapterFactory, [NotNull] IDiagnosticsLogger updateLogger) : base(dependencies) { Check.NotNull(storeCache, nameof(storeCache)); Check.NotNull(options, nameof(options)); + Check.NotNull(updateAdapterFactory, nameof(updateAdapterFactory)); Check.NotNull(updateLogger, nameof(updateLogger)); _store = storeCache.GetStore(options); + _updateAdapterFactory = updateAdapterFactory; _updateLogger = updateLogger; } @@ -93,8 +96,8 @@ public override Task SaveChangesAsync( /// 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 EnsureDatabaseCreated(StateManagerDependencies stateManagerDependencies) - => _store.EnsureCreated(Check.NotNull(stateManagerDependencies, nameof(stateManagerDependencies)), _updateLogger); + public virtual bool EnsureDatabaseCreated() + => _store.EnsureCreated(_updateAdapterFactory, _updateLogger); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.InMemory/Storage/Internal/InMemoryDatabaseCreator.cs b/src/EFCore.InMemory/Storage/Internal/InMemoryDatabaseCreator.cs index 2aa7d1a67b2..1ec74324311 100644 --- a/src/EFCore.InMemory/Storage/Internal/InMemoryDatabaseCreator.cs +++ b/src/EFCore.InMemory/Storage/Internal/InMemoryDatabaseCreator.cs @@ -4,7 +4,6 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -27,7 +26,7 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal /// public class InMemoryDatabaseCreator : IDatabaseCreator { - private readonly StateManagerDependencies _stateManagerDependencies; + private readonly IDatabase _database; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -35,11 +34,11 @@ public class InMemoryDatabaseCreator : IDatabaseCreator /// 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 InMemoryDatabaseCreator([NotNull] StateManagerDependencies stateManagerDependencies) + public InMemoryDatabaseCreator([NotNull] IDatabase database) { - Check.NotNull(stateManagerDependencies, nameof(stateManagerDependencies)); + Check.NotNull(database, nameof(database)); - _stateManagerDependencies = stateManagerDependencies; + _database = database; } /// @@ -48,7 +47,7 @@ public InMemoryDatabaseCreator([NotNull] StateManagerDependencies stateManagerDe /// 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 IInMemoryDatabase Database => (IInMemoryDatabase)_stateManagerDependencies.Database; + protected virtual IInMemoryDatabase Database => (IInMemoryDatabase)_database; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -74,7 +73,7 @@ public virtual Task EnsureDeletedAsync(CancellationToken cancellationToken /// 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 EnsureCreated() => Database.EnsureDatabaseCreated(_stateManagerDependencies); + public virtual bool EnsureCreated() => Database.EnsureDatabaseCreated(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -83,7 +82,7 @@ public virtual Task EnsureDeletedAsync(CancellationToken cancellationToken /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual Task EnsureCreatedAsync(CancellationToken cancellationToken = default) - => Task.FromResult(Database.EnsureDatabaseCreated(_stateManagerDependencies)); + => Task.FromResult(Database.EnsureDatabaseCreated()); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.InMemory/Storage/Internal/InMemoryStore.cs b/src/EFCore.InMemory/Storage/Internal/InMemoryStore.cs index 138ac934f0c..c4359792c47 100644 --- a/src/EFCore.InMemory/Storage/Internal/InMemoryStore.cs +++ b/src/EFCore.InMemory/Storage/Internal/InMemoryStore.cs @@ -5,14 +5,10 @@ using System.Diagnostics; using System.Linq; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.InMemory.Internal; using Microsoft.EntityFrameworkCore.InMemory.ValueGeneration.Internal; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Update; namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal @@ -82,7 +78,7 @@ public virtual InMemoryIntegerValueGenerator GetIntegerValueGenerator /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool EnsureCreated( - StateManagerDependencies stateManagerDependencies, + IUpdateAdapterFactory updateAdapterFactory, IDiagnosticsLogger updateLogger) { lock (_lock) @@ -93,14 +89,14 @@ public virtual bool EnsureCreated( // ReSharper disable once AssignmentIsFullyDiscarded _tables = CreateTables(); - var stateManager = new StateManager(stateManagerDependencies); + var updateAdapter = updateAdapterFactory.Create(); var entries = new List(); - foreach (var entityType in stateManagerDependencies.Model.GetEntityTypes()) + foreach (var entityType in updateAdapter.Model.GetEntityTypes()) { - foreach (var targetSeed in entityType.GetData()) + foreach (var targetSeed in entityType.GetSeedData()) { - var entry = stateManager.CreateEntry(targetSeed, entityType); - entry.SetEntityState(EntityState.Added); + var entry = updateAdapter.CreateEntry(targetSeed, entityType); + entry.EntityState = EntityState.Added; entries.Add(entry); } } diff --git a/src/EFCore.InMemory/Storage/Internal/InMemoryTable.cs b/src/EFCore.InMemory/Storage/Internal/InMemoryTable.cs index f1c5d38b7ba..9674535fb08 100644 --- a/src/EFCore.InMemory/Storage/Internal/InMemoryTable.cs +++ b/src/EFCore.InMemory/Storage/Internal/InMemoryTable.cs @@ -8,11 +8,9 @@ using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.InMemory.Internal; using Microsoft.EntityFrameworkCore.InMemory.ValueGeneration.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Update; using Microsoft.EntityFrameworkCore.Utilities; @@ -26,7 +24,8 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal /// public class InMemoryTable : IInMemoryTable { - private readonly IPrincipalKeyValueFactory _keyValueFactory; + // WARNING: The in-memory provider is using EF internal code here. This should not be copied by other providers. See #15096 + private readonly ChangeTracking.Internal.IPrincipalKeyValueFactory _keyValueFactory; private readonly bool _sensitiveLoggingEnabled; private readonly Dictionary _rows; @@ -38,7 +37,10 @@ public class InMemoryTable : IInMemoryTable /// 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 InMemoryTable([NotNull] IPrincipalKeyValueFactory keyValueFactory, bool sensitiveLoggingEnabled) + public InMemoryTable( + // WARNING: The in-memory provider is using EF internal code here. This should not be copied by other providers. See #15096 + [NotNull] ChangeTracking.Internal.IPrincipalKeyValueFactory keyValueFactory, + bool sensitiveLoggingEnabled) { _keyValueFactory = keyValueFactory; _sensitiveLoggingEnabled = sensitiveLoggingEnabled; @@ -58,7 +60,8 @@ public virtual InMemoryIntegerValueGenerator GetIntegerValueGenerator _integerGenerators = new Dictionary(); } - var propertyIndex = property.GetIndex(); + // WARNING: The in-memory provider is using EF internal code here. This should not be copied by other providers. See #15096 + var propertyIndex = EntityFrameworkCore.Metadata.Internal.PropertyBaseExtensions.GetIndex(property); if (!_integerGenerators.TryGetValue(propertyIndex, out var generator)) { generator = new InMemoryIntegerValueGenerator(propertyIndex); @@ -212,8 +215,9 @@ private void BumpValueGenerators(object[] row) } } + // WARNING: The in-memory provider is using EF internal code here. This should not be copied by other providers. See #15096 private TKey CreateKey(IUpdateEntry entry) - => _keyValueFactory.CreateFromCurrentValues((InternalEntityEntry)entry); + => _keyValueFactory.CreateFromCurrentValues((ChangeTracking.Internal.InternalEntityEntry)entry); private static object SnapshotValue(IProperty property, ValueComparer comparer, IUpdateEntry entry) => SnapshotValue(comparer, entry.GetCurrentValue(property)); diff --git a/src/EFCore.InMemory/Storage/Internal/InMemoryTableFactory.cs b/src/EFCore.InMemory/Storage/Internal/InMemoryTableFactory.cs index 85bf36cb5f0..d3b3c29af9b 100644 --- a/src/EFCore.InMemory/Storage/Internal/InMemoryTableFactory.cs +++ b/src/EFCore.InMemory/Storage/Internal/InMemoryTableFactory.cs @@ -5,10 +5,8 @@ using System.Collections.Concurrent; using System.Reflection; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal @@ -19,7 +17,9 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public class InMemoryTableFactory : IdentityMapFactoryFactoryBase, IInMemoryTableFactory + public class InMemoryTableFactory + // WARNING: The in-memory provider is using EF internal code here. This should not be copied by other providers. See #15096 + : ChangeTracking.Internal.IdentityMapFactoryFactoryBase, IInMemoryTableFactory { private readonly bool _sensitiveLoggingEnabled; @@ -56,6 +56,9 @@ private Func Create([NotNull] IKey key) [UsedImplicitly] private static Func CreateFactory(IKey key, bool sensitiveLoggingEnabled) - => () => new InMemoryTable(key.GetPrincipalKeyValueFactory(), sensitiveLoggingEnabled); + => () => new InMemoryTable( + // WARNING: The in-memory provider is using EF internal code here. This should not be copied by other providers. See #15096 + EntityFrameworkCore.Metadata.Internal.KeyExtensions.GetPrincipalKeyValueFactory(key), + sensitiveLoggingEnabled); } } diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index dcc5e692f50..856e571d560 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Reflection; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; @@ -50,8 +49,8 @@ public class MigrationsModelDiffer : IMigrationsModelDiffer private static readonly Type[] _constraintOperationTypes = { typeof(AddForeignKeyOperation), typeof(CreateIndexOperation) }; - private IStateManager _sourceStateManager; - private IStateManager _targetStateManager; + private IUpdateAdapter _sourceUpdateAdapter; + private IUpdateAdapter _targetUpdateAdapter; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -63,18 +62,18 @@ public MigrationsModelDiffer( [NotNull] IRelationalTypeMappingSource typeMappingSource, [NotNull] IMigrationsAnnotationProvider migrationsAnnotations, [NotNull] IChangeDetector changeDetector, - [NotNull] StateManagerDependencies stateManagerDependencies, + [NotNull] IUpdateAdapterFactory updateAdapterFactory, [NotNull] CommandBatchPreparerDependencies commandBatchPreparerDependencies) { Check.NotNull(typeMappingSource, nameof(typeMappingSource)); Check.NotNull(migrationsAnnotations, nameof(migrationsAnnotations)); - Check.NotNull(stateManagerDependencies, nameof(stateManagerDependencies)); + Check.NotNull(updateAdapterFactory, nameof(updateAdapterFactory)); Check.NotNull(commandBatchPreparerDependencies, nameof(commandBatchPreparerDependencies)); TypeMappingSource = typeMappingSource; MigrationsAnnotations = migrationsAnnotations; ChangeDetector = changeDetector; - StateManagerDependencies = stateManagerDependencies; + UpdateAdapterFactory = updateAdapterFactory; CommandBatchPreparerDependencies = commandBatchPreparerDependencies; } @@ -100,7 +99,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 StateManagerDependencies StateManagerDependencies { get; } + protected virtual IUpdateAdapterFactory UpdateAdapterFactory { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1514,33 +1513,37 @@ protected virtual void TrackData( { if (target == null) { - _targetStateManager = null; + _targetUpdateAdapter = null; return; } - _targetStateManager = new StateManager(StateManagerDependencies.With(target)); + _targetUpdateAdapter = UpdateAdapterFactory.Create(target); foreach (var targetEntityType in target.GetEntityTypes()) { - foreach (var targetSeed in targetEntityType.GetData()) + foreach (var targetSeed in targetEntityType.GetSeedData()) { - _targetStateManager.CreateEntry(targetSeed, targetEntityType).SetEntityState(EntityState.Added); + _targetUpdateAdapter + .CreateEntry(targetSeed, targetEntityType) + .EntityState = EntityState.Added; } } if (source == null) { - _sourceStateManager = null; + _sourceUpdateAdapter = null; return; } - _sourceStateManager = new StateManager(StateManagerDependencies.With(source)); + _sourceUpdateAdapter = UpdateAdapterFactory.Create(source); foreach (var sourceEntityType in source.GetEntityTypes()) { - foreach (var sourceSeed in sourceEntityType.GetData()) + foreach (var sourceSeed in sourceEntityType.GetSeedData()) { - _sourceStateManager.CreateEntry(sourceSeed, sourceEntityType).SetEntityState(EntityState.Added); + _sourceUpdateAdapter + .CreateEntry(sourceSeed, sourceEntityType) + .EntityState = EntityState.Added; } } } @@ -1560,18 +1563,18 @@ protected virtual void DiffData( Check.NotNull(target, nameof(target)); Check.NotNull(diffContext, nameof(diffContext)); - var targetTableEntryMappingMap = SharedTableEntryMap>.CreateSharedTableEntryMapFactory( + var targetTableEntryMappingMap = SharedTableEntryMap>.CreateSharedTableEntryMapFactory( target.EntityTypes, - _targetStateManager, + _targetUpdateAdapter, target.Name, target.Schema) - ((t, s, c) => new List()); + ((t, s, c) => new List()); foreach (var targetEntityType in target.EntityTypes) { - foreach (var targetSeed in targetEntityType.GetData()) + foreach (var targetSeed in targetEntityType.GetSeedData()) { - var targetEntry = GetEntry(targetSeed, targetEntityType, _targetStateManager); + var targetEntry = GetEntry(targetSeed, targetEntityType, _targetUpdateAdapter); var targetEntries = targetTableEntryMappingMap.GetOrAddValue(targetEntry); targetEntries.Add(targetEntry); } @@ -1622,16 +1625,16 @@ protected virtual void DiffData( var sourceTableEntryMappingMap = SharedTableEntryMap.CreateSharedTableEntryMapFactory( source.EntityTypes, - _sourceStateManager, + _sourceUpdateAdapter, source.Name, source.Schema) ((t, s, c) => new EntryMapping()); foreach (var sourceEntityType in source.EntityTypes) { - foreach (var sourceSeed in sourceEntityType.GetData()) + foreach (var sourceSeed in sourceEntityType.GetSeedData()) { - var sourceEntry = GetEntry(sourceSeed, sourceEntityType, _sourceStateManager); + var sourceEntry = GetEntry(sourceSeed, sourceEntityType, _sourceUpdateAdapter); var entryMapping = sourceTableEntryMappingMap.GetOrAddValue(sourceEntry); entryMapping.SourceEntries.Add(sourceEntry); @@ -1670,7 +1673,7 @@ protected virtual void DiffData( } } - var entry = _targetStateManager.TryGetEntry(targetKey, targetKeyValues); + var entry = _targetUpdateAdapter.TryGetEntry(targetKey, targetKeyValues); if (entry == null) { continue; @@ -1691,7 +1694,7 @@ protected virtual void DiffData( } } - targetEntry.SetEntityState(EntityState.Unchanged); + targetEntry.EntityState = EntityState.Unchanged; } if (entryMapping.RecreateRow) @@ -1773,26 +1776,26 @@ var modelValuesChanged { foreach (var targetEntry in entryMapping.TargetEntries) { - targetEntry.SetEntityState(EntityState.Added); + targetEntry.EntityState = EntityState.Added; } foreach (var sourceEntry in entryMapping.SourceEntries) { - sourceEntry.SetEntityState(EntityState.Deleted); + sourceEntry.EntityState = EntityState.Deleted; } } else { foreach (var sourceEntry in entryMapping.SourceEntries) { - sourceEntry.SetEntityState(EntityState.Detached); + sourceEntry.EntityState = EntityState.Detached; } } } } - private static InternalEntityEntry GetEntry( - IDictionary sourceSeed, IEntityType sourceEntityType, IStateManager stateManager) + private static IUpdateEntry GetEntry( + IDictionary sourceSeed, IEntityType sourceEntityType, IUpdateAdapter updateAdapter) { var key = sourceEntityType.FindPrimaryKey(); var keyValues = new object[key.Properties.Count]; @@ -1801,7 +1804,7 @@ private static InternalEntityEntry GetEntry( keyValues[i] = sourceSeed[key.Properties[i].Name]; } - return stateManager.TryGetEntry(key, keyValues); + return updateAdapter.TryGetEntry(key, keyValues); } /// @@ -1812,31 +1815,31 @@ private static InternalEntityEntry GetEntry( /// protected virtual IEnumerable GetDataOperations() { - if (_sourceStateManager != null) + if (_sourceUpdateAdapter != null) { - foreach (var sourceEntry in _sourceStateManager.ToListForState(added: true)) + foreach (var sourceEntry in _sourceUpdateAdapter.Entries.Where(e => e.EntityState == EntityState.Added).ToList()) { - sourceEntry.SetEntityState(EntityState.Detached); + sourceEntry.EntityState = EntityState.Detached; } } - foreach (var stateManager in new[] { _sourceStateManager, _targetStateManager }) + foreach (var updateAdapter in new[] { _sourceUpdateAdapter, _targetUpdateAdapter }) { - if (stateManager == null) + if (updateAdapter == null) { continue; } - ChangeDetector.DetectChanges(stateManager); - var entries = stateManager.GetEntriesToSave(); + updateAdapter.DetectChanges(); + var entries = updateAdapter.GetEntriesToSave(); if (entries == null || entries.Count == 0) { continue; } - var commandBatches = new CommandBatchPreparer(CommandBatchPreparerDependencies.With(() => stateManager)) - .BatchCommands(entries); + var commandBatches = new CommandBatchPreparer(CommandBatchPreparerDependencies) + .BatchCommands(entries, updateAdapter); foreach (var commandBatch in commandBatches) { @@ -2112,8 +2115,8 @@ private static ReferentialAction ToReferentialAction(DeleteBehavior deleteBehavi private class EntryMapping { - public HashSet SourceEntries { get; } = new HashSet(); - public HashSet TargetEntries { get; } = new HashSet(); + public HashSet SourceEntries { get; } = new HashSet(); + public HashSet TargetEntries { get; } = new HashSet(); public bool RecreateRow { get; set; } } diff --git a/src/EFCore.Relational/Storage/RelationalDatabase.cs b/src/EFCore.Relational/Storage/RelationalDatabase.cs index 63eea42dce0..525d3d01ff0 100644 --- a/src/EFCore.Relational/Storage/RelationalDatabase.cs +++ b/src/EFCore.Relational/Storage/RelationalDatabase.cs @@ -55,10 +55,15 @@ public RelationalDatabase( /// The number of state entries persisted to the database. public override int SaveChanges( IList entries) - => RelationalDependencies.BatchExecutor.Execute( + { + Check.NotNull(entries, nameof(entries)); + + return RelationalDependencies.BatchExecutor.Execute( RelationalDependencies.BatchPreparer.BatchCommands( - Check.NotNull(entries, nameof(entries))), + entries, + Dependencies.UpdateAdapterFactory.Create()), RelationalDependencies.Connection); + } /// /// Asynchronously persists changes from the supplied entries to the database. @@ -72,10 +77,15 @@ public override int SaveChanges( public override Task SaveChangesAsync( IList entries, CancellationToken cancellationToken = default) - => RelationalDependencies.BatchExecutor.ExecuteAsync( + { + Check.NotNull(entries, nameof(entries)); + + return RelationalDependencies.BatchExecutor.ExecuteAsync( RelationalDependencies.BatchPreparer.BatchCommands( - Check.NotNull(entries, nameof(entries))), + entries, + Dependencies.UpdateAdapterFactory.Create()), RelationalDependencies.Connection, cancellationToken); + } } } diff --git a/src/EFCore.Relational/Update/ICommandBatchPreparer.cs b/src/EFCore.Relational/Update/ICommandBatchPreparer.cs index 61740e1cc9f..9be281323d8 100644 --- a/src/EFCore.Relational/Update/ICommandBatchPreparer.cs +++ b/src/EFCore.Relational/Update/ICommandBatchPreparer.cs @@ -29,7 +29,10 @@ public interface ICommandBatchPreparer /// list of s. /// /// The entries that represent the entities to be modified. + /// The model data. /// The list of batches to execute. - IEnumerable BatchCommands([NotNull] IList entries); + IEnumerable BatchCommands( + [NotNull] IList entries, + [NotNull] IUpdateAdapter modelData); } } diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs index 6e347cc4352..1043e240b67 100644 --- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs +++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs @@ -38,7 +38,6 @@ public class CommandBatchPreparer : ICommandBatchPreparer private readonly IComparer _modificationCommandComparer; private readonly IKeyValueIndexFactorySource _keyValueIndexFactorySource; private readonly int _minBatchSize; - private IStateManager _stateManager; private readonly bool _sensitiveLoggingEnabled; private IReadOnlyDictionary<(string Schema, string Name), SharedTableEntryMapFactory> _sharedTableEntryMapFactories; @@ -67,18 +66,18 @@ public CommandBatchPreparer([NotNull] CommandBatchPreparerDependencies dependenc private CommandBatchPreparerDependencies Dependencies { get; } - private IStateManager StateManager => _stateManager ?? (_stateManager = Dependencies.StateManager()); - /// /// 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 BatchCommands(IList entries) + public virtual IEnumerable BatchCommands( + IList entries, + IUpdateAdapter modelData) { var parameterNameGenerator = _parameterNameGeneratorFactory.Create(); - var commands = CreateModificationCommands(entries, parameterNameGenerator.GenerateNext); + var commands = CreateModificationCommands(entries, modelData, parameterNameGenerator.GenerateNext); var sortedCommandSets = TopologicalSort(commands); // TODO: Enable batching of dependent commands by passing through the dependency graph @@ -157,13 +156,14 @@ private ModificationCommandBatch StartNewBatch(ParameterNameGenerator parameterN /// protected virtual IEnumerable CreateModificationCommands( [NotNull] IList entries, + [NotNull] IUpdateAdapter modelData, [NotNull] Func generateParameterName) { var commands = new List(); if (_sharedTableEntryMapFactories == null) { _sharedTableEntryMapFactories = SharedTableEntryMap - .CreateSharedTableEntryMapFactories(entries[0].EntityType.Model, StateManager); + .CreateSharedTableEntryMapFactories(modelData.Model, modelData); } Dictionary<(string Schema, string Name), SharedTableEntryMap> sharedTablesCommandsMap = @@ -308,7 +308,7 @@ private void AddUnchangedSharingEntries( continue; } - entry.SetEntityState(EntityState.Modified, modifyProperties: false); + entry.EntityState = EntityState.Modified; command.AddEntry(entry); entries.Add(entry); diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparerDependencies.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparerDependencies.cs index e87e26a74fb..688b4beb35a 100644 --- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparerDependencies.cs +++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparerDependencies.cs @@ -1,10 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Generic; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; @@ -61,7 +59,6 @@ public CommandBatchPreparerDependencies( [NotNull] IParameterNameGeneratorFactory parameterNameGeneratorFactory, [NotNull] IComparer modificationCommandComparer, [NotNull] IKeyValueIndexFactorySource keyValueIndexFactorySource, - [NotNull] Func stateManager, [NotNull] ILoggingOptions loggingOptions, [NotNull] IDiagnosticsLogger updateLogger, [NotNull] IDbContextOptions options) @@ -70,7 +67,6 @@ public CommandBatchPreparerDependencies( ParameterNameGeneratorFactory = parameterNameGeneratorFactory; ModificationCommandComparer = modificationCommandComparer; KeyValueIndexFactorySource = keyValueIndexFactorySource; - StateManager = stateManager; LoggingOptions = loggingOptions; UpdateLogger = updateLogger; Options = options; @@ -108,14 +104,6 @@ public CommandBatchPreparerDependencies( /// public IKeyValueIndexFactorySource KeyValueIndexFactorySource { get; } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public Func StateManager { 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 @@ -151,7 +139,6 @@ public CommandBatchPreparerDependencies With([NotNull] IModificationCommandBatch ParameterNameGeneratorFactory, ModificationCommandComparer, KeyValueIndexFactorySource, - StateManager, LoggingOptions, UpdateLogger, Options); @@ -167,7 +154,6 @@ public CommandBatchPreparerDependencies With([NotNull] IParameterNameGeneratorFa parameterNameGeneratorFactory, ModificationCommandComparer, KeyValueIndexFactorySource, - StateManager, LoggingOptions, UpdateLogger, Options); @@ -183,7 +169,6 @@ public CommandBatchPreparerDependencies With([NotNull] IComparer - /// Clones this dependency parameter object with one service replaced. - /// - /// A replacement for the current dependency of this type. - /// A new parameter object with the given service replaced. - public CommandBatchPreparerDependencies With([NotNull] Func stateManager) - => new CommandBatchPreparerDependencies( - ModificationCommandBatchFactory, - ParameterNameGeneratorFactory, - ModificationCommandComparer, - KeyValueIndexFactorySource, - stateManager, LoggingOptions, UpdateLogger, Options); @@ -231,7 +199,6 @@ public CommandBatchPreparerDependencies With([NotNull] ILoggingOptions loggingOp ParameterNameGeneratorFactory, ModificationCommandComparer, KeyValueIndexFactorySource, - StateManager, loggingOptions, UpdateLogger, Options); @@ -247,7 +214,6 @@ public CommandBatchPreparerDependencies With([NotNull] IDiagnosticsLogger public class SharedTableEntryMap { - private readonly IStateManager _stateManager; + private readonly IUpdateAdapter _updateAdapter; private readonly IReadOnlyDictionary> _principals; private readonly IReadOnlyDictionary> _dependents; private readonly string _name; @@ -27,8 +26,8 @@ public class SharedTableEntryMap private readonly SharedTableEntryValueFactory _createElement; private readonly IComparer _comparer; - private readonly Dictionary _entryValueMap - = new Dictionary(); + private readonly Dictionary _entryValueMap + = new Dictionary(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -37,14 +36,14 @@ private readonly Dictionary _entryValueMap /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public SharedTableEntryMap( - [NotNull] IStateManager stateManager, + [NotNull] IUpdateAdapter updateAdapter, [NotNull] IReadOnlyDictionary> principals, [NotNull] IReadOnlyDictionary> dependents, [NotNull] string name, [CanBeNull] string schema, [NotNull] SharedTableEntryValueFactory createElement) { - _stateManager = stateManager; + _updateAdapter = updateAdapter; _principals = principals; _dependents = dependents; _name = name; @@ -60,7 +59,9 @@ public SharedTableEntryMap( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public static Dictionary<(string Schema, string Name), SharedTableEntryMapFactory> - CreateSharedTableEntryMapFactories([NotNull] IModel model, [NotNull] IStateManager stateManager) + CreateSharedTableEntryMapFactories( + [NotNull] IModel model, + [NotNull] IUpdateAdapter updateAdapter) { var tables = new Dictionary<(string Schema, string TableName), List>(); foreach (var entityType in model.GetEntityTypes().Where(et => et.FindPrimaryKey() != null)) @@ -83,7 +84,7 @@ public SharedTableEntryMap( continue; } - var factory = CreateSharedTableEntryMapFactory(tableMapping.Value, stateManager, tableMapping.Key.TableName, tableMapping.Key.Schema); + var factory = CreateSharedTableEntryMapFactory(tableMapping.Value, updateAdapter, tableMapping.Key.TableName, tableMapping.Key.Schema); sharedTablesMap.Add(tableMapping.Key, factory); } @@ -99,7 +100,7 @@ public SharedTableEntryMap( /// public static SharedTableEntryMapFactory CreateSharedTableEntryMapFactory( [NotNull] IReadOnlyList entityTypes, - [NotNull] IStateManager stateManager, + [NotNull] IUpdateAdapter updateAdapter, [NotNull] string tableName, [NotNull] string schema) { @@ -137,7 +138,7 @@ public static SharedTableEntryMapFactory CreateSharedTableEntryMapFactor } return createElement => new SharedTableEntryMap( - stateManager, + updateAdapter, principals, dependents, tableName, @@ -189,12 +190,12 @@ public virtual TValue GetOrAddValue([NotNull] IUpdateEntry entry) /// public virtual IReadOnlyList GetDependents([NotNull] IEntityType entityType) => _dependents[entityType]; - private InternalEntityEntry GetMainEntry(IUpdateEntry entry) + private IUpdateEntry GetMainEntry(IUpdateEntry entry) { var entityType = entry.EntityType.RootType(); if (_principals[entityType].Count == 0) { - return (InternalEntityEntry)entry; + return entry; } foreach (var foreignKey in entityType.FindForeignKeys(entityType.FindPrimaryKey().Properties)) @@ -202,7 +203,7 @@ private InternalEntityEntry GetMainEntry(IUpdateEntry entry) if (foreignKey.PrincipalKey.IsPrimaryKey() && _principals.ContainsKey(foreignKey.PrincipalEntityType)) { - var principalEntry = _stateManager.GetPrincipal((InternalEntityEntry)entry, foreignKey); + var principalEntry = _updateAdapter.FindPrincipal(entry, foreignKey); if (principalEntry != null) { return GetMainEntry(principalEntry); @@ -210,7 +211,7 @@ private InternalEntityEntry GetMainEntry(IUpdateEntry entry) } } - return (InternalEntityEntry)entry; + return entry; } /// @@ -219,15 +220,15 @@ private InternalEntityEntry GetMainEntry(IUpdateEntry entry) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IReadOnlyList GetAllEntries([NotNull] IUpdateEntry entry) + public virtual IReadOnlyList GetAllEntries([NotNull] IUpdateEntry entry) { - var entries = new List(); + var entries = new List(); AddAllDependentsInclusive(GetMainEntry(entry), entries); return entries; } - private void AddAllDependentsInclusive(InternalEntityEntry entry, List entries) + private void AddAllDependentsInclusive(IUpdateEntry entry, List entries) { entries.Add(entry); foreach (var foreignKey in entry.EntityType.GetReferencingForeignKeys()) @@ -236,7 +237,7 @@ private void AddAllDependentsInclusive(InternalEntityEntry entry, List GetNonDeletedEntities() /// 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. /// + IModel Model { get; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// IEntityMaterializerSource EntityMaterializerSource { get; } /// @@ -244,7 +249,7 @@ IEnumerable GetNonDeletedEntities() /// 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. /// - InternalEntityEntry GetPrincipal([NotNull] InternalEntityEntry dependentEntry, [NotNull] IForeignKey foreignKey); + InternalEntityEntry FindPrincipal([NotNull] InternalEntityEntry dependentEntry, [NotNull] IForeignKey foreignKey); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -252,7 +257,7 @@ IEnumerable GetNonDeletedEntities() /// 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. /// - InternalEntityEntry GetPrincipalUsingPreStoreGeneratedValues([NotNull] InternalEntityEntry dependentEntry, [NotNull] IForeignKey foreignKey); + InternalEntityEntry FindPrincipalUsingPreStoreGeneratedValues([NotNull] InternalEntityEntry dependentEntry, [NotNull] IForeignKey foreignKey); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -260,7 +265,7 @@ IEnumerable GetNonDeletedEntities() /// 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. /// - InternalEntityEntry GetPrincipalUsingRelationshipSnapshot([NotNull] InternalEntityEntry dependentEntry, [NotNull] IForeignKey foreignKey); + InternalEntityEntry FindPrincipalUsingRelationshipSnapshot([NotNull] InternalEntityEntry dependentEntry, [NotNull] IForeignKey foreignKey); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index 0d622a37371..be72a7a63a5 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -14,7 +14,6 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; @@ -66,6 +65,20 @@ protected InternalEntityEntry( /// 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 IUpdateEntry.SetOriginalValue(IProperty property, object value) + => SetOriginalValue(property, value); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + void IUpdateEntry.SetPropertyModified(IProperty property) + => SetPropertyModified(property); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// public virtual IEntityType EntityType { [DebuggerStepThrough] get; } /// @@ -74,6 +87,16 @@ protected InternalEntityEntry( /// 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. /// + EntityState IUpdateEntry.EntityState + { + get => EntityState; + set => SetEntityState(value, modifyProperties: false); + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// public virtual IStateManager StateManager { [DebuggerStepThrough] get; } /// diff --git a/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs b/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs index 6a3f6573162..69769ce7057 100644 --- a/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs +++ b/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs @@ -161,7 +161,7 @@ public virtual void NavigationReferenceChanged(InternalEntityEntry entry, INavig // Navigation points to dependent and is 1:1. Find the principal that previously pointed to that // dependent and null out its navigation property. A.k.a. reference stealing. // However, if the reference is already set to point to something else, then don't change it. - var victimPrincipalEntry = stateManager.GetPrincipal(newTargetEntry, foreignKey); + var victimPrincipalEntry = stateManager.FindPrincipal(newTargetEntry, foreignKey); if (victimPrincipalEntry != null && victimPrincipalEntry != entry && ReferenceEquals(victimPrincipalEntry[navigation], newTargetEntry.Entity)) @@ -268,7 +268,7 @@ public virtual void NavigationCollectionChanged( // For a dependent added to the collection, remove it from the collection of // the principal entity that it was previously part of - var oldPrincipalEntry = stateManager.GetPrincipalUsingRelationshipSnapshot(newTargetEntry, foreignKey); + var oldPrincipalEntry = stateManager.FindPrincipalUsingRelationshipSnapshot(newTargetEntry, foreignKey); if (oldPrincipalEntry != null && oldPrincipalEntry != entry) { @@ -328,9 +328,9 @@ public virtual void KeyPropertyChanged( foreach (var foreignKey in containingForeignKeys) { - var newPrincipalEntry = stateManager.GetPrincipal(entry, foreignKey) - ?? stateManager.GetPrincipalUsingPreStoreGeneratedValues(entry, foreignKey); - var oldPrincipalEntry = stateManager.GetPrincipalUsingRelationshipSnapshot(entry, foreignKey); + var newPrincipalEntry = stateManager.FindPrincipal(entry, foreignKey) + ?? stateManager.FindPrincipalUsingPreStoreGeneratedValues(entry, foreignKey); + var oldPrincipalEntry = stateManager.FindPrincipalUsingRelationshipSnapshot(entry, foreignKey); var principalToDependent = foreignKey.PrincipalToDependent; if (principalToDependent != null) @@ -536,7 +536,7 @@ private void DeleteFixup(InternalEntityEntry entry) var principalToDependent = foreignKey.PrincipalToDependent; if (principalToDependent != null) { - var principalEntry = stateManager.GetPrincipal(entry, foreignKey); + var principalEntry = stateManager.FindPrincipal(entry, foreignKey); if (principalEntry != null && principalEntry.EntityState != EntityState.Deleted) { @@ -586,7 +586,7 @@ private void InitialFixup( { if (handledForeignKeys?.Contains(foreignKey) != true) { - var principalEntry = stateManager.GetPrincipal(entry, foreignKey); + var principalEntry = stateManager.FindPrincipal(entry, foreignKey); if (principalEntry != null) { // Set navigation to principal based on FK properties @@ -875,7 +875,7 @@ private void ConditionallyNullForeignKeyProperties( var dependentProperties = foreignKey.Properties; var hasOnlyKeyProperties = true; - var currentPrincipal = dependentEntry.StateManager.GetPrincipal(dependentEntry, foreignKey); + var currentPrincipal = dependentEntry.StateManager.FindPrincipal(dependentEntry, foreignKey); if (currentPrincipal != null && currentPrincipal != principalEntry) { diff --git a/src/EFCore/ChangeTracking/Internal/StateManager.cs b/src/EFCore/ChangeTracking/Internal/StateManager.cs index 38fdac4f5ea..bedfbd41480 100644 --- a/src/EFCore/ChangeTracking/Internal/StateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/StateManager.cs @@ -176,6 +176,12 @@ public virtual void StateChanging(InternalEntityEntry entry, EntityState newStat /// 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 IModel Model => _model; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// public virtual IEntityFinderFactory EntityFinderFactory { get; } /// @@ -729,7 +735,7 @@ public virtual IEnumerable> GetRecordedR /// 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 InternalEntityEntry GetPrincipal( + public virtual InternalEntityEntry FindPrincipal( InternalEntityEntry dependentEntry, IForeignKey foreignKey) => FilterIncompatiblePrincipal( @@ -743,7 +749,7 @@ public virtual InternalEntityEntry GetPrincipal( /// 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 InternalEntityEntry GetPrincipalUsingPreStoreGeneratedValues( + public virtual InternalEntityEntry FindPrincipalUsingPreStoreGeneratedValues( InternalEntityEntry dependentEntry, IForeignKey foreignKey) => FilterIncompatiblePrincipal( foreignKey, @@ -756,7 +762,7 @@ public virtual InternalEntityEntry GetPrincipalUsingPreStoreGeneratedValues( /// 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 InternalEntityEntry GetPrincipalUsingRelationshipSnapshot( + public virtual InternalEntityEntry FindPrincipalUsingRelationshipSnapshot( InternalEntityEntry dependentEntry, IForeignKey foreignKey) => FilterIncompatiblePrincipal( foreignKey, diff --git a/src/EFCore/EFCore.csproj b/src/EFCore/EFCore.csproj index c5347e7cd21..ee214d8dc79 100644 --- a/src/EFCore/EFCore.csproj +++ b/src/EFCore/EFCore.csproj @@ -68,8 +68,4 @@ Microsoft.EntityFrameworkCore.DbSet - - - - diff --git a/src/EFCore/Extensions/EntityTypeExtensions.cs b/src/EFCore/Extensions/EntityTypeExtensions.cs index 4a8381a7349..dfae762e949 100644 --- a/src/EFCore/Extensions/EntityTypeExtensions.cs +++ b/src/EFCore/Extensions/EntityTypeExtensions.cs @@ -459,6 +459,15 @@ public static ChangeTrackingStrategy GetChangeTrackingStrategy([NotNull] this IE ?? entityType.Model.GetChangeTrackingStrategy(); } + /// + /// Gets the data stored in the model for the given entity type. + /// + /// The entity type. + /// If true, then provider values are used. + /// The data. + public static IEnumerable> GetSeedData([NotNull] this IEntityType entityType, bool providerValues = false) + => entityType.AsEntityType().GetSeedData(providerValues); + /// /// Gets the LINQ expression filter automatically applied to queries for this entity type. /// diff --git a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs index c3ff49f46f7..552057d79a8 100644 --- a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs +++ b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs @@ -21,6 +21,8 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.EntityFrameworkCore.Update; +using Microsoft.EntityFrameworkCore.Update.Internal; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.EntityFrameworkCore.ValueGeneration; using Microsoft.Extensions.Caching.Memory; @@ -111,7 +113,6 @@ public static readonly IDictionary CoreServices { typeof(INavigationFixer), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(ILocalViewListener), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IStateManager), new ServiceCharacteristics(ServiceLifetime.Scoped) }, - { typeof(Func), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IConcurrencyDetector), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IInternalEntityEntryNotifier), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IValueGenerationManager), new ServiceCharacteristics(ServiceLifetime.Scoped) }, @@ -131,6 +132,7 @@ public static readonly IDictionary CoreServices { typeof(ICompiledQueryCacheKeyGenerator), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IResultOperatorHandler), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IModel), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(IUpdateAdapterFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(ICurrentDbContext), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IDbContextDependencies), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IDbContextOptions), new ServiceCharacteristics(ServiceLifetime.Scoped) }, @@ -283,7 +285,6 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(p => p.GetService()); TryAdd(p => p.GetService()); TryAdd(p => p.GetService()); - TryAdd>(p => p.GetService); TryAdd(); TryAdd(); TryAdd(); @@ -296,6 +297,7 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(); TryAdd(p => new MemoryCache(new MemoryCacheOptions())); + TryAdd(); ServiceCollectionMap .TryAddSingleton(new DiagnosticListener(DbLoggerCategory.Name)); diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 8fe7a9c3bec..7c6fe0d277b 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -625,7 +625,7 @@ protected virtual void ValidateData([NotNull] IModel model, DiagnosticsLoggers l } IIdentityMap identityMap = null; - foreach (var seedDatum in entityType.GetData()) + foreach (var seedDatum in entityType.GetSeedData()) { foreach (var property in entityType.GetProperties()) { diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index 7b462261fee..3c34a19175c 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -2234,7 +2234,7 @@ public override void OnTypeMemberIgnored(string name) /// 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> GetData(bool providerValues = false) + public virtual IEnumerable> GetSeedData(bool providerValues = false) { if (_data == null || _data.Count == 0) diff --git a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs index 1f772fde562..e7025259f91 100644 --- a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs +++ b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs @@ -667,15 +667,6 @@ public static IEnumerable GetNotificationProperties( } } - /// - /// 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 IEnumerable> GetData([NotNull] this IEntityType entityType, bool providerValues = false) - => entityType.AsEntityType().GetData(providerValues); - /// /// 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/Storage/DatabaseDependencies.cs b/src/EFCore/Storage/DatabaseDependencies.cs index 90f686f5318..008fe82474d 100644 --- a/src/EFCore/Storage/DatabaseDependencies.cs +++ b/src/EFCore/Storage/DatabaseDependencies.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Update; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -47,11 +48,16 @@ public sealed class DatabaseDependencies /// /// /// Factory for compilation contexts to process LINQ queries. - public DatabaseDependencies([NotNull] IQueryCompilationContextFactory queryCompilationContextFactory) + /// Factory for creating model data tracker. + public DatabaseDependencies( + [NotNull] IQueryCompilationContextFactory queryCompilationContextFactory, + [NotNull] IUpdateAdapterFactory updateAdapterFactory) { Check.NotNull(queryCompilationContextFactory, nameof(queryCompilationContextFactory)); + Check.NotNull(updateAdapterFactory, nameof(updateAdapterFactory)); QueryCompilationContextFactory = queryCompilationContextFactory; + UpdateAdapterFactory = updateAdapterFactory; } /// @@ -59,6 +65,11 @@ public DatabaseDependencies([NotNull] IQueryCompilationContextFactory queryCompi /// public IQueryCompilationContextFactory QueryCompilationContextFactory { get; } + /// + /// Factory for creating model data tracker. + /// + public IUpdateAdapterFactory UpdateAdapterFactory { get; } + /// /// Clones this dependency parameter object with one service replaced. /// @@ -67,6 +78,16 @@ public DatabaseDependencies([NotNull] IQueryCompilationContextFactory queryCompi /// /// A new parameter object with the given service replaced. public DatabaseDependencies With([NotNull] IQueryCompilationContextFactory queryCompilationContextFactory) - => new DatabaseDependencies(Check.NotNull(queryCompilationContextFactory, nameof(queryCompilationContextFactory))); + => new DatabaseDependencies(queryCompilationContextFactory, UpdateAdapterFactory); + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// + /// A replacement for the current dependency of this type. + /// + /// A new parameter object with the given service replaced. + public DatabaseDependencies With([NotNull] IUpdateAdapterFactory updateAdapterFactory) + => new DatabaseDependencies(QueryCompilationContextFactory, updateAdapterFactory); } } diff --git a/src/EFCore/Update/IUpdateAdapter.cs b/src/EFCore/Update/IUpdateAdapter.cs new file mode 100644 index 00000000000..07351a88282 --- /dev/null +++ b/src/EFCore/Update/IUpdateAdapter.cs @@ -0,0 +1,76 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Microsoft.EntityFrameworkCore.Update +{ + /// + /// + /// Providers tracking capabilities for seed data stored in the model using + /// . + /// + /// + /// This interface is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + public interface IUpdateAdapter + { + /// + /// Gets the principal entry for the given dependent entry and foreign key. + /// + /// The dependent entry. + /// The foreign key that defines the relationship. + /// The principal, or null if none was found. + IUpdateEntry FindPrincipal([NotNull] IUpdateEntry dependentEntry, [NotNull] IForeignKey foreignKey); + + /// + /// Returns the dependents associated with the given principal and foreign key. + /// + /// The principal entry. + /// The foreign key that defines the relationship. + /// The dependents. + IEnumerable GetDependents([NotNull] IUpdateEntry principalEntry, [NotNull] IForeignKey foreignKey); + + /// + /// Finds the tracked entity for the given key values. + /// + /// The primary or alternate key to use. + /// The key values. + /// The entry for the found entity, or null if no entity with these key values is being tracked. + IUpdateEntry TryGetEntry([NotNull] IKey key, [NotNull] object[] keyValues); + + /// + /// All the entries currently being tracked. + /// + IEnumerable Entries { get; } + + /// + /// Causes the underlying tracker to detect changes made to the tracked entities. + /// + void DetectChanges(); + + /// + /// Gets all the entries that require inserts/updates/deletes in the database. + /// + /// The entries that need to be saved. + IList GetEntriesToSave(); + + /// + /// Creates a new entry with the given property values for the given entity type. + /// + /// A dictionary of property names to values. + /// The entity type. + /// The created entry. + IUpdateEntry CreateEntry([NotNull] IDictionary values, [NotNull] IEntityType entityType); + + /// + /// The model with which the data is associated. + /// + IModel Model { get; } + } +} diff --git a/src/EFCore/Update/IUpdateAdapterFactory.cs b/src/EFCore/Update/IUpdateAdapterFactory.cs new file mode 100644 index 00000000000..8d018b2912a --- /dev/null +++ b/src/EFCore/Update/IUpdateAdapterFactory.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Microsoft.EntityFrameworkCore.Update +{ + /// + /// + /// Factory for creating instances. + /// + /// + /// This interface is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + public interface IUpdateAdapterFactory + { + /// + /// Creates a tracker for the given model. + /// + /// The model for which a tracker is needed, or null to use the current model. + /// The new tracker. + IUpdateAdapter Create([CanBeNull] IModel model = null); + } +} diff --git a/src/EFCore/Update/IUpdateEntry.cs b/src/EFCore/Update/IUpdateEntry.cs index 0fc3e0b089f..2a931ab662d 100644 --- a/src/EFCore/Update/IUpdateEntry.cs +++ b/src/EFCore/Update/IUpdateEntry.cs @@ -18,6 +18,19 @@ namespace Microsoft.EntityFrameworkCore.Update /// public interface IUpdateEntry { + /// + /// Sets the original value of the given property. + /// + /// The property to set. + /// The value to set. + void SetOriginalValue(IProperty property, object value); + + /// + /// Marks the given property as modified. + /// + /// The property to mark as modified. + void SetPropertyModified(IProperty property); + /// /// The type of entity to be saved to the database. /// @@ -26,7 +39,7 @@ public interface IUpdateEntry /// /// The state of the entity to be saved. /// - EntityState EntityState { get; } + EntityState EntityState { get; set; } /// /// The other entry that has the same key values, if one exists. diff --git a/src/EFCore/Update/Internal/UpdateAdapter.cs b/src/EFCore/Update/Internal/UpdateAdapter.cs new file mode 100644 index 00000000000..73bcd745db7 --- /dev/null +++ b/src/EFCore/Update/Internal/UpdateAdapter.cs @@ -0,0 +1,90 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Microsoft.EntityFrameworkCore.Update.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class UpdateAdapter : IUpdateAdapter + { + private readonly IStateManager _stateManager; + private readonly IChangeDetector _changeDetector; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public UpdateAdapter( + [NotNull] IStateManager stateManager, + [NotNull] IChangeDetector changeDetector) + { + _stateManager = stateManager; + _changeDetector = changeDetector; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual IUpdateEntry FindPrincipal(IUpdateEntry dependentEntry, IForeignKey foreignKey) + => _stateManager.FindPrincipal((InternalEntityEntry)dependentEntry, foreignKey); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual IEnumerable GetDependents(IUpdateEntry principalEntry, IForeignKey foreignKey) + => _stateManager.GetDependents((InternalEntityEntry)principalEntry, foreignKey); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual IUpdateEntry TryGetEntry(IKey key, object[] keyValues) + => _stateManager.TryGetEntry(key, keyValues); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual IEnumerable Entries + => _stateManager.Entries; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual void DetectChanges() + => _changeDetector.DetectChanges(_stateManager); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual IList GetEntriesToSave() + => _stateManager.GetEntriesToSave(); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual IUpdateEntry CreateEntry( + IDictionary values, + IEntityType entityType) + => _stateManager.CreateEntry(values, entityType); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual IModel Model + => _stateManager.Model; + } +} diff --git a/src/EFCore/Update/Internal/UpdateAdapterFactory.cs b/src/EFCore/Update/Internal/UpdateAdapterFactory.cs new file mode 100644 index 00000000000..c7dd2fb6801 --- /dev/null +++ b/src/EFCore/Update/Internal/UpdateAdapterFactory.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.Update.Internal +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class UpdateAdapterFactory : IUpdateAdapterFactory + { + private readonly IServiceProvider _serviceProvider; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public UpdateAdapterFactory( + [NotNull] IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual IUpdateAdapter Create(IModel model = null) + => new UpdateAdapter( + model == null + ? _serviceProvider.GetService() + : new StateManager( + _serviceProvider.GetService().With(model)), + _serviceProvider.GetService()); + } +} diff --git a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs index f2750bf8316..1e782dc2ea8 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; @@ -16,6 +15,7 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.EntityFrameworkCore.TestUtilities.FakeProvider; +using Microsoft.EntityFrameworkCore.Update; using Microsoft.EntityFrameworkCore.Update.Internal; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -77,7 +77,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.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 6d762304145..c09ba5f787b 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -1032,7 +1032,7 @@ public virtual void Owned_types_are_stored_in_snapshot() { var entityWithOneProperty = o.FindEntityType(typeof(EntityWithOneProperty)); Assert.Equal("PK_Custom", entityWithOneProperty.GetKeys().Single().Relational().Name); - Assert.Equal(new object[] { 1 }, entityWithOneProperty.GetData().Single().Values); + Assert.Equal(new object[] { 1 }, entityWithOneProperty.GetSeedData().Single().Values); var ownership1 = entityWithOneProperty.FindNavigation(nameof(EntityWithOneProperty.EntityWithTwoProperties)) .ForeignKey; @@ -1052,7 +1052,7 @@ public virtual void Owned_types_are_stored_in_snapshot() Assert.Equal("Id", owned1index2.Properties[0].Name); Assert.False(owned1index2.IsUnique); Assert.Null(owned1index2.Relational().Filter); - Assert.Equal(new object[] { 1, -1 }, ownedType1.GetData().Single().Values); + Assert.Equal(new object[] { 1, -1 }, ownedType1.GetSeedData().Single().Values); Assert.Equal(nameof(EntityWithOneProperty), ownedType1.Relational().TableName); var entityWithStringKey = o.FindEntityType(typeof(EntityWithStringKey)); @@ -3092,7 +3092,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) } ", o => Assert.Collection( - o.GetEntityTypes().SelectMany(e => e.GetData()), + o.GetEntityTypes().SelectMany(e => e.GetSeedData()), seed => { Assert.Equal(42, seed["Id"]); diff --git a/test/EFCore.InMemory.Tests/InMemoryDatabaseCreatorTest.cs b/test/EFCore.InMemory.Tests/InMemoryDatabaseCreatorTest.cs index 87904696720..dfebeca5644 100644 --- a/test/EFCore.InMemory.Tests/InMemoryDatabaseCreatorTest.cs +++ b/test/EFCore.InMemory.Tests/InMemoryDatabaseCreatorTest.cs @@ -3,11 +3,10 @@ using System; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -55,9 +54,7 @@ private static InMemoryDatabaseCreator CreateDatabaseCreator(IServiceProvider se optionsBuilder.UseInMemoryDatabase(nameof(InMemoryDatabaseCreatorTest)); var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(serviceProvider, optionsBuilder.Options); - var model = CreateModel(); - return new InMemoryDatabaseCreator( - contextServices.GetRequiredService().With(model)); + return new InMemoryDatabaseCreator(contextServices.GetRequiredService()); } [Fact] diff --git a/test/EFCore.InMemory.Tests/InMemoryDatabaseTest.cs b/test/EFCore.InMemory.Tests/InMemoryDatabaseTest.cs index ddb3ed54784..7a2e4cb4437 100644 --- a/test/EFCore.InMemory.Tests/InMemoryDatabaseTest.cs +++ b/test/EFCore.InMemory.Tests/InMemoryDatabaseTest.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.InMemory.Diagnostics.Internal; @@ -50,17 +49,15 @@ public void Uses_persistent_database_if_configured_as_persistent() public void EnsureDatabaseCreated_returns_true_for_first_use_of_persistent_database_and_false_thereafter() { var serviceProvider = InMemoryTestHelpers.Instance.CreateServiceProvider(); - var model = CreateModel(); var store = CreateStore(serviceProvider); - var stateManager = CreateContextServices(serviceProvider).GetRequiredService().With(model); - Assert.True(store.EnsureDatabaseCreated(stateManager)); - Assert.False(store.EnsureDatabaseCreated(stateManager)); - Assert.False(store.EnsureDatabaseCreated(stateManager)); + Assert.True(store.EnsureDatabaseCreated()); + Assert.False(store.EnsureDatabaseCreated()); + Assert.False(store.EnsureDatabaseCreated()); store = CreateStore(serviceProvider); - Assert.False(store.EnsureDatabaseCreated(stateManager)); + Assert.False(store.EnsureDatabaseCreated()); } private static IInMemoryDatabase CreateStore(IServiceProvider serviceProvider) diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs index ef0a87f5db3..64c2051b77d 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs @@ -4,13 +4,13 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations.Operations; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.EntityFrameworkCore.Update; using Microsoft.EntityFrameworkCore.Update.Internal; using Xunit; @@ -121,7 +121,7 @@ protected virtual MigrationsModelDiffer CreateModelDiffer(IModel model) new MigrationsAnnotationProvider( new MigrationsAnnotationProviderDependencies()), ctx.GetService(), - ctx.GetService(), + ctx.GetService(), ctx.GetService()); } } diff --git a/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs b/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs index 8a49c41e270..ed13f6cea43 100644 --- a/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs +++ b/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs @@ -34,7 +34,11 @@ public void BatchCommands_creates_valid_batch_for_added_entities() entry.SetEntityState(EntityState.Added); - var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { entry }).ToArray(); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + + var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { entry }, modelData).ToArray(); Assert.Equal(1, commandBatches.Length); Assert.Equal(1, commandBatches.First().ModificationCommands.Count); @@ -77,7 +81,11 @@ public void BatchCommands_creates_valid_batch_for_modified_entities() entry.SetEntityState(EntityState.Modified); - var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { entry }).ToArray(); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + + var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { entry }, modelData).ToArray(); Assert.Equal(1, commandBatches.Length); Assert.Equal(1, commandBatches.First().ModificationCommands.Count); @@ -120,7 +128,11 @@ public void BatchCommands_creates_valid_batch_for_deleted_entities() entry.SetEntityState(EntityState.Deleted); - var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { entry }).ToArray(); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + + var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { entry }, modelData).ToArray(); Assert.Equal(1, commandBatches.Length); Assert.Equal(1, commandBatches.First().ModificationCommands.Count); @@ -153,6 +165,10 @@ public void BatchCommands_sorts_related_added_entities() }); entry.SetEntityState(EntityState.Added); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + var relatedEntry = stateManager.GetOrCreateEntry( new RelatedFakeEntity { @@ -160,7 +176,7 @@ public void BatchCommands_sorts_related_added_entities() }); relatedEntry.SetEntityState(EntityState.Added); - var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { relatedEntry, entry }).ToArray(); + var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { relatedEntry, entry }, modelData).ToArray(); Assert.Equal( new[] { entry, relatedEntry }, @@ -181,6 +197,10 @@ public void BatchCommands_sorts_added_and_related_modified_entities() }); entry.SetEntityState(EntityState.Added); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + var relatedEntry = stateManager.GetOrCreateEntry( new RelatedFakeEntity { @@ -188,7 +208,7 @@ public void BatchCommands_sorts_added_and_related_modified_entities() }); relatedEntry.SetEntityState(EntityState.Modified); - var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { relatedEntry, entry }).ToArray(); + var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { relatedEntry, entry }, modelData).ToArray(); Assert.Equal( new[] { entry, relatedEntry }, @@ -216,7 +236,11 @@ public void BatchCommands_sorts_unrelated_entities() }); secondEntry.SetEntityState(EntityState.Added); - var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { secondEntry, firstEntry }).ToArray(); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + + var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { secondEntry, firstEntry }, modelData).ToArray(); Assert.Equal( new[] { firstEntry, secondEntry }, @@ -254,7 +278,11 @@ public void BatchCommands_sorts_entities_when_reparenting() relatedEntry.SetEntityState(EntityState.Modified); relatedEntry.SetOriginalValue(relatedEntry.EntityType.FindProperty("RelatedId"), 42); - var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { relatedEntry, previousParent, newParent }).ToArray(); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + + var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { relatedEntry, previousParent, newParent }, modelData).ToArray(); Assert.Equal( new[] { newParent, relatedEntry, previousParent }, @@ -291,7 +319,11 @@ public void BatchCommands_sorts_when_reassigning_child() }); newChild.SetEntityState(EntityState.Added); - var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { newChild, previousChild }).ToArray(); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + + var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { newChild, previousChild }, modelData).ToArray(); Assert.Equal( new[] { previousChild, newChild }, @@ -344,8 +376,12 @@ public void BatchCommands_sorts_entities_while_reassigning_child_tree() }); newChildEntity.SetEntityState(EntityState.Added); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + var sortedEntities = CreateCommandBatchPreparer() - .BatchCommands(new[] { newEntity, newChildEntity, oldEntity, oldChildEntity }) + .BatchCommands(new[] { newEntity, newChildEntity, oldEntity, oldChildEntity }, modelData) .Select(cb => cb.ModificationCommands.Single()).Select(mc => mc.Entries.Single()).ToArray(); Assert.Equal( @@ -379,7 +415,11 @@ public void BatchCommands_creates_batches_lazily() var factory = (TestModificationCommandBatchFactory)configuration.GetService(); - var commandBatches = CreateCommandBatchPreparer(factory).BatchCommands(new[] { relatedEntry, entry }); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + + var commandBatches = CreateCommandBatchPreparer(factory).BatchCommands(new[] { relatedEntry, entry }, modelData); using (var commandBatchesEnumerator = commandBatches.GetEnumerator()) { @@ -426,8 +466,12 @@ public void Batch_command_does_not_order_non_unique_index_values() fakeEntry2.SetEntityState(EntityState.Modified); fakeEntry2.SetOriginalValue(fakeEntry2.EntityType.FindProperty(nameof(FakeEntity.Value)), "Test"); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + var sortedEntities = CreateCommandBatchPreparer() - .BatchCommands(new[] { fakeEntry, fakeEntry2, relatedFakeEntry }) + .BatchCommands(new[] { fakeEntry, fakeEntry2, relatedFakeEntry }, modelData) .Select(cb => cb.ModificationCommands.Single()).Select(mc => mc.Entries.Single()).ToArray(); Assert.Equal( @@ -480,6 +524,10 @@ public void Batch_command_throws_on_commands_with_circular_dependencies(bool sen }); relatedFakeEntry.SetEntityState(EntityState.Added); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + var expectedCycle = sensitiveLogging ? "FakeEntity { 'Id': 42 } [Added] <- ForeignKey { 'RelatedId': 42 } RelatedFakeEntity { 'Id': 1 } [Added] <- ForeignKey { 'RelatedId': 1 } FakeEntity { 'Id': 42 } [Added]" : "FakeEntity [Added] <- ForeignKey { 'RelatedId' } RelatedFakeEntity [Added] <- ForeignKey { 'RelatedId' } FakeEntity [Added]"; @@ -487,8 +535,8 @@ public void Batch_command_throws_on_commands_with_circular_dependencies(bool sen Assert.Equal( CoreStrings.CircularDependency(expectedCycle), Assert.Throws( - () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: sensitiveLogging) - .BatchCommands(new[] { fakeEntry, relatedFakeEntry }).ToArray()).Message); + () => CreateCommandBatchPreparer(updateAdapter: modelData, sensitiveLogging: sensitiveLogging) + .BatchCommands(new[] { fakeEntry, relatedFakeEntry }, modelData).ToArray()).Message); } [InlineData(true)] @@ -526,6 +574,10 @@ public void Batch_command_throws_on_commands_with_circular_dependencies_includin fakeEntry2.SetEntityState(EntityState.Modified); fakeEntry2.SetOriginalValue(fakeEntry2.EntityType.FindProperty(nameof(FakeEntity.UniqueValue)), "Test"); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + var expectedCycle = sensitiveLogging ? "FakeEntity { 'Id': 42 } [Added] <- ForeignKey { 'RelatedId': 42 } RelatedFakeEntity { 'Id': 1 } [Added] <- ForeignKey { 'RelatedId': 1 } FakeEntity { 'Id': 2 } [Modified] <- Index { 'UniqueValue': Test } FakeEntity { 'Id': 42 } [Added]" : "FakeEntity [Added] <- ForeignKey { 'RelatedId' } RelatedFakeEntity [Added] <- ForeignKey { 'RelatedId' } FakeEntity [Modified] <- Index { 'UniqueValue' } FakeEntity [Added]"; @@ -533,8 +585,8 @@ public void Batch_command_throws_on_commands_with_circular_dependencies_includin Assert.Equal( CoreStrings.CircularDependency(expectedCycle), Assert.Throws( - () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: sensitiveLogging) - .BatchCommands(new[] { fakeEntry, relatedFakeEntry, fakeEntry2 }).ToArray()).Message); + () => CreateCommandBatchPreparer(updateAdapter: modelData, sensitiveLogging: sensitiveLogging) + .BatchCommands(new[] { fakeEntry, relatedFakeEntry, fakeEntry2 }, modelData).ToArray()).Message); } [InlineData(true)] @@ -570,6 +622,10 @@ public void Batch_command_throws_on_delete_commands_with_circular_dependencies(b }); anotherFakeEntry.SetEntityState(EntityState.Deleted); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + var expectedCycle = sensitiveLogging ? "FakeEntity { 'Id': 1 } [Deleted] ForeignKey { 'RelatedId': 2 } <- RelatedFakeEntity { 'Id': 2 } [Deleted] ForeignKey { 'RelatedId': 1 } <- FakeEntity { 'Id': 1 } [Deleted]" : "FakeEntity [Deleted] ForeignKey { 'RelatedId' } <- RelatedFakeEntity [Deleted] ForeignKey { 'RelatedId' } <- FakeEntity [Deleted]"; @@ -577,9 +633,9 @@ public void Batch_command_throws_on_delete_commands_with_circular_dependencies(b Assert.Equal( CoreStrings.CircularDependency(expectedCycle), Assert.Throws( - () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: sensitiveLogging).BatchCommands( + () => CreateCommandBatchPreparer(updateAdapter: modelData, sensitiveLogging: sensitiveLogging).BatchCommands( // Order is important for this test. Entry which is not part of cycle but tail should come first. - new[] { anotherFakeEntry, fakeEntry, relatedFakeEntry }).ToArray()).Message); + new[] { anotherFakeEntry, fakeEntry, relatedFakeEntry }, modelData).ToArray()).Message); } [Fact] @@ -606,8 +662,12 @@ public void BatchCommands_works_with_duplicate_values_for_unique_indexes() fakeEntry2.SetEntityState(EntityState.Modified); fakeEntry2.SetOriginalValue(fakeEntry.EntityType.FindProperty(nameof(FakeEntity.UniqueValue)), "Test"); - var batches = CreateCommandBatchPreparer(stateManager: stateManager) - .BatchCommands(new[] { fakeEntry, fakeEntry2 }).ToArray(); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + + var batches = CreateCommandBatchPreparer(updateAdapter: modelData) + .BatchCommands(new[] { fakeEntry, fakeEntry2 }, modelData).ToArray(); Assert.Equal(2, batches.Length); } @@ -632,7 +692,12 @@ public void BatchCommands_creates_valid_batch_for_shared_table_added_entities() var secondEntry = stateManager.GetOrCreateEntry(second); secondEntry.SetEntityState(EntityState.Added); - var commandBatches = CreateCommandBatchPreparer(stateManager: stateManager).BatchCommands(new[] { firstEntry, secondEntry }) + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + + var commandBatches = CreateCommandBatchPreparer(updateAdapter: modelData) + .BatchCommands(new[] { firstEntry, secondEntry }, modelData) .ToArray(); Assert.Equal(1, commandBatches.Length); Assert.Equal(1, commandBatches.First().ModificationCommands.Count); @@ -687,7 +752,14 @@ public void BatchCommands_creates_valid_batch_for_shared_table_modified_entities entry.SetEntityState(EntityState.Modified); - var commandBatches = CreateCommandBatchPreparer(stateManager: stateManager).BatchCommands(new[] { entry }).ToArray(); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + + var commandBatches = CreateCommandBatchPreparer(updateAdapter: modelData) + .BatchCommands(new[] { entry }, modelData) + .ToArray(); + Assert.Equal(1, commandBatches.Length); Assert.Equal(1, commandBatches.First().ModificationCommands.Count); @@ -746,8 +818,12 @@ public void BatchCommands_creates_valid_batch_for_shared_table_deleted_entities( var secondEntry = stateManager.GetOrCreateEntry(second); secondEntry.SetEntityState(EntityState.Deleted); - var commandBatches = CreateCommandBatchPreparer(stateManager: stateManager) - .BatchCommands(new[] { firstEntry, secondEntry }).ToArray(); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + + var commandBatches = CreateCommandBatchPreparer(updateAdapter: modelData) + .BatchCommands(new[] { firstEntry, secondEntry }, modelData).ToArray(); Assert.Equal(1, commandBatches.Length); Assert.Equal(1, commandBatches.First().ModificationCommands.Count); @@ -799,6 +875,10 @@ public void BatchCommands_throws_on_conflicting_updates_for_shared_table_added_e var secondEntry = stateManager.GetOrCreateEntry(second); secondEntry.SetEntityState(EntityState.Deleted); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + if (sensitiveLogging) { Assert.Equal( @@ -806,8 +886,8 @@ public void BatchCommands_throws_on_conflicting_updates_for_shared_table_added_e nameof(RelatedFakeEntity), "{Id: 42}", EntityState.Deleted, nameof(FakeEntity), "{Id: 42}", EntityState.Added), Assert.Throws( - () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: true) - .BatchCommands(new[] { firstEntry, secondEntry }).ToArray()).Message); + () => CreateCommandBatchPreparer(updateAdapter: modelData, sensitiveLogging: true) + .BatchCommands(new[] { firstEntry, secondEntry }, modelData).ToArray()).Message); } else { @@ -816,8 +896,8 @@ public void BatchCommands_throws_on_conflicting_updates_for_shared_table_added_e nameof(RelatedFakeEntity), EntityState.Deleted, nameof(FakeEntity), EntityState.Added), Assert.Throws( - () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: false) - .BatchCommands(new[] { firstEntry, secondEntry }).ToArray()).Message); + () => CreateCommandBatchPreparer(updateAdapter: modelData, sensitiveLogging: false) + .BatchCommands(new[] { firstEntry, secondEntry }, modelData).ToArray()).Message); } } @@ -856,6 +936,10 @@ public void BatchCommands_throws_on_conflicting_values_for_shared_table_added_en new EntityEntry(secondEntry).Property(e => e.RelatedId).OriginalValue = 2; } + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + if (useCurrentValues) { if (sensitiveLogging) @@ -865,8 +949,8 @@ public void BatchCommands_throws_on_conflicting_values_for_shared_table_added_en nameof(FakeEntity), nameof(RelatedFakeEntity), "{Id: 42}", "{RelatedId: 1}", "{RelatedId: 2}", "{'RelatedId'}"), Assert.Throws( - () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: true) - .BatchCommands(new[] { firstEntry, secondEntry }).ToArray()).Message); + () => CreateCommandBatchPreparer(updateAdapter: modelData, sensitiveLogging: true) + .BatchCommands(new[] { firstEntry, secondEntry }, modelData).ToArray()).Message); } else { @@ -875,8 +959,8 @@ public void BatchCommands_throws_on_conflicting_values_for_shared_table_added_en nameof(FakeEntity), nameof(RelatedFakeEntity), "{'RelatedId'}", "{'RelatedId'}", "{'RelatedId'}"), Assert.Throws( - () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: false) - .BatchCommands(new[] { firstEntry, secondEntry }).ToArray()).Message); + () => CreateCommandBatchPreparer(updateAdapter: modelData, sensitiveLogging: false) + .BatchCommands(new[] { firstEntry, secondEntry }, modelData).ToArray()).Message); } } else @@ -888,8 +972,8 @@ public void BatchCommands_throws_on_conflicting_values_for_shared_table_added_en nameof(FakeEntity), nameof(RelatedFakeEntity), "{Id: 42}", "{RelatedId: 1}", "{RelatedId: 2}", "{'RelatedId'}"), Assert.Throws( - () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: true) - .BatchCommands(new[] { firstEntry, secondEntry }).ToArray()).Message); + () => CreateCommandBatchPreparer(updateAdapter: modelData, sensitiveLogging: true) + .BatchCommands(new[] { firstEntry, secondEntry }, modelData).ToArray()).Message); } else { @@ -898,8 +982,8 @@ public void BatchCommands_throws_on_conflicting_values_for_shared_table_added_en nameof(FakeEntity), nameof(RelatedFakeEntity), "{'RelatedId'}", "{'RelatedId'}", "{'RelatedId'}"), Assert.Throws( - () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: false) - .BatchCommands(new[] { firstEntry, secondEntry }).ToArray()).Message); + () => CreateCommandBatchPreparer(updateAdapter: modelData, sensitiveLogging: false) + .BatchCommands(new[] { firstEntry, secondEntry }, modelData).ToArray()).Message); } } } @@ -928,14 +1012,18 @@ public void BatchCommands_throws_on_incomplete_updates_for_shared_table_no_princ var secondEntry = stateManager.GetOrCreateEntry(second); secondEntry.SetEntityState(state); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + if (sensitiveLogging) { Assert.Equal( RelationalStrings.SharedRowEntryCountMismatchSensitive( nameof(DerivedRelatedFakeEntity), nameof(FakeEntity), nameof(FakeEntity), "{Id: 42}", state), Assert.Throws( - () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: true) - .BatchCommands(new[] { firstEntry }).ToArray()).Message); + () => CreateCommandBatchPreparer(updateAdapter: modelData, sensitiveLogging: true) + .BatchCommands(new[] { firstEntry }, modelData).ToArray()).Message); } else { @@ -943,8 +1031,8 @@ public void BatchCommands_throws_on_incomplete_updates_for_shared_table_no_princ RelationalStrings.SharedRowEntryCountMismatch( nameof(DerivedRelatedFakeEntity), nameof(FakeEntity), nameof(FakeEntity), state), Assert.Throws( - () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: false) - .BatchCommands(new[] { firstEntry, secondEntry }).ToArray()).Message); + () => CreateCommandBatchPreparer(updateAdapter: modelData, sensitiveLogging: false) + .BatchCommands(new[] { firstEntry, secondEntry }, modelData).ToArray()).Message); } } @@ -969,9 +1057,13 @@ public void BatchCommands_works_with_incomplete_updates_for_shared_table_no_leaf }; var secondEntry = stateManager.GetOrCreateEntry(second); secondEntry.SetEntityState(state); - - var batches = CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: false) - .BatchCommands(new[] { firstEntry, secondEntry }).ToArray(); + + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + + var batches = CreateCommandBatchPreparer(updateAdapter: modelData, sensitiveLogging: false) + .BatchCommands(new[] { firstEntry, secondEntry }, modelData).ToArray(); Assert.Equal(1, batches.Length); } @@ -1001,6 +1093,10 @@ public void BatchCommands_throws_on_incomplete_updates_for_shared_table_no_middl var secondEntry = stateManager.GetOrCreateEntry(second); secondEntry.SetEntityState(state); + var modelData = new UpdateAdapter( + stateManager, + stateManager.Context.GetDependencies().ChangeDetector); + if (sensitiveLogging) { Assert.Equal( @@ -1008,8 +1104,8 @@ public void BatchCommands_throws_on_incomplete_updates_for_shared_table_no_middl nameof(AnotherFakeEntity), nameof(FakeEntity), nameof(DerivedRelatedFakeEntity), "{Id: 42}", state), Assert.Throws( () => - CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: true) - .BatchCommands(new[] { firstEntry, secondEntry }).ToArray()).Message); + CreateCommandBatchPreparer(updateAdapter: modelData, sensitiveLogging: true) + .BatchCommands(new[] { firstEntry, secondEntry }, modelData).ToArray()).Message); } else { @@ -1018,8 +1114,8 @@ public void BatchCommands_throws_on_incomplete_updates_for_shared_table_no_middl nameof(AnotherFakeEntity), nameof(FakeEntity), nameof(DerivedRelatedFakeEntity), state), Assert.Throws( () => - CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: false) - .BatchCommands(new[] { firstEntry, secondEntry }).ToArray()).Message); + CreateCommandBatchPreparer(updateAdapter: modelData, sensitiveLogging: false) + .BatchCommands(new[] { firstEntry, secondEntry }, modelData).ToArray()).Message); } } @@ -1028,16 +1124,13 @@ private static IServiceProvider CreateContextServices(IModel model) public ICommandBatchPreparer CreateCommandBatchPreparer( IModificationCommandBatchFactory modificationCommandBatchFactory = null, - IStateManager stateManager = null, + IUpdateAdapter updateAdapter = null, bool sensitiveLogging = false) { modificationCommandBatchFactory = modificationCommandBatchFactory ?? RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); - stateManager = stateManager - ?? RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService(); - var loggingOptions = new LoggingOptions(); if (sensitiveLogging) { @@ -1050,7 +1143,6 @@ public ICommandBatchPreparer CreateCommandBatchPreparer( new ParameterNameGeneratorFactory(new ParameterNameGeneratorDependencies()), new ModificationCommandComparer(), new KeyValueIndexFactorySource(), - () => stateManager, loggingOptions, new FakeDiagnosticsLogger(), new DbContextOptionsBuilder().Options)); diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs index 0c9ad51954a..dd28ef12981 100644 --- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs @@ -3,7 +3,6 @@ using System; using System.Reflection; -using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -14,6 +13,7 @@ using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.EntityFrameworkCore.Update; using Microsoft.EntityFrameworkCore.Update.Internal; using Xunit; @@ -832,7 +832,7 @@ protected override MigrationsModelDiffer CreateModelDiffer(IModel model) new SqlServerMigrationsAnnotationProvider( new MigrationsAnnotationProviderDependencies()), ctx.GetService(), - ctx.GetService(), + ctx.GetService(), ctx.GetService()); } diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index eaeed3b8865..3473f107af4 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -678,7 +678,7 @@ public virtual void Passes_for_ignored_invalid_properties() Validate(modelBuilder.Model); - var data = modelBuilder.Model.GetEntityTypes().Single().GetData(); + var data = modelBuilder.Model.GetEntityTypes().Single().GetSeedData(); Assert.Equal(-1, data.First().Values.Single()); Assert.Equal(-2, data.Last().Values.Single()); } diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index bc7d47a2d3c..b6f42eedb89 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -1279,7 +1279,7 @@ public virtual void Can_add_seed_data_objects() modelBuilder.FinalizeModel(); var customer = model.FindEntityType(typeof(Beta)); - var data = customer.GetData(); + var data = customer.GetSeedData(); Assert.Equal(2, data.Count()); Assert.Equal(-1, data.First()[nameof(Beta.Id)]); Assert.Equal(-2, data.Last()[nameof(Beta.Id)]); @@ -1311,7 +1311,7 @@ public virtual void Can_add_seed_data_anonymous_objects() modelBuilder.FinalizeModel(); var customer = model.FindEntityType(typeof(Beta)); - var data = customer.GetData(); + var data = customer.GetSeedData(); Assert.Equal(2, data.Count()); Assert.Equal(-1, data.First().Values.Single()); Assert.Equal(-2, data.Last().Values.Single()); diff --git a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs index 4e91f82f3f0..af690fadb0a 100644 --- a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs @@ -414,7 +414,7 @@ public virtual void Can_configure_owned_type_from_an_owned_type_collection() Assert.True(chainedOwnership.IsUnique); Assert.Equal(nameof(OrderDetails.OrderId), chainedOwned.FindPrimaryKey().Properties.Single().Name); Assert.Empty(chainedOwned.GetIndexes()); - Assert.Equal(-1, chainedOwned.GetData().Single()[nameof(OrderDetails.OrderId)]); + Assert.Equal(-1, chainedOwned.GetSeedData().Single()[nameof(OrderDetails.OrderId)]); Assert.Equal(nameof(OrderDetails.OrderId), chainedOwnership.Properties.Single().Name); Assert.Equal(nameof(OrderDetails.Order), chainedOwnership.DependentToPrincipal.Name); @@ -447,7 +447,7 @@ public virtual void Can_chain_owned_type_collection_configurations() var ownership = model.FindEntityType(typeof(Customer)).FindNavigation(nameof(Customer.Orders)).ForeignKey; var owned = ownership.DeclaringEntityType; Assert.Equal(1, ownership.DeclaringEntityType.GetForeignKeys().Count()); - var seedData = owned.GetData().Single(); + var seedData = owned.GetSeedData().Single(); Assert.Equal(-2, seedData[nameof(Order.OrderId)]); Assert.Equal(-1, seedData[nameof(Order.CustomerId)]); var chainedOwnership = owned.FindNavigation(nameof(Order.Products)).ForeignKey; diff --git a/test/EFCore.Tests/TestUtilities/FakeStateManager.cs b/test/EFCore.Tests/TestUtilities/FakeStateManager.cs index 80704b1214a..e239ac243aa 100644 --- a/test/EFCore.Tests/TestUtilities/FakeStateManager.cs +++ b/test/EFCore.Tests/TestUtilities/FakeStateManager.cs @@ -6,13 +6,11 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Update; @@ -122,13 +120,13 @@ public void RecordReferencedUntrackedEntity( public IEnumerable> GetRecordedReferrers(object referencedEntity, bool clear) => throw new NotImplementedException(); - public InternalEntityEntry GetPrincipal(InternalEntityEntry entityEntry, IForeignKey foreignKey) => + public InternalEntityEntry FindPrincipal(InternalEntityEntry entityEntry, IForeignKey foreignKey) => throw new NotImplementedException(); - public InternalEntityEntry GetPrincipalUsingPreStoreGeneratedValues(InternalEntityEntry entityEntry, IForeignKey foreignKey) => + public InternalEntityEntry FindPrincipalUsingPreStoreGeneratedValues(InternalEntityEntry entityEntry, IForeignKey foreignKey) => throw new NotImplementedException(); - public InternalEntityEntry GetPrincipalUsingRelationshipSnapshot(InternalEntityEntry entityEntry, IForeignKey foreignKey) => + public InternalEntityEntry FindPrincipalUsingRelationshipSnapshot(InternalEntityEntry entityEntry, IForeignKey foreignKey) => throw new NotImplementedException(); public DbContext Context => new DbContext(new DbContextOptionsBuilder() @@ -136,6 +134,7 @@ public InternalEntityEntry GetPrincipalUsingRelationshipSnapshot(InternalEntityE .UseInMemoryDatabase("D") .Options); + public IModel Model => throw new NotImplementedException(); public event EventHandler Tracked; public void OnTracked(InternalEntityEntry internalEntityEntry, bool fromQuery) => Tracked?.Invoke(null, null); public event EventHandler StateChanged;