CreateSharedTableEntryMapFactories(
            [NotNull] IModel model,
            [NotNull] IUpdateAdapter updateAdapter)
        {
            var tables = new Dictionary <(string Schema, string TableName), List <IEntityType> >();

            foreach (var entityType in model.GetEntityTypes().Where(et => et.FindPrimaryKey() != null))
            {
                var fullName = (entityType.Relational().Schema, entityType.Relational().TableName);
                if (!tables.TryGetValue(fullName, out var mappedEntityTypes))
                {
                    mappedEntityTypes = new List <IEntityType>();
                    tables.Add(fullName, mappedEntityTypes);
                }

                mappedEntityTypes.Add(entityType);
            }

            var sharedTablesMap = new Dictionary <(string Schema, string Name), SharedTableEntryMapFactory <TValue> >();

            foreach (var tableMapping in tables)
            {
                if (tableMapping.Value.Count <= 1)
                {
                    continue;
                }

                var factory = CreateSharedTableEntryMapFactory(tableMapping.Value, updateAdapter, tableMapping.Key.TableName, tableMapping.Key.Schema);

                sharedTablesMap.Add(tableMapping.Key, factory);
            }

            return(sharedTablesMap);
        }
Beispiel #2
0
    public ICommandBatchPreparer CreateCommandBatchPreparer(
        IModificationCommandBatchFactory modificationCommandBatchFactory = null,
        IUpdateAdapter updateAdapter = null,
        bool sensitiveLogging        = false)
    {
        modificationCommandBatchFactory ??=
        RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService <IModificationCommandBatchFactory>();

        var loggingOptions = new LoggingOptions();

        if (sensitiveLogging)
        {
            loggingOptions.Initialize(new DbContextOptionsBuilder <DbContext>().EnableSensitiveDataLogging().Options);
        }

        return(new CommandBatchPreparer(
                   new CommandBatchPreparerDependencies(
                       modificationCommandBatchFactory,
                       new ParameterNameGeneratorFactory(new ParameterNameGeneratorDependencies()),
                       new ModificationCommandComparer(),
                       new KeyValueIndexFactorySource(),
                       new ModificationCommandFactory(),
                       loggingOptions,
                       new FakeDiagnosticsLogger <DbLoggerCategory.Update>(),
                       new DbContextOptionsBuilder().Options)));
    }
Beispiel #3
0
 /// <summary>
 ///     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.
 /// </summary>
 public static SharedTableEntryMapFactory <TValue> CreateSharedTableEntryMapFactory(
     [NotNull] ITable table,
     [NotNull] IUpdateAdapter updateAdapter)
 => createElement
 => new SharedTableEntryMap <TValue>(
     table,
     updateAdapter,
     createElement);
Beispiel #4
0
 public List <ModificationCommandBatch> CreateBatches(
     IUpdateEntry[] entries,
     IUpdateAdapter updateAdapter,
     bool sensitiveLogging = false)
 => CreateCommandBatchPreparer(updateAdapter: updateAdapter, sensitiveLogging: sensitiveLogging)
 .BatchCommands(entries, updateAdapter)
 .Select(t => t.Batch)
 .ToList();
 /// <summary>
 ///     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.
 /// </summary>
 public SharedTableEntryMap(
     [NotNull] ITable table,
     [NotNull] IUpdateAdapter updateAdapter)
 {
     _table         = table;
     _updateAdapter = updateAdapter;
     _comparer      = new EntryComparer(table);
 }
Beispiel #6
0
 /// <summary>
 ///     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.
 /// </summary>
 public SharedTableEntryMap(
     [NotNull] ITable table,
     [NotNull] IUpdateAdapter updateAdapter,
     [NotNull] SharedTableEntryValueFactory <TValue> createElement)
 {
     _table         = table;
     _updateAdapter = updateAdapter;
     _createElement = createElement;
     _comparer      = new EntryComparer(table);
 }
        /// <summary>
        ///     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.
        /// </summary>
        public static SharedTableEntryMapFactory <TValue> CreateSharedTableEntryMapFactory(
            [NotNull] IReadOnlyList <IEntityType> entityTypes,
            [NotNull] IUpdateAdapter updateAdapter,
            [NotNull] string tableName,
            [NotNull] string schema)
        {
            var principals = new Dictionary <IEntityType, IReadOnlyList <IEntityType> >(entityTypes.Count);
            var dependents = new Dictionary <IEntityType, IReadOnlyList <IEntityType> >(entityTypes.Count);

            foreach (var entityType in entityTypes.Where(t => t.FindPrimaryKey() != null))
            {
                var principalList = new List <IEntityType>();
                foreach (var foreignKey in entityType.FindForeignKeys(entityType.FindPrimaryKey().Properties))
                {
                    if (foreignKey.PrincipalKey.IsPrimaryKey() &&
                        entityTypes.Contains(foreignKey.PrincipalEntityType) &&
                        !foreignKey.IsIntraHierarchical())
                    {
                        principalList.Add(foreignKey.PrincipalEntityType);
                    }
                }

                principals[entityType] = principalList;

                var dependentList = new List <IEntityType>();
                foreach (var referencingForeignKey in entityType.FindPrimaryKey().GetReferencingForeignKeys())
                {
                    if (referencingForeignKey.PrincipalEntityType.IsAssignableFrom(entityType) &&
                        entityTypes.Contains(referencingForeignKey.DeclaringEntityType) &&
                        !referencingForeignKey.IsIntraHierarchical() &&
                        (PropertyListComparer.Instance.Compare(
                             referencingForeignKey.DeclaringEntityType.FindPrimaryKey().Properties,
                             referencingForeignKey.Properties)
                         == 0))
                    {
                        dependentList.Add(referencingForeignKey.DeclaringEntityType);
                    }
                }

                dependents[entityType] = dependentList;
            }

            return(createElement => new SharedTableEntryMap <TValue>(
                       updateAdapter,
                       principals,
                       dependents,
                       tableName,
                       schema,
                       createElement));
        }
 /// <summary>
 ///     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.
 /// </summary>
 public SharedTableEntryMap(
     [NotNull] IUpdateAdapter updateAdapter,
     [NotNull] IReadOnlyDictionary <IEntityType, IReadOnlyList <IEntityType> > principals,
     [NotNull] IReadOnlyDictionary <IEntityType, IReadOnlyList <IEntityType> > dependents,
     [NotNull] string name,
     [CanBeNull] string schema,
     [NotNull] SharedTableEntryValueFactory <TValue> createElement)
 {
     _updateAdapter = updateAdapter;
     _principals    = principals;
     _dependents    = dependents;
     _name          = name;
     _schema        = schema;
     _createElement = createElement;
     _comparer      = new EntryComparer(principals);
 }
Beispiel #9
0
        CreateSharedTableEntryMapFactories(
            [NotNull] IModel model,
            [NotNull] IUpdateAdapter updateAdapter)
        {
            var sharedTablesMap = new Dictionary <(string, string), SharedTableEntryMapFactory <TValue> >();

            foreach (var table in model.GetRelationalModel().Tables)
            {
                if (!table.IsSplit)
                {
                    continue;
                }

                var factory = CreateSharedTableEntryMapFactory(table, updateAdapter);

                sharedTablesMap.Add((table.Name, table.Schema), factory);
            }

            return(sharedTablesMap);
        }
Beispiel #10
0
        /// <summary>
        ///     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.
        /// </summary>
        public virtual IEnumerable <ModificationCommandBatch> BatchCommands(
            IList <IUpdateEntry> entries,
            IUpdateAdapter updateAdapter)
        {
            var parameterNameGenerator = _parameterNameGeneratorFactory.Create();
            var commands          = CreateModificationCommands(entries, updateAdapter, parameterNameGenerator.GenerateNext);
            var sortedCommandSets = TopologicalSort(commands);

            // TODO: Enable batching of dependent commands by passing through the dependency graph
            foreach (var independentCommandSet in sortedCommandSets)
            {
                independentCommandSet.Sort(_modificationCommandComparer);

                var batch = _modificationCommandBatchFactory.Create();
                foreach (var modificationCommand in independentCommandSet)
                {
                    if (!batch.AddCommand(modificationCommand))
                    {
                        if (batch.ModificationCommands.Count == 1 ||
                            batch.ModificationCommands.Count >= _minBatchSize)
                        {
                            if (batch.ModificationCommands.Count > 1)
                            {
                                Dependencies.UpdateLogger.BatchReadyForExecution(
                                    batch.ModificationCommands.SelectMany(c => c.Entries), batch.ModificationCommands.Count);
                            }

                            yield return(batch);
                        }
                        else
                        {
                            Dependencies.UpdateLogger.BatchSmallerThanMinBatchSize(
                                batch.ModificationCommands.SelectMany(c => c.Entries), batch.ModificationCommands.Count, _minBatchSize);

                            foreach (var command in batch.ModificationCommands)
                            {
                                yield return(StartNewBatch(parameterNameGenerator, command));
                            }
                        }

                        batch = StartNewBatch(parameterNameGenerator, modificationCommand);
                    }
                }

                if (batch.ModificationCommands.Count == 1 ||
                    batch.ModificationCommands.Count >= _minBatchSize)
                {
                    if (batch.ModificationCommands.Count > 1)
                    {
                        Dependencies.UpdateLogger.BatchReadyForExecution(
                            batch.ModificationCommands.SelectMany(c => c.Entries), batch.ModificationCommands.Count);
                    }

                    yield return(batch);
                }
                else
                {
                    Dependencies.UpdateLogger.BatchSmallerThanMinBatchSize(
                        batch.ModificationCommands.SelectMany(c => c.Entries), batch.ModificationCommands.Count, _minBatchSize);

                    foreach (var command in batch.ModificationCommands)
                    {
                        yield return(StartNewBatch(parameterNameGenerator, command));
                    }
                }
            }
        }
Beispiel #11
0
        /// <summary>
        ///     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.
        /// </summary>
        protected virtual IEnumerable <ModificationCommand> CreateModificationCommands(
            [NotNull] IList <IUpdateEntry> entries,
            [NotNull] IUpdateAdapter updateAdapter,
            [NotNull] Func <string> generateParameterName)
        {
            var commands = new List <ModificationCommand>();

            if (_sharedTableEntryMapFactories == null)
            {
                _sharedTableEntryMapFactories = SharedTableEntryMap <ModificationCommand>
                                                .CreateSharedTableEntryMapFactories(updateAdapter.Model, updateAdapter);
            }

            Dictionary <(string Name, string Schema), SharedTableEntryMap <ModificationCommand> > sharedTablesCommandsMap =
                null;

            foreach (var entry in entries)
            {
                if (entry.SharedIdentityEntry != null &&
                    entry.EntityState == EntityState.Deleted)
                {
                    continue;
                }

                var entityType = entry.EntityType;
                var table      = entityType.GetTableName();
                var schema     = entityType.GetSchema();
                var tableKey   = (table, schema);

                ModificationCommand command;
                var isMainEntry = true;
                if (_sharedTableEntryMapFactories.TryGetValue(tableKey, out var commandIdentityMapFactory))
                {
                    if (sharedTablesCommandsMap == null)
                    {
                        sharedTablesCommandsMap =
                            new Dictionary <(string, string), SharedTableEntryMap <ModificationCommand> >();
                    }

                    if (!sharedTablesCommandsMap.TryGetValue(tableKey, out var sharedCommandsMap))
                    {
                        sharedCommandsMap = commandIdentityMapFactory(
                            (n, s, c) => new ModificationCommand(
                                n, s, generateParameterName, _sensitiveLoggingEnabled, c));
                        sharedTablesCommandsMap.Add(tableKey, sharedCommandsMap);
                    }

                    command     = sharedCommandsMap.GetOrAddValue(entry);
                    isMainEntry = sharedCommandsMap.IsMainEntityType(entry.EntityType.GetRootType());
                }
                else
                {
                    command = new ModificationCommand(
                        table, schema, generateParameterName, _sensitiveLoggingEnabled, comparer: null);
                }

                command.AddEntry(entry, isMainEntry);
                commands.Add(command);
            }

            if (sharedTablesCommandsMap != null)
            {
                AddUnchangedSharingEntries(sharedTablesCommandsMap.Values, entries);
            }

            return(commands.Where(
                       c => c.EntityState != EntityState.Modified ||
                       c.ColumnModifications.Any(m => m.IsWrite)));
        }
Beispiel #12
0
        /// <summary>
        ///     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.
        /// </summary>
        public virtual IEnumerable <ModificationCommandBatch> BatchCommands(
            IList <IUpdateEntry> entries,
            IUpdateAdapter updateAdapter)
        {
            var parameterNameGenerator = _parameterNameGeneratorFactory.Create();
            var commands          = CreateModificationCommands(entries, updateAdapter, parameterNameGenerator.GenerateNext);
            var sortedCommandSets = TopologicalSort(commands);

            foreach (var independentCommandSet in sortedCommandSets)
            {
                independentCommandSet.Sort(_modificationCommandComparer);

                var batch = _modificationCommandBatchFactory.Create();
                foreach (var modificationCommand in independentCommandSet)
                {
                    modificationCommand.AssertColumnsNotInitialized();
                    if (modificationCommand.EntityState == EntityState.Modified &&
                        !modificationCommand.ColumnModifications.Any(m => m.IsWrite))
                    {
                        continue;
                    }

                    if (!batch.AddCommand(modificationCommand))
                    {
                        if (batch.ModificationCommands.Count == 1 ||
                            batch.ModificationCommands.Count >= _minBatchSize)
                        {
                            if (batch.ModificationCommands.Count > 1)
                            {
                                Dependencies.UpdateLogger.BatchReadyForExecution(
                                    batch.ModificationCommands.SelectMany(c => c.Entries), batch.ModificationCommands.Count);
                            }

                            yield return(batch);
                        }
                        else
                        {
                            Dependencies.UpdateLogger.BatchSmallerThanMinBatchSize(
                                batch.ModificationCommands.SelectMany(c => c.Entries), batch.ModificationCommands.Count, _minBatchSize);

                            foreach (var command in batch.ModificationCommands)
                            {
                                yield return(StartNewBatch(parameterNameGenerator, command));
                            }
                        }

                        batch = StartNewBatch(parameterNameGenerator, modificationCommand);
                    }
                }

                if (batch.ModificationCommands.Count == 1 ||
                    batch.ModificationCommands.Count >= _minBatchSize)
                {
                    if (batch.ModificationCommands.Count > 1)
                    {
                        Dependencies.UpdateLogger.BatchReadyForExecution(
                            batch.ModificationCommands.SelectMany(c => c.Entries), batch.ModificationCommands.Count);
                    }

                    yield return(batch);
                }
                else
                {
                    Dependencies.UpdateLogger.BatchSmallerThanMinBatchSize(
                        batch.ModificationCommands.SelectMany(c => c.Entries), batch.ModificationCommands.Count, _minBatchSize);

                    foreach (var command in batch.ModificationCommands)
                    {
                        yield return(StartNewBatch(parameterNameGenerator, command));
                    }
                }
            }
        }
Beispiel #13
0
        /// <summary>
        ///     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.
        /// </summary>
        protected virtual IEnumerable <ModificationCommand> CreateModificationCommands(
            IList <IUpdateEntry> entries,
            IUpdateAdapter updateAdapter,
            Func <string> generateParameterName)
        {
            var commands = new List <ModificationCommand>();
            Dictionary <(string Name, string?Schema), SharedTableEntryMap <ModificationCommand> >?sharedTablesCommandsMap =
                null;

            foreach (var entry in entries)
            {
                if (entry.SharedIdentityEntry != null &&
                    entry.EntityState == EntityState.Deleted)
                {
                    continue;
                }

                var mappings     = (IReadOnlyCollection <ITableMapping>)entry.EntityType.GetTableMappings();
                var mappingCount = mappings.Count;
                ModificationCommand?firstCommand = null;
                foreach (var mapping in mappings)
                {
                    var table    = mapping.Table;
                    var tableKey = (table.Name, table.Schema);

                    ModificationCommand command;
                    var isMainEntry = true;
                    if (table.IsShared)
                    {
                        if (sharedTablesCommandsMap == null)
                        {
                            sharedTablesCommandsMap = new Dictionary <(string, string?), SharedTableEntryMap <ModificationCommand> >();
                        }

                        if (!sharedTablesCommandsMap.TryGetValue(tableKey, out var sharedCommandsMap))
                        {
                            sharedCommandsMap = new SharedTableEntryMap <ModificationCommand>(table, updateAdapter);
                            sharedTablesCommandsMap.Add(tableKey, sharedCommandsMap);
                        }

                        command = sharedCommandsMap.GetOrAddValue(
                            entry,
                            (n, s, c) => new ModificationCommand(n, s, generateParameterName, _sensitiveLoggingEnabled, c, Dependencies.UpdateLogger));
                        isMainEntry = sharedCommandsMap.IsMainEntry(entry);
                    }
                    else
                    {
                        command = new ModificationCommand(
                            table.Name, table.Schema, generateParameterName, _sensitiveLoggingEnabled, comparer: null, Dependencies.UpdateLogger);
                    }

                    command.AddEntry(entry, isMainEntry);
                    commands.Add(command);

                    if (firstCommand == null)
                    {
                        Check.DebugAssert(firstCommand == null, "firstCommand == null");
                        firstCommand = command;
                    }
                }

                if (firstCommand == null)
                {
                    throw new InvalidOperationException(RelationalStrings.ReadonlyEntitySaved(entry.EntityType.DisplayName()));
                }
            }

            if (sharedTablesCommandsMap != null)
            {
                AddUnchangedSharingEntries(sharedTablesCommandsMap.Values, entries);
            }

            return(commands);
        }
        /// <summary>
        ///     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.
        /// </summary>
        protected virtual IEnumerable <ModificationCommand> CreateModificationCommands(
            [NotNull] IList <IUpdateEntry> entries,
            [NotNull] IUpdateAdapter modelData,
            [NotNull] Func <string> generateParameterName)
        {
            var commands = new List <ModificationCommand>();

            if (_sharedTableEntryMapFactories == null)
            {
                _sharedTableEntryMapFactories = SharedTableEntryMap <ModificationCommand>
                                                .CreateSharedTableEntryMapFactories(modelData.Model, modelData);
            }

            Dictionary <(string Schema, string Name), SharedTableEntryMap <ModificationCommand> > sharedTablesCommandsMap =
                null;

            foreach (var entry in entries)
            {
                if (entry.SharedIdentityEntry != null &&
                    entry.EntityState == EntityState.Deleted)
                {
                    continue;
                }

                var entityType           = entry.EntityType;
                var relationalExtensions = entityType.Relational();
                var table    = relationalExtensions.TableName;
                var schema   = relationalExtensions.Schema;
                var tableKey = (schema, table);

                ModificationCommand command;
                if (_sharedTableEntryMapFactories.TryGetValue(tableKey, out var commandIdentityMapFactory))
                {
                    if (sharedTablesCommandsMap == null)
                    {
                        sharedTablesCommandsMap =
                            new Dictionary <(string Schema, string Name), SharedTableEntryMap <ModificationCommand> >();
                    }

                    if (!sharedTablesCommandsMap.TryGetValue(tableKey, out var sharedCommandsMap))
                    {
                        sharedCommandsMap = commandIdentityMapFactory(
                            (t, s, c) => new ModificationCommand(
                                t, s, generateParameterName, _sensitiveLoggingEnabled, c));
                        sharedTablesCommandsMap.Add((schema, table), sharedCommandsMap);
                    }

                    command = sharedCommandsMap.GetOrAddValue(entry);
                }
                else
                {
                    command = new ModificationCommand(
                        table, schema, generateParameterName, _sensitiveLoggingEnabled, comparer: null);
                }

                command.AddEntry(entry);
                commands.Add(command);
            }

            if (sharedTablesCommandsMap != null)
            {
                Validate(sharedTablesCommandsMap);
                AddUnchangedSharingEntries(sharedTablesCommandsMap, entries);
            }

            return(commands.Where(
                       c => c.EntityState != EntityState.Modified ||
                       c.ColumnModifications.Any(m => m.IsWrite)));
        }
        /// <summary>
        ///     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.
        /// </summary>
        protected virtual IEnumerable <ModificationCommand> CreateModificationCommands(
            [NotNull] IList <IUpdateEntry> entries,
            [NotNull] IUpdateAdapter updateAdapter,
            [NotNull] Func <string> generateParameterName)
        {
            var commands = new List <ModificationCommand>();

            if (_sharedTableEntryMapFactories == null)
            {
                _sharedTableEntryMapFactories = SharedTableEntryMap <ModificationCommand>
                                                .CreateSharedTableEntryMapFactories(updateAdapter.Model, updateAdapter);
            }

            Dictionary <(string Name, string Schema), SharedTableEntryMap <ModificationCommand> > sharedTablesCommandsMap =
                null;

            foreach (var entry in entries)
            {
                if (entry.SharedIdentityEntry != null &&
                    entry.EntityState == EntityState.Deleted)
                {
                    continue;
                }

                var mappings     = (IReadOnlyCollection <ITableMapping>)entry.EntityType.GetTableMappings();
                var mappingCount = mappings.Count;
                ModificationCommand mainCommand = null;
                var relatedCommands             = mappingCount > 1 ? new List <ModificationCommand>(mappingCount - 1) : null;
                foreach (var mapping in mappings)
                {
                    var table    = mapping.Table;
                    var tableKey = (table.Name, table.Schema);

                    ModificationCommand command;
                    var isMainEntry = true;
                    if (_sharedTableEntryMapFactories.TryGetValue(tableKey, out var commandIdentityMapFactory))
                    {
                        if (sharedTablesCommandsMap == null)
                        {
                            sharedTablesCommandsMap =
                                new Dictionary <(string, string), SharedTableEntryMap <ModificationCommand> >();
                        }

                        if (!sharedTablesCommandsMap.TryGetValue(tableKey, out var sharedCommandsMap))
                        {
                            sharedCommandsMap = commandIdentityMapFactory(
                                (n, s, c) => new ModificationCommand(
                                    n, s, generateParameterName, _sensitiveLoggingEnabled, c));
                            sharedTablesCommandsMap.Add(tableKey, sharedCommandsMap);
                        }

                        command     = sharedCommandsMap.GetOrAddValue(entry);
                        isMainEntry = sharedCommandsMap.IsMainEntry(entry);
                    }
                    else
                    {
                        command = new ModificationCommand(
                            table.Name, table.Schema, generateParameterName, _sensitiveLoggingEnabled, comparer: null);
                    }

                    command.AddEntry(entry, isMainEntry);
                    commands.Add(command);

                    if (mapping.IsMainTableMapping)
                    {
                        Check.DebugAssert(mainCommand == null, "mainCommand == null");
                        mainCommand = command;
                    }
                    else if (relatedCommands != null)
                    {
                        relatedCommands.Add(command);
                    }
                }

                if (mainCommand == null)
                {
                    throw new InvalidOperationException(RelationalStrings.ReadonlyEntitySaved(entry.EntityType.DisplayName()));
                }

                if (relatedCommands != null)
                {
                    foreach (var relatedCommand in relatedCommands)
                    {
                        relatedCommand.Predecessor = mainCommand;
                    }
                }
            }

            if (sharedTablesCommandsMap != null)
            {
                AddUnchangedSharingEntries(sharedTablesCommandsMap.Values, entries);
            }

            return(commands);
        }