private IReadOnlyList <ColumnModification> GenerateColumnModifications() { var adding = EntityState == EntityState.Added; var columnModifications = new List <ColumnModification>(); if (_comparer != null) { _entries.Sort(_comparer); } var columnMap = _entries.Count == 1 ? null : new Dictionary <string, ColumnModification>(); Dictionary <IUpdateEntry, List <ColumnModification> > conflictingColumnValues = null; Dictionary <IUpdateEntry, List <ColumnModification> > conflictingOriginalColumnValues = null; foreach (var entry in _entries) { var entityType = entry.EntityType; foreach (var property in entityType.GetProperties()) { var isKey = property.IsPrimaryKey(); var isConcurrencyToken = property.IsConcurrencyToken; var isCondition = !adding && (isKey || isConcurrencyToken); var readValue = entry.IsStoreGenerated(property); var writeValue = !readValue && (adding && property.BeforeSaveBehavior == PropertySaveBehavior.Save || !adding && property.AfterSaveBehavior == PropertySaveBehavior.Save && entry.IsModified(property)); if (readValue || writeValue || isCondition) { if (readValue) { _requiresResultPropagation = true; } var columnModification = new ColumnModification( entry, property, property.Relational(), _generateParameterName, readValue, writeValue, isKey, isCondition, isConcurrencyToken); if (columnMap != null) { if (columnMap.TryGetValue(columnModification.ColumnName, out var existingColumn)) { if (columnModification.UseCurrentValueParameter && !Equals(columnModification.Value, existingColumn.Value)) { conflictingColumnValues = AddConflictingColumnValues( conflictingColumnValues, columnModification, existingColumn); } else if (columnModification.UseOriginalValueParameter && !Equals(columnModification.OriginalValue, existingColumn.OriginalValue)) { conflictingOriginalColumnValues = AddConflictingColumnValues( conflictingOriginalColumnValues, columnModification, existingColumn); } continue; } columnMap.Add(columnModification.ColumnName, columnModification); } columnModifications.Add(columnModification); } } } if (conflictingColumnValues != null) { var firstPair = conflictingColumnValues.First(); var firstEntry = firstPair.Key; var firstProperties = firstPair.Value.Select(c => c.Property).ToList(); var lastPair = conflictingColumnValues.Last(); var lastEntry = lastPair.Key; var lastProperties = lastPair.Value.Select(c => c.Property); if (_sensitiveLoggingEnabled) { throw new InvalidOperationException( RelationalStrings.ConflictingRowValuesSensitive( firstEntry.EntityType.DisplayName(), lastEntry.EntityType.DisplayName(), firstEntry.BuildCurrentValuesString(firstEntry.EntityType.FindPrimaryKey().Properties), firstEntry.BuildCurrentValuesString(firstProperties), lastEntry.BuildCurrentValuesString(lastProperties), firstProperties.FormatColumns())); } throw new InvalidOperationException( RelationalStrings.ConflictingRowValues( firstEntry.EntityType.DisplayName(), lastEntry.EntityType.DisplayName(), Property.Format(firstProperties), Property.Format(lastProperties), firstProperties.FormatColumns())); } if (conflictingOriginalColumnValues != null) { var firstPair = conflictingOriginalColumnValues.First(); var firstEntry = firstPair.Key; var firstProperties = firstPair.Value.Select(c => c.Property).ToList(); var lastPair = conflictingOriginalColumnValues.Last(); var lastEntry = lastPair.Key; var lastProperties = lastPair.Value.Select(c => c.Property); if (_sensitiveLoggingEnabled) { throw new InvalidOperationException( RelationalStrings.ConflictingOriginalRowValuesSensitive( firstEntry.EntityType.DisplayName(), lastEntry.EntityType.DisplayName(), firstEntry.BuildCurrentValuesString(firstEntry.EntityType.FindPrimaryKey().Properties), firstEntry.BuildOriginalValuesString(firstProperties), lastEntry.BuildOriginalValuesString(lastProperties), firstProperties.FormatColumns())); } throw new InvalidOperationException( RelationalStrings.ConflictingOriginalRowValues( firstEntry.EntityType.DisplayName(), lastEntry.EntityType.DisplayName(), Property.Format(firstProperties), Property.Format(lastProperties), firstProperties.FormatColumns())); } return(columnModifications); }
public void BatchCommands_throws_on_conflicting_values_for_shared_table_added_entities(bool useCurrentValues, bool sensitiveLogging) { var currentDbContext = CreateContextServices(CreateSharedTableModel()).GetRequiredService <ICurrentDbContext>(); var stateManager = currentDbContext.GetDependencies().StateManager; var first = new FakeEntity { Id = 42, Value = "Test" }; var firstEntry = stateManager.GetOrCreateEntry(first); firstEntry.SetEntityState(EntityState.Modified); firstEntry.SetPropertyModified(firstEntry.EntityType.FindPrimaryKey().Properties.Single(), isModified: false); var second = new RelatedFakeEntity { Id = 42 }; var secondEntry = stateManager.GetOrCreateEntry(second); secondEntry.SetEntityState(EntityState.Modified); secondEntry.SetPropertyModified(secondEntry.EntityType.FindPrimaryKey().Properties.Single(), isModified: false); if (useCurrentValues) { first.RelatedId = 1; second.RelatedId = 2; } else { new EntityEntry <FakeEntity>(firstEntry).Property(e => e.RelatedId).OriginalValue = 1; new EntityEntry <RelatedFakeEntity>(secondEntry).Property(e => e.RelatedId).OriginalValue = 2; } if (useCurrentValues) { if (sensitiveLogging) { Assert.Equal( RelationalStrings.ConflictingRowValuesSensitive( nameof(RelatedFakeEntity), nameof(FakeEntity), "{Id: 42}", "{RelatedId: 2}", "{RelatedId: 1}", "{'RelatedId'}"), Assert.Throws <InvalidOperationException>( () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: true) .BatchCommands(new[] { firstEntry, secondEntry }).ToArray()).Message); } else { Assert.Equal( RelationalStrings.ConflictingRowValues( nameof(RelatedFakeEntity), nameof(FakeEntity), "{'RelatedId'}", "{'RelatedId'}", "{'RelatedId'}"), Assert.Throws <InvalidOperationException>( () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: false) .BatchCommands(new[] { firstEntry, secondEntry }).ToArray()).Message); } } else { if (sensitiveLogging) { Assert.Equal( RelationalStrings.ConflictingOriginalRowValuesSensitive( nameof(RelatedFakeEntity), nameof(FakeEntity), "{Id: 42}", "{RelatedId: 2}", "{RelatedId: 1}", "{'RelatedId'}"), Assert.Throws <InvalidOperationException>( () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: true) .BatchCommands(new[] { firstEntry, secondEntry }).ToArray()).Message); } else { Assert.Equal( RelationalStrings.ConflictingOriginalRowValues( nameof(RelatedFakeEntity), nameof(FakeEntity), "{'RelatedId'}", "{'RelatedId'}", "{'RelatedId'}"), Assert.Throws <InvalidOperationException>( () => CreateCommandBatchPreparer(stateManager: stateManager, sensitiveLogging: false) .BatchCommands(new[] { firstEntry, secondEntry }).ToArray()).Message); } } }