Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2036,9 +2036,12 @@ protected virtual IEnumerable<MigrationOperation> GetDataOperations([NotNull] Di
private object GetValue(ColumnModification columnModification)
{
var converter = GetValueConverter(columnModification.Property);
var value = columnModification.UseCurrentValueParameter
? columnModification.Value
: columnModification.OriginalValue;
return converter != null
? converter.ConvertToProvider(columnModification.Value)
: columnModification.Value;
? converter.ConvertToProvider(value)
: value;
}

#endregion
Expand Down
12 changes: 7 additions & 5 deletions src/EFCore.Relational/Update/ColumnModification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,24 +149,24 @@ public ColumnModification(
/// <summary>
/// Indicates whether the original value of the property must be passed as a parameter to the SQL
/// </summary>
public virtual bool UseOriginalValueParameter => _useParameters && IsCondition && IsConcurrencyToken;
public virtual bool UseOriginalValueParameter => _useParameters && IsCondition;

/// <summary>
/// Indicates whether the current value of the property must be passed as a parameter to the SQL
/// </summary>
public virtual bool UseCurrentValueParameter => _useParameters && (IsWrite || IsCondition && !IsConcurrencyToken);
public virtual bool UseCurrentValueParameter => _useParameters && IsWrite;

/// <summary>
/// The parameter name to use for the current value parameter (<see cref="UseCurrentValueParameter" />), if needed.
/// </summary>
public virtual string ParameterName
=> _parameterName ?? (_parameterName = _generateParameterName());
=> _parameterName ?? (_parameterName = UseCurrentValueParameter ? _generateParameterName() : null);

/// <summary>
/// The parameter name to use for the original value parameter (<see cref="UseOriginalValueParameter" />), if needed.
/// </summary>
public virtual string OriginalParameterName
=> _originalParameterName ?? (_originalParameterName = _generateParameterName());
=> _originalParameterName ?? (_originalParameterName = UseOriginalValueParameter ? _generateParameterName() : null);

/// <summary>
/// The name of the column.
Expand All @@ -187,7 +187,9 @@ public virtual string OriginalParameterName
/// </summary>
public virtual object Value
{
get => Entry == null ? _value : Entry.GetCurrentValue(Property);
get => Entry == null
? _value
: Entry.EntityState == EntityState.Deleted ? null : Entry.GetCurrentValue(Property);
[param: CanBeNull]
set
{
Expand Down
80 changes: 8 additions & 72 deletions src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ protected virtual IEnumerable<ModificationCommand> CreateModificationCommands(
var tableKey = (schema, table);

ModificationCommand command;
var isMainEntry = true;
if (_sharedTableEntryMapFactories.TryGetValue(tableKey, out var commandIdentityMapFactory))
{
if (sharedTablesCommandsMap == null)
Expand All @@ -201,20 +202,20 @@ protected virtual IEnumerable<ModificationCommand> CreateModificationCommands(
}

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

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

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

Expand All @@ -223,86 +224,20 @@ protected virtual IEnumerable<ModificationCommand> CreateModificationCommands(
|| c.ColumnModifications.Any(m => m.IsWrite));
}

private void Validate(
Dictionary<(string Schema, string Name),
SharedTableEntryMap<ModificationCommand>> sharedTablesCommandsMap)
{
foreach (var modificationCommandIdentityMap in sharedTablesCommandsMap.Values)
{
foreach (var command in modificationCommandIdentityMap.Values)
{
if (command.EntityState != EntityState.Added
&& command.EntityState != EntityState.Deleted)
{
continue;
}

// ReSharper disable once ForCanBeConvertedToForeach
for (var entryIndex = 0; entryIndex < command.Entries.Count; entryIndex++)
{
var entry = command.Entries[entryIndex];
var principals = modificationCommandIdentityMap.GetPrincipals(entry.EntityType);
// ReSharper disable once ForCanBeConvertedToForeach
for (var principalIndex = 0; principalIndex < principals.Count; principalIndex++)
{
var principalEntityType = principals[principalIndex];
var principalFound = false;
// ReSharper disable once ForCanBeConvertedToForeach
for (var otherEntryIndex = 0; otherEntryIndex < command.Entries.Count; otherEntryIndex++)
{
var principalEntry = command.Entries[otherEntryIndex];
if (principalEntry != entry
&& principalEntityType.IsAssignableFrom(principalEntry.EntityType))
{
principalFound = true;
break;
}
}

if (principalFound)
{
continue;
}

var tableName = (string.IsNullOrEmpty(command.Schema) ? "" : command.Schema + ".") +
command.TableName;
if (_sensitiveLoggingEnabled)
{
throw new InvalidOperationException(
RelationalStrings.SharedRowEntryCountMismatchSensitive(
entry.EntityType.DisplayName(),
tableName,
principalEntityType.DisplayName(),
entry.BuildCurrentValuesString(entry.EntityType.FindPrimaryKey().Properties),
command.EntityState));
}

throw new InvalidOperationException(
RelationalStrings.SharedRowEntryCountMismatch(
entry.EntityType.DisplayName(),
tableName,
principalEntityType.DisplayName(),
command.EntityState));
}
}
}
}
}

private void AddUnchangedSharingEntries(
Dictionary<(string Schema, string Name), SharedTableEntryMap<ModificationCommand>> sharedTablesCommandsMap,
IList<IUpdateEntry> entries)
{
foreach (var modificationCommandIdentityMap in sharedTablesCommandsMap.Values)
foreach (var sharedCommandsMap in sharedTablesCommandsMap.Values)
{
foreach (var command in modificationCommandIdentityMap.Values)
foreach (var command in sharedCommandsMap.Values)
{
if (command.EntityState != EntityState.Modified)
{
continue;
}

foreach (var entry in modificationCommandIdentityMap.GetAllEntries(command.Entries[0]))
foreach (var entry in sharedCommandsMap.GetAllEntries(command.Entries[0]))
{
if (entry.EntityState != EntityState.Unchanged)
{
Expand All @@ -311,7 +246,8 @@ private void AddUnchangedSharingEntries(

entry.EntityState = EntityState.Modified;

command.AddEntry(entry);
var isMainEntry = sharedCommandsMap.GetPrincipals(entry.EntityType.GetRootType()).Count == 0;
command.AddEntry(entry, isMainEntry);
entries.Add(entry);
}
}
Expand Down
13 changes: 5 additions & 8 deletions src/EFCore.Relational/Update/Internal/SharedTableEntryMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,14 +257,11 @@ public EntryComparer(IReadOnlyDictionary<IEntityType, IReadOnlyList<IEntityType>
}

public int Compare(IUpdateEntry x, IUpdateEntry y)
{
if (_principals[x.EntityType].Count == 0)
{
return -1;
}

return _principals[y.EntityType].Count == 0 ? 1 : StringComparer.Ordinal.Compare(x.EntityType.Name, y.EntityType.Name);
}
=> _principals[x.EntityType].Count == 0
? -1
: _principals[y.EntityType].Count == 0
? 1
: StringComparer.Ordinal.Compare(x.EntityType.Name, y.EntityType.Name);
}
}
}
126 changes: 84 additions & 42 deletions src/EFCore.Relational/Update/ModificationCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class ModificationCommand
private readonly List<IUpdateEntry> _entries = new List<IUpdateEntry>();
private IReadOnlyList<ColumnModification> _columnModifications;
private bool _requiresResultPropagation;
private bool _mainEntryAdded;

/// <summary>
/// Initializes a new <see cref="ModificationCommand" /> instance.
Expand Down Expand Up @@ -104,22 +105,10 @@ public virtual EntityState EntityState
{
get
{
if (_entries.Count > 0)
if (_mainEntryAdded)
{
for (var i = 0; i < _entries.Count; i++)
{
var entry = _entries[0];
if (entry.SharedIdentityEntry != null)
{
return EntityState.Modified;
}

var state = entry.EntityState;
if (state != EntityState.Unchanged)
{
return state;
}
}
var entry = _entries[0];
return entry.SharedIdentityEntry != null ? EntityState.Modified : entry.EntityState;
}

return EntityState.Modified;
Expand Down Expand Up @@ -152,7 +141,16 @@ public virtual bool RequiresResultPropagation
/// Adds an <see cref="IUpdateEntry" /> to this command representing an entity to be inserted, updated, or deleted.
/// </summary>
/// <param name="entry"> The entry representing the entity to add. </param>
[Obsolete("Use AddEntry with most parameters")]
public virtual void AddEntry([NotNull] IUpdateEntry entry)
=> AddEntry(entry, mainEntry: false);

/// <summary>
/// Adds an <see cref="IUpdateEntry" /> to this command representing an entity to be inserted, updated, or deleted.
/// </summary>
/// <param name="entry"> The entry representing the entity to add. </param>
/// <param name="mainEntry"> A value indicating whether this is the main entry for the row. </param>
public virtual void AddEntry([NotNull] IUpdateEntry entry, bool mainEntry)
{
Check.NotNull(entry, nameof(entry));

Expand All @@ -166,38 +164,65 @@ public virtual void AddEntry([NotNull] IUpdateEntry entry)
throw new ArgumentException(RelationalStrings.ModificationCommandInvalidEntityState(entry.EntityState));
}

if (_entries.Count > 0)
if (mainEntry)
{
var currentState = EntityState;
var entryState = entry.SharedIdentityEntry == null
? entry.EntityState
: EntityState.Modified;
if (currentState != entryState
&& entryState != EntityState.Unchanged)
Check.DebugAssert(!_mainEntryAdded, "Only expected a single main entry");

for (var i = 0; i < _entries.Count; i++)
{
if (_sensitiveLoggingEnabled)
{
throw new InvalidOperationException(
RelationalStrings.ConflictingRowUpdateTypesSensitive(
entry.EntityType.DisplayName(),
entry.BuildCurrentValuesString(entry.EntityType.FindPrimaryKey().Properties),
entryState,
_entries[0].EntityType.DisplayName(),
_entries[0].BuildCurrentValuesString(_entries[0].EntityType.FindPrimaryKey().Properties),
currentState));
}
ValidateState(entry, _entries[i]);
}

_mainEntryAdded = true;
_entries.Insert(0, entry);
}
else
{
if (_mainEntryAdded)
{
ValidateState(_entries[0], entry);
}

_entries.Add(entry);
}

_columnModifications = null;
}

private void ValidateState(IUpdateEntry mainEntry, IUpdateEntry entry)
{
var mainEntryState = mainEntry.SharedIdentityEntry == null
? mainEntry.EntityState
: EntityState.Modified;
if (mainEntryState == EntityState.Modified)
{
return;
}

var entryState = entry.SharedIdentityEntry == null
? entry.EntityState
: EntityState.Modified;
if (mainEntryState != entryState)
{
if (_sensitiveLoggingEnabled)
{
throw new InvalidOperationException(
RelationalStrings.ConflictingRowUpdateTypes(
RelationalStrings.ConflictingRowUpdateTypesSensitive(
entry.EntityType.DisplayName(),
entry.BuildCurrentValuesString(entry.EntityType.FindPrimaryKey().Properties),
entryState,
_entries[0].EntityType.DisplayName(),
currentState));
mainEntry.EntityType.DisplayName(),
mainEntry.BuildCurrentValuesString(mainEntry.EntityType.FindPrimaryKey().Properties),
mainEntryState));
}
}

_entries.Add(entry);
_columnModifications = null;
throw new InvalidOperationException(
RelationalStrings.ConflictingRowUpdateTypes(
entry.EntityType.DisplayName(),
entryState,
mainEntry.EntityType.DisplayName(),
mainEntryState));
}
}

private IReadOnlyList<ColumnModification> GenerateColumnModifications()
Expand Down Expand Up @@ -231,6 +256,10 @@ private IReadOnlyList<ColumnModification> GenerateColumnModifications()

foreach (var entry in _entries)
{
var nonMainEntry = updating
&& (entry.EntityState == EntityState.Deleted
|| entry.EntityState == EntityState.Added);

foreach (var property in entry.EntityType.GetProperties())
{
var isKey = property.IsPrimaryKey();
Expand All @@ -247,7 +276,8 @@ private IReadOnlyList<ColumnModification> GenerateColumnModifications()
{
writeValue = property.GetBeforeSaveBehavior() == PropertySaveBehavior.Save;
}
else if (updating && property.GetAfterSaveBehavior() == PropertySaveBehavior.Save)
else if ((updating && property.GetAfterSaveBehavior() == PropertySaveBehavior.Save)
|| (!isKey && nonMainEntry))
{
writeValue = columnPropagator?.TryPropagate(property, (InternalEntityEntry)entry)
?? entry.IsModified(property);
Expand Down Expand Up @@ -353,15 +383,27 @@ public void RecordValue(IProperty property, IUpdateEntry entry)

break;
case EntityState.Added:
if (!_write)
_currentValue = entry.GetCurrentValue(property);

var comparer = property.GetValueComparer() ?? property.FindTypeMapping()?.Comparer;
if (comparer == null)
{
_currentValue = entry.GetCurrentValue(property);
_write = !Equals(_originalValue, _currentValue);
}
else
{
_write = !comparer.Equals(_originalValue, _currentValue);
}

break;
case EntityState.Deleted:
_originalValue = entry.GetOriginalValue(property);
if (!_write
&& !property.IsPrimaryKey())
{
_write = true;
_currentValue = null;
}
break;
}
}
Expand Down
Loading