Skip to content

Commit

Permalink
Improve SaveChanges circular dependency message
Browse files Browse the repository at this point in the history
Update transient error message
Add sensitive data to the conceptual null exception
Don't validate mapping for shadow properties created by convention
Improve incompatible principal entity during fixup exception
Correct property is already a navigation exception

Fixes #8363
Fixes #8365
Fixes #9696
Fixes #9817
Fixes #10135
Fixes #10856
  • Loading branch information
AndriySvyryd committed Feb 6, 2018
1 parent 29efd84 commit 779d0ef
Show file tree
Hide file tree
Showing 25 changed files with 574 additions and 249 deletions.
2 changes: 1 addition & 1 deletion src/EFCore.InMemory/Storage/Internal/InMemoryTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ protected virtual void ThrowUpdateConcurrencyException([NotNull] IUpdateEntry en
entry.EntityType.DisplayName(),
entry.BuildCurrentValuesString(entry.EntityType.FindPrimaryKey().Properties),
entry.BuildOriginalValuesString(concurrencyConflicts.Keys),
string.Join(", ", concurrencyConflicts.Select(c => c.Key.Name + ":" + c.Value))),
"{" + string.Join(", ", concurrencyConflicts.Select(c => c.Key.Name + ": " + c.Value)) + "}"),
new[] { entry });
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.EntityFrameworkCore.TestModels.Inheritance;
using Xunit;

// ReSharper disable InconsistentNaming
namespace Microsoft.EntityFrameworkCore.Query
{
public abstract class InheritanceRelationalTestBase<TFixture> : InheritanceTestBase<TFixture>
Expand Down
178 changes: 169 additions & 9 deletions src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
Expand Down Expand Up @@ -292,10 +293,171 @@ protected virtual IReadOnlyList<List<ModificationCommand>> TopologicalSort([NotN

AddUniqueValueEdges(modificationCommandGraph);

var sortedCommands
= modificationCommandGraph.BatchingTopologicalSort(data => { return string.Join(", ", data.Select(d => d.Item3.First())); });
return modificationCommandGraph.BatchingTopologicalSort(FormatCycle);
}

private string FormatCycle(IReadOnlyList<Tuple<ModificationCommand, ModificationCommand, IEnumerable<IAnnotatable>>> data)
{
var builder = new StringBuilder();
for (var i = 0; i < data.Count; i++)
{
var edge = data[i];
Format(edge.Item1, builder);

switch (edge.Item3.First())
{
case IForeignKey foreignKey:
Format(foreignKey, edge.Item1, edge.Item2, builder);
break;
case IIndex index:
Format(index, edge.Item1, edge.Item2, builder);
break;
}

if (i == data.Count - 1)
{
Format(edge.Item2, builder);
}
}

return builder.ToString();
}

private void Format(ModificationCommand command, StringBuilder builder)
{
var entry = command.Entries.First();
var entityType = entry.EntityType;
builder.Append(entityType.DisplayName());
if (_sensitiveLoggingEnabled)
{
builder.Append(" { ");
var properties = entityType.FindPrimaryKey().Properties;
for (var i = 0; i < properties.Count; i++)
{
var keyProperty = properties[i];
builder.Append("'");
builder.Append(keyProperty.Name);
builder.Append("': ");
builder.Append(entry.GetCurrentValue(keyProperty));

if (i != properties.Count - 1)
{
builder.Append(", ");
}
}
builder.Append(" } ");
}
else
{
builder.Append(" ");
}

builder.Append("[");
builder.Append(entry.EntityState);
builder.Append("]");
}

private void Format(IForeignKey foreignKey, ModificationCommand source, ModificationCommand target, StringBuilder builder)
{
var reverseDependency = !source.Entries.Any(e => foreignKey.DeclaringEntityType.IsAssignableFrom(e.EntityType));
if (reverseDependency)
{
builder.Append(" <-");
}

builder.Append(" ");
if (foreignKey.DependentToPrincipal != null
|| foreignKey.PrincipalToDependent != null)
{
if (!reverseDependency
&& foreignKey.DependentToPrincipal != null)
{
builder.Append(foreignKey.DependentToPrincipal.Name);
builder.Append(" ");
}

if (foreignKey.PrincipalToDependent != null)
{
builder.Append(foreignKey.PrincipalToDependent.Name);
builder.Append(" ");
}

if (reverseDependency
&& foreignKey.DependentToPrincipal != null)
{
builder.Append(foreignKey.DependentToPrincipal.Name);
builder.Append(" ");
}
}
else
{
builder.Append("ForeignKey ");
}

var dependentCommand = reverseDependency ? target : source;
var dependentEntry = dependentCommand.Entries.First(e => foreignKey.DeclaringEntityType.IsAssignableFrom(e.EntityType));
builder.Append("{ ");
for (var i = 0; i < foreignKey.Properties.Count; i++)
{
var property = foreignKey.Properties[i];
builder.Append("'");
builder.Append(property.Name);
builder.Append("'");
if (_sensitiveLoggingEnabled)
{
builder.Append(": ");
builder.Append(dependentEntry.GetCurrentValue(property));
}

return sortedCommands;
if (i != foreignKey.Properties.Count - 1)
{
builder.Append(", ");
}
}
builder.Append(" } ");

if (!reverseDependency)
{
builder.Append("<- ");
}
}

private void Format(IIndex index, ModificationCommand source, ModificationCommand target, StringBuilder builder)
{
var reverseDependency = source.EntityState != EntityState.Deleted;
if (reverseDependency)
{
builder.Append(" <-");
}

builder.Append(" Index ");

var dependentCommand = reverseDependency ? target : source;
var dependentEntry = dependentCommand.Entries.First(e => index.DeclaringEntityType.IsAssignableFrom(e.EntityType));
builder.Append("{ ");
for (var i = 0; i < index.Properties.Count; i++)
{
var property = index.Properties[i];
builder.Append("'");
builder.Append(property.Name);
builder.Append("'");
if (_sensitiveLoggingEnabled)
{
builder.Append(": ");
builder.Append(dependentEntry.GetCurrentValue(property));
}

if (i != index.Properties.Count - 1)
{
builder.Append(", ");
}
}
builder.Append(" } ");

if (!reverseDependency)
{
builder.Append("<- ");
}
}

// Builds a map from foreign key values to list of modification commands, with an entry for every command
Expand Down Expand Up @@ -452,9 +614,8 @@ private void AddUniqueValueEdges(Multigraph<ModificationCommand, IAnnotatable> c
{
var indexColumnModifications =
command.ColumnModifications.Where(
cm =>
index.Properties.Contains(cm.Property)
&& (cm.IsWrite || cm.IsRead));
cm => index.Properties.Contains(cm.Property)
&& (cm.IsWrite || cm.IsRead));

if (command.EntityState == EntityState.Deleted
|| indexColumnModifications.Any())
Expand Down Expand Up @@ -492,9 +653,8 @@ private void AddUniqueValueEdges(Multigraph<ModificationCommand, IAnnotatable> c
{
var indexColumnModifications =
command.ColumnModifications.Where(
cm =>
index.Properties.Contains(cm.Property)
&& cm.IsWrite);
cm => index.Properties.Contains(cm.Property)
&& cm.IsWrite);

if (command.EntityState == EntityState.Added
|| indexColumnModifications.Any())
Expand Down
Loading

0 comments on commit 779d0ef

Please sign in to comment.