Skip to content

Commit

Permalink
Update state manager debug view based on design meeting feedback
Browse files Browse the repository at this point in the history
Issue #19109

* Moved to StateManager
* Implemented as ToDebugString on InternalEntityEntry
* Used for ToString on InternalEntityEntry
* Also for ToString on external EntityEntry
* Also added DebugView on InternalEntityEntry to access long form for single entry
  • Loading branch information
ajcvickers committed Dec 12, 2019
1 parent 82b0019 commit d3214e2
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 191 deletions.
13 changes: 0 additions & 13 deletions src/EFCore/ChangeTracking/ChangeTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Utilities;

Expand Down Expand Up @@ -397,18 +396,6 @@ Task IResettableService.ResetStateAsync(CancellationToken cancellationToken)
return default;
}

/// <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>
[EntityFrameworkInternal]
public virtual DebugView DebugView
=> new DebugView(
() => StateManager.ToDebugString(StateManagerDebugStringOptions.ShortDefault),
() => StateManager.ToDebugString(StateManagerDebugStringOptions.LongDefault));

#region Hidden System.Object members

/// <summary>
Expand Down
7 changes: 3 additions & 4 deletions src/EFCore/ChangeTracking/EntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -403,14 +403,13 @@ private void Reload(PropertyValues storeValues)
private IEntityFinder Finder
=> InternalEntry.StateManager.CreateEntityFinder(InternalEntry.EntityType);

#region Hidden System.Object members

/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns> A string that represents the current object. </returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string ToString() => base.ToString();
public override string ToString() => InternalEntry.ToString();

#region Hidden System.Object members

/// <summary>
/// Determines whether the specified object is equal to the current object.
Expand Down
196 changes: 194 additions & 2 deletions src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
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;
Expand Down Expand Up @@ -1673,8 +1676,197 @@ public virtual bool IsLoaded([NotNull] INavigation navigation)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public override string ToString()
=> $"{this.BuildCurrentValuesString(EntityType.FindPrimaryKey().Properties)} {EntityState}"
+ $"{(((IUpdateEntry)this).SharedIdentityEntry == null ? "" : " Shared")} {EntityType}";
=> ToDebugString(StateManagerDebugStringOptions.ShortDefault);

/// <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 DebugView DebugView
=> new DebugView(
() => this.ToDebugString(StateManagerDebugStringOptions.ShortDefault),
() => this.ToDebugString(StateManagerDebugStringOptions.LongDefault));

/// <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 string ToDebugString(StateManagerDebugStringOptions options)
{
var builder = new StringBuilder();
var keyString = this.BuildCurrentValuesString(EntityType.FindPrimaryKey().Properties);

builder
.Append(EntityType.DisplayName())
.Append(' ')
.Append(SharedIdentityEntry == null ? "(Shared) " : "")
.Append(keyString)
.Append(' ')
.Append(EntityState.ToString());

if ((options & StateManagerDebugStringOptions.IncludeProperties) != 0)
{
foreach (var property in EntityType.GetProperties())
{
builder.AppendLine();

var currentValue = GetCurrentValue(property);
builder
.Append(" ")
.Append(property.Name)
.Append(": ");

AppendValue(currentValue);

if (property.IsPrimaryKey())
{
builder.Append(" PK");
}
else if (property.IsKey())
{
builder.Append(" AK");
}

if (property.IsForeignKey())
{
builder.Append(" FK");
}

if (IsModified(property))
{
builder.Append(" Modified");
}

if (HasTemporaryValue(property))
{
builder.Append(" Temporary");
}

if (HasOriginalValuesSnapshot
&& property.GetOriginalValueIndex() != -1)
{
var originalValue = GetOriginalValue(property);
if (!Equals(originalValue, currentValue))
{
builder.Append(" Originally ");
AppendValue(originalValue);
}
}
}
}
else
{
foreach (var alternateKey in EntityType.GetKeys().Where(k => !k.IsPrimaryKey()))
{
builder
.Append(" AK ")
.Append(this.BuildCurrentValuesString(alternateKey.Properties));
}

foreach (var foreignKey in EntityType.GetForeignKeys())
{
builder
.Append(" FK ")
.Append(this.BuildCurrentValuesString(foreignKey.Properties));
}
}

if ((options & StateManagerDebugStringOptions.IncludeNavigations) != 0)
{
foreach (var navigation in EntityType.GetNavigations())
{
builder.AppendLine();

var currentValue = GetCurrentValue(navigation);
var targetType = navigation.GetTargetType();

builder
.Append(" ")
.Append(navigation.Name)
.Append(": ");

if (currentValue == null)
{
builder.Append("<null>");
}
else if (navigation.IsCollection())
{
builder.Append('[');

const int maxRelatedToShow = 32;
var relatedEntities = ((IEnumerable)currentValue).Cast<object>().Take(maxRelatedToShow + 1).ToList();

for (var i = 0; i < relatedEntities.Count; i++)
{
if (i != 0)
{
builder.Append(", ");
}

if (i < 32)
{
AppendRelatedKey(targetType, relatedEntities[i]);
}
else
{
builder.Append("...");
}
}

builder.Append(']');
}
else
{
AppendRelatedKey(targetType, currentValue);
}
}
}

return builder.ToString();

void AppendValue(object value)
{
if (value == null)
{
builder.Append("<null>");
}
else if (value.GetType().IsNumeric())
{
builder.Append(value);
}
else if (value is byte[] bytes)
{
builder.AppendBytes(bytes);
}
else
{
var stringValue = value.ToString();
if (stringValue.Length > 63)
{
stringValue = stringValue.Substring(0, 60) + "...";
}

builder
.Append('\'')
.Append(stringValue)
.Append('\'');
}
}

void AppendRelatedKey(IEntityType targetType, object value)
{
var otherEntry = StateManager.TryGetEntry(value, targetType, throwOnTypeMismatch: false);

builder.Append(
otherEntry == null
? "<not found>"
: otherEntry.BuildCurrentValuesString(targetType.FindPrimaryKey().Properties));
}
}

IUpdateEntry IUpdateEntry.SharedIdentityEntry => SharedIdentityEntry;

Expand Down
11 changes: 11 additions & 0 deletions src/EFCore/ChangeTracking/Internal/StateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1254,5 +1254,16 @@ public virtual void OnStateChanged(InternalEntityEntry internalEntityEntry, Enti

@event?.Invoke(Context.ChangeTracker, new EntityStateChangedEventArgs(internalEntityEntry, oldState, newState));
}

/// <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 DebugView DebugView
=> new DebugView(
() => this.ToDebugString(StateManagerDebugStringOptions.ShortDefault),
() => this.ToDebugString(StateManagerDebugStringOptions.LongDefault));
}
}
Loading

0 comments on commit d3214e2

Please sign in to comment.