Skip to content

Commit

Permalink
Force use of common value generator for all derived types
Browse files Browse the repository at this point in the history
Fixes #19854

Because in-memory isn't "TPH" and so derived types have their own "table" but the value generator is stored on the "table" that the property is defined in.
  • Loading branch information
ajcvickers committed Mar 19, 2020
1 parent 20481dd commit 2972c62
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 80 deletions.
35 changes: 34 additions & 1 deletion src/EFCore.InMemory/Storage/Internal/IInMemoryTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ public interface IInMemoryTable
/// </summary>
IReadOnlyList<object[]> SnapshotRows();

/// <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>
IEnumerable<object[]> Rows { get; }

/// <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
Expand Down Expand Up @@ -55,6 +63,31 @@ public interface IInMemoryTable
/// 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>
InMemoryIntegerValueGenerator<TProperty> GetIntegerValueGenerator<TProperty>([NotNull] IProperty property);
InMemoryIntegerValueGenerator<TProperty> GetIntegerValueGenerator<TProperty>(
[NotNull] IProperty property, [NotNull] IReadOnlyList<IInMemoryTable> tables);

/// <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>
void BumpValueGenerators([NotNull] object[] row);

/// <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>
IInMemoryTable BaseTable { get; }

/// <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>
IEntityType EntityType { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ public interface IInMemoryTableFactory
/// 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>
IInMemoryTable Create([NotNull] IEntityType entityType);
IInMemoryTable Create([NotNull] IEntityType entityType, [CanBeNull] IInMemoryTable baseTable);
}
}
26 changes: 18 additions & 8 deletions src/EFCore.InMemory/Storage/Internal/InMemoryStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ public virtual InMemoryIntegerValueGenerator<TProperty> GetIntegerValueGenerator
lock (_lock)
{
var entityType = property.DeclaringEntityType;
var key = _useNameMatching ? (object)entityType.Name : entityType;

return EnsureTable(key, entityType).GetIntegerValueGenerator<TProperty>(property);
return EnsureTable(entityType).GetIntegerValueGenerator<TProperty>(
property,
entityType.GetDerivedTypesInclusive().Select(type => EnsureTable(type)).ToArray());
}
}

Expand Down Expand Up @@ -170,8 +171,7 @@ public virtual int ExecuteTransaction(

Check.DebugAssert(!entityType.IsAbstract(), "entityType is abstract");

var key = _useNameMatching ? (object)entityType.Name : entityType;
var table = EnsureTable(key, entityType);
var table = EnsureTable(entityType);

if (entry.SharedIdentityEntry != null)
{
Expand Down Expand Up @@ -206,19 +206,29 @@ public virtual int ExecuteTransaction(
}

// Must be called from inside the lock
private IInMemoryTable EnsureTable(object key, IEntityType entityType)
private IInMemoryTable EnsureTable(IEntityType entityType)
{
if (_tables == null)
{
_tables = CreateTables();
}

if (!_tables.TryGetValue(key, out var table))
IInMemoryTable baseTable = null;

var entityTypes = entityType.GetAllBaseTypesInclusive();
foreach (var currentEntityType in entityTypes)
{
_tables.Add(key, table = _tableFactory.Create(entityType));
var key = _useNameMatching ? (object)currentEntityType.Name : currentEntityType;
if (!_tables.TryGetValue(key, out var table))
{
_tables.Add(key, table = _tableFactory.Create(currentEntityType, baseTable));
}

baseTable = table;
}

return table;

return _tables[_useNameMatching ? (object)entityType.Name : entityType];
}
}
}
49 changes: 41 additions & 8 deletions src/EFCore.InMemory/Storage/Internal/InMemoryTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal
public class InMemoryTable<TKey> : IInMemoryTable
{
// WARNING: The in-memory provider is using EF internal code here. This should not be copied by other providers. See #15096
private readonly IEntityType _entityType;
private readonly IPrincipalKeyValueFactory<TKey> _keyValueFactory;
private readonly bool _sensitiveLoggingEnabled;
private readonly Dictionary<TKey, object[]> _rows;
Expand All @@ -43,9 +42,10 @@ public class InMemoryTable<TKey> : IInMemoryTable
/// 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 InMemoryTable([NotNull] IEntityType entityType, bool sensitiveLoggingEnabled)
public InMemoryTable([NotNull] IEntityType entityType, [CanBeNull] IInMemoryTable baseTable, bool sensitiveLoggingEnabled)
{
_entityType = entityType;
EntityType = entityType;
BaseTable = baseTable;
// WARNING: The in-memory provider is using EF internal code here. This should not be copied by other providers. See #15096
_keyValueFactory = entityType.FindPrimaryKey().GetPrincipalKeyValueFactory<TKey>();
_sensitiveLoggingEnabled = sensitiveLoggingEnabled;
Expand Down Expand Up @@ -83,7 +83,24 @@ public InMemoryTable([NotNull] IEntityType entityType, bool sensitiveLoggingEnab
/// 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 InMemoryIntegerValueGenerator<TProperty> GetIntegerValueGenerator<TProperty>(IProperty property)
public virtual IInMemoryTable BaseTable { get; }

/// <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 IEntityType EntityType { get; }

/// <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 InMemoryIntegerValueGenerator<TProperty> GetIntegerValueGenerator<TProperty>(
IProperty property, IReadOnlyList<IInMemoryTable> tables)
{
if (_integerGenerators == null)
{
Expand All @@ -97,15 +114,26 @@ public virtual InMemoryIntegerValueGenerator<TProperty> GetIntegerValueGenerator
generator = new InMemoryIntegerValueGenerator<TProperty>(propertyIndex);
_integerGenerators[propertyIndex] = generator;

foreach (var row in _rows.Values)
foreach (var table in tables)
{
generator.Bump(row);
foreach (var row in table.Rows)
{
generator.Bump(row);
}
}
}

return (InMemoryIntegerValueGenerator<TProperty>)generator;
}

/// <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<object[]> Rows => _rows.Values;

/// <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
Expand All @@ -116,7 +144,7 @@ public virtual IReadOnlyList<object[]> SnapshotRows()
{
var rows = _rows.Values.ToList();
var rowCount = rows.Count;
var properties = _entityType.GetProperties().ToList();
var properties = EntityType.GetProperties().ToList();
var propertyCount = properties.Count;

for (var rowIndex = 0; rowIndex < rowCount; rowIndex++)
Expand Down Expand Up @@ -266,8 +294,13 @@ public virtual void Update(IUpdateEntry entry)
}
}

private void BumpValueGenerators(object[] row)
public virtual void BumpValueGenerators(object[] row)
{
if (BaseTable != null)
{
BaseTable.BumpValueGenerators(row);
}

if (_integerGenerators != null)
{
foreach (var generator in _integerGenerators.Values)
Expand Down
17 changes: 9 additions & 8 deletions src/EFCore.InMemory/Storage/Internal/InMemoryTableFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public class InMemoryTableFactory
{
private readonly bool _sensitiveLoggingEnabled;

private readonly ConcurrentDictionary<IEntityType, Func<IInMemoryTable>> _factories
= new ConcurrentDictionary<IEntityType, Func<IInMemoryTable>>();
private readonly ConcurrentDictionary<(IEntityType EntityType, IInMemoryTable BaseTable), Func<IInMemoryTable>> _factories
= new ConcurrentDictionary<(IEntityType, IInMemoryTable), Func<IInMemoryTable>>();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -46,17 +46,18 @@ public InMemoryTableFactory([NotNull] ILoggingOptions loggingOptions)
/// 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 IInMemoryTable Create(IEntityType entityType)
=> _factories.GetOrAdd(entityType, CreateTable)();
public virtual IInMemoryTable Create(IEntityType entityType, IInMemoryTable baseTable)
=> _factories.GetOrAdd((entityType, baseTable), e => CreateTable(e.EntityType, e.BaseTable))();

private Func<IInMemoryTable> CreateTable([NotNull] IEntityType entityType)
private Func<IInMemoryTable> CreateTable([NotNull] IEntityType entityType, IInMemoryTable baseTable)
=> (Func<IInMemoryTable>)typeof(InMemoryTableFactory).GetTypeInfo()
.GetDeclaredMethod(nameof(CreateFactory))
.MakeGenericMethod(GetKeyType(entityType.FindPrimaryKey()))
.Invoke(null, new object[] { entityType, _sensitiveLoggingEnabled });
.Invoke(null, new object[] { entityType, baseTable, _sensitiveLoggingEnabled });

[UsedImplicitly]
private static Func<IInMemoryTable> CreateFactory<TKey>(IEntityType entityType, bool sensitiveLoggingEnabled)
=> () => new InMemoryTable<TKey>(entityType, sensitiveLoggingEnabled);
private static Func<IInMemoryTable> CreateFactory<TKey>(
IEntityType entityType, IInMemoryTable baseTable, bool sensitiveLoggingEnabled)
=> () => new InMemoryTable<TKey>(entityType, baseTable, sensitiveLoggingEnabled);
}
}
Loading

0 comments on commit 2972c62

Please sign in to comment.