diff --git a/src/EFCore/ChangeTracking/Internal/ISharedTypeEntityFinder.cs b/src/EFCore/ChangeTracking/Internal/ISharedTypeEntityFinder.cs
new file mode 100644
index 00000000000..bd296d448b7
--- /dev/null
+++ b/src/EFCore/ChangeTracking/Internal/ISharedTypeEntityFinder.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Metadata;
+
+namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal
+{
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public interface ISharedTypeEntityFinder
+ {
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ /// the shared-type EntityType corresponding to this instance if found in the model, or null if not.
+ IEntityType FindSharedTypeEntityType([NotNull] object entityInstance);
+ }
+}
diff --git a/src/EFCore/ChangeTracking/Internal/SelfDescribingIndexPropertyEntityFinder.cs b/src/EFCore/ChangeTracking/Internal/SelfDescribingIndexPropertyEntityFinder.cs
new file mode 100644
index 00000000000..48d372285e4
--- /dev/null
+++ b/src/EFCore/ChangeTracking/Internal/SelfDescribingIndexPropertyEntityFinder.cs
@@ -0,0 +1,83 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal
+{
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class SelfDescribingIndexPropertyEntityFinder : ISharedTypeEntityFinder
+ {
+ public const string DefaultEntityTypeNamePropertyName = "__EntityTypeName__";
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public SelfDescribingIndexPropertyEntityFinder([NotNull] IModel model)
+ {
+ Check.NotNull(model, nameof(model));
+
+ Model = model;
+ EntityTypeNamePropertyName = DefaultEntityTypeNamePropertyName;
+ }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual string EntityTypeNamePropertyName { get; [NotNull]set; }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual IModel Model { get; }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ /// the shared-type EntityType corresponding to this instance if found in the model, or null if not.
+ public virtual IEntityType FindSharedTypeEntityType(object entityInstance)
+ {
+ Check.NotNull(entityInstance, nameof(entityInstance));
+
+ var efIndexerPropInfo = entityInstance.GetType()
+ .GetRuntimeProperties().FirstOrDefault(p => p.IsEFIndexerProperty());
+ if (efIndexerPropInfo == null)
+ {
+ return null;
+ }
+
+ string entityTypeName = null;
+ try
+ {
+ entityTypeName = efIndexerPropInfo
+ .GetValue(entityInstance, new[] { EntityTypeNamePropertyName }) as string;
+ }
+ catch
+ {
+ // exceptions indicate the indexer does not have the
+ // EntityTypeNamePropertyName key etc.
+ }
+
+ var entityType = entityTypeName == null
+ ? null
+ : Model.FindEntityType(entityTypeName);
+
+ return entityType != null && entityType.IsSharedType ? entityType : null;
+ }
+ }
+}
diff --git a/src/EFCore/ChangeTracking/Internal/StateManager.cs b/src/EFCore/ChangeTracking/Internal/StateManager.cs
index 8a5ae9254f2..25ca951d42c 100644
--- a/src/EFCore/ChangeTracking/Internal/StateManager.cs
+++ b/src/EFCore/ChangeTracking/Internal/StateManager.cs
@@ -47,6 +47,7 @@ public class StateManager : IStateManager
private readonly IModel _model;
private readonly IDatabase _database;
private readonly IConcurrencyDetector _concurrencyDetector;
+ private readonly ISharedTypeEntityFinder _sharedTypeEntityFinder;
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
@@ -72,6 +73,7 @@ public StateManager([NotNull] StateManagerDependencies dependencies)
UpdateLogger = dependencies.UpdateLogger;
_changeTrackingLogger = dependencies.ChangeTrackingLogger;
+ _sharedTypeEntityFinder = dependencies.SharedTypeEntityFinder;
}
///
@@ -155,7 +157,8 @@ public virtual InternalEntityEntry GetOrCreateEntry(object entity)
{
_trackingQueryMode = TrackingQueryMode.Multiple;
- var entityType = _model.FindRuntimeEntityType(entity.GetType());
+ var entityType = _sharedTypeEntityFinder.FindSharedTypeEntityType(entity)
+ ?? _model.FindRuntimeEntityType(entity.GetType());
if (entityType == null)
{
if (_model.HasEntityTypeWithDefiningNavigation(entity.GetType()))
diff --git a/src/EFCore/ChangeTracking/Internal/StateManagerDependencies.cs b/src/EFCore/ChangeTracking/Internal/StateManagerDependencies.cs
index 7311724918d..cdc587cb6b8 100644
--- a/src/EFCore/ChangeTracking/Internal/StateManagerDependencies.cs
+++ b/src/EFCore/ChangeTracking/Internal/StateManagerDependencies.cs
@@ -60,7 +60,8 @@ public sealed class StateManagerDependencies
[NotNull] IEntityMaterializerSource entityMaterializerSource,
[NotNull] ILoggingOptions loggingOptions,
[NotNull] IDiagnosticsLogger updateLogger,
- [NotNull] IDiagnosticsLogger changeTrackingLogger)
+ [NotNull] IDiagnosticsLogger changeTrackingLogger,
+ [NotNull] ISharedTypeEntityFinder sharedEntityTypeFinder)
{
InternalEntityEntryFactory = internalEntityEntryFactory;
InternalEntityEntrySubscriber = internalEntityEntrySubscriber;
@@ -76,6 +77,7 @@ public sealed class StateManagerDependencies
LoggingOptions = loggingOptions;
UpdateLogger = updateLogger;
ChangeTrackingLogger = changeTrackingLogger;
+ SharedTypeEntityFinder = sharedEntityTypeFinder;
}
///
@@ -162,6 +164,12 @@ public sealed class StateManagerDependencies
///
public IDiagnosticsLogger ChangeTrackingLogger { get; }
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public ISharedTypeEntityFinder SharedTypeEntityFinder { get; }
+
///
/// Clones this dependency parameter object with one service replaced.
///
@@ -182,7 +190,8 @@ public StateManagerDependencies With([NotNull] IInternalEntityEntryFactory inter
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
- ChangeTrackingLogger);
+ ChangeTrackingLogger,
+ SharedTypeEntityFinder);
///
/// Clones this dependency parameter object with one service replaced.
@@ -204,7 +213,8 @@ public StateManagerDependencies With([NotNull] IInternalEntityEntrySubscriber in
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
- ChangeTrackingLogger);
+ ChangeTrackingLogger,
+ SharedTypeEntityFinder);
///
/// Clones this dependency parameter object with one service replaced.
@@ -226,7 +236,8 @@ public StateManagerDependencies With([NotNull] IInternalEntityEntryNotifier inte
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
- ChangeTrackingLogger);
+ ChangeTrackingLogger,
+ SharedTypeEntityFinder);
///
/// Clones this dependency parameter object with one service replaced.
@@ -248,7 +259,8 @@ public StateManagerDependencies With([NotNull] ValueGenerationManager valueGener
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
- ChangeTrackingLogger);
+ ChangeTrackingLogger,
+ SharedTypeEntityFinder);
///
/// Clones this dependency parameter object with one service replaced.
@@ -270,7 +282,8 @@ public StateManagerDependencies With([NotNull] IModel model)
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
- ChangeTrackingLogger);
+ ChangeTrackingLogger,
+ SharedTypeEntityFinder);
///
/// Clones this dependency parameter object with one service replaced.
@@ -292,7 +305,8 @@ public StateManagerDependencies With([NotNull] IDatabase database)
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
- ChangeTrackingLogger);
+ ChangeTrackingLogger,
+ SharedTypeEntityFinder);
///
/// Clones this dependency parameter object with one service replaced.
@@ -314,7 +328,8 @@ public StateManagerDependencies With([NotNull] IConcurrencyDetector concurrencyD
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
- ChangeTrackingLogger);
+ ChangeTrackingLogger,
+ SharedTypeEntityFinder);
///
/// Clones this dependency parameter object with one service replaced.
@@ -336,7 +351,8 @@ public StateManagerDependencies With([NotNull] ICurrentDbContext currentContext)
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
- ChangeTrackingLogger);
+ ChangeTrackingLogger,
+ SharedTypeEntityFinder);
///
/// Clones this dependency parameter object with one service replaced.
@@ -358,7 +374,8 @@ public StateManagerDependencies With([NotNull] IEntityFinderSource entityFinderS
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
- ChangeTrackingLogger);
+ ChangeTrackingLogger,
+ SharedTypeEntityFinder);
///
/// Clones this dependency parameter object with one service replaced.
@@ -380,7 +397,8 @@ public StateManagerDependencies With([NotNull] IDbSetSource setSource)
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
- ChangeTrackingLogger);
+ ChangeTrackingLogger,
+ SharedTypeEntityFinder);
///
/// Clones this dependency parameter object with one service replaced.
@@ -402,7 +420,8 @@ public StateManagerDependencies With([NotNull] IEntityMaterializerSource entityM
entityMaterializerSource,
LoggingOptions,
UpdateLogger,
- ChangeTrackingLogger);
+ ChangeTrackingLogger,
+ SharedTypeEntityFinder);
///
/// Clones this dependency parameter object with one service replaced.
@@ -424,7 +443,8 @@ public StateManagerDependencies With([NotNull] ILoggingOptions loggingOptions)
EntityMaterializerSource,
loggingOptions,
UpdateLogger,
- ChangeTrackingLogger);
+ ChangeTrackingLogger,
+ SharedTypeEntityFinder);
///
/// Clones this dependency parameter object with one service replaced.
@@ -446,7 +466,8 @@ public StateManagerDependencies With([NotNull] IDiagnosticsLogger
/// Clones this dependency parameter object with one service replaced.
@@ -468,6 +489,31 @@ public StateManagerDependencies With([NotNull] IDiagnosticsLogger
+ /// Clones this dependency parameter object with one service replaced.
+ ///
+ /// A replacement for the current dependency of this type.
+ /// A new parameter object with the given service replaced.
+ public StateManagerDependencies With([NotNull] ISharedTypeEntityFinder sharedTypeEntityFinder)
+ => new StateManagerDependencies(
+ InternalEntityEntryFactory,
+ InternalEntityEntrySubscriber,
+ InternalEntityEntryNotifier,
+ ValueGenerationManager,
+ Model,
+ Database,
+ ConcurrencyDetector,
+ CurrentContext,
+ EntityFinderSource,
+ SetSource,
+ EntityMaterializerSource,
+ LoggingOptions,
+ UpdateLogger,
+ ChangeTrackingLogger,
+ sharedTypeEntityFinder);
+
}
}
diff --git a/src/EFCore/DbContext.cs b/src/EFCore/DbContext.cs
index cb41a4a6af8..c60395cdd26 100644
--- a/src/EFCore/DbContext.cs
+++ b/src/EFCore/DbContext.cs
@@ -54,6 +54,7 @@ public class DbContext :
IDbContextPoolable
{
private IDictionary _sets;
+ private IDictionary _sharedTypeSets;
private IDictionary _queries;
private readonly DbContextOptions _options;
@@ -215,6 +216,28 @@ object IDbSetCache.GetOrAddSet(IDbSetSource source, Type type)
return set;
}
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ object IDbSetCache.GetOrAddSharedTypeSet(IDbSetSource source, string entityTypeName, Type clrType)
+ {
+ CheckDisposed();
+
+ if (_sharedTypeSets == null)
+ {
+ _sharedTypeSets = new Dictionary();
+ }
+
+ if (!_sharedTypeSets.TryGetValue(entityTypeName, out var set))
+ {
+ set = source.CreateSharedTypeSet(this, entityTypeName, clrType);
+ _sharedTypeSets[entityTypeName] = set;
+ }
+
+ return set;
+ }
+
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
@@ -246,6 +269,16 @@ public virtual DbSet Set()
where TEntity : class
=> (DbSet)((IDbSetCache)this).GetOrAddSet(DbContextDependencies.SetSource, typeof(TEntity));
+ ///
+ /// Creates a that can be used to query and save instances of .
+ ///
+ /// The type of entity for which a set should be returned.
+ /// The name of the entity type as defined by .
+ /// A set for the given entity type.
+ public virtual DbSet SharedTypeSet(string entityTypeName)
+ where TEntity : class
+ => (DbSet)((IDbSetCache)this).GetOrAddSharedTypeSet(DbContextDependencies.SetSource, entityTypeName, typeof(TEntity));
+
///
/// Creates a that can be used to query instances of .
///
@@ -628,6 +661,17 @@ var resettableServices
}
}
+ if (_sharedTypeSets != null)
+ {
+ foreach (var set in _sharedTypeSets.Values)
+ {
+ if (set is IResettableService resettable)
+ {
+ resettable.ResetState();
+ }
+ }
+ }
+
if (_queries != null)
{
foreach (var query in _queries.Values)
diff --git a/src/EFCore/DbSet`.cs b/src/EFCore/DbSet`.cs
index 33309d3eb7d..390e1906999 100644
--- a/src/EFCore/DbSet`.cs
+++ b/src/EFCore/DbSet`.cs
@@ -35,7 +35,7 @@ namespace Microsoft.EntityFrameworkCore
///
/// objects are usually obtained from a
/// property on a derived or from the
- /// method.
+ /// or methods.
///
///
/// The type of entity being operated on by this set.
diff --git a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs
index fcb488d8d5f..a98591d16f0 100644
--- a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs
+++ b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs
@@ -106,6 +106,7 @@ public class EntityFrameworkServicesBuilder
{ typeof(IKeyPropagator), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(INavigationFixer), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(ILocalViewListener), new ServiceCharacteristics(ServiceLifetime.Scoped) },
+ { typeof(ISharedTypeEntityFinder), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IStateManager), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(Func), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IConcurrencyDetector), new ServiceCharacteristics(ServiceLifetime.Scoped) },
@@ -232,6 +233,7 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd();
TryAdd();
TryAdd();
+ TryAdd();
TryAdd();
TryAdd();
TryAdd();
diff --git a/src/EFCore/Internal/DbSetSource.cs b/src/EFCore/Internal/DbSetSource.cs
index d9012aceef1..c1dc7a2ef6a 100644
--- a/src/EFCore/Internal/DbSetSource.cs
+++ b/src/EFCore/Internal/DbSetSource.cs
@@ -17,41 +17,56 @@ public class DbSetSource : IDbSetSource, IDbQuerySource
private static readonly MethodInfo _genericCreateSet
= typeof(DbSetSource).GetTypeInfo().GetDeclaredMethod(nameof(CreateSetFactory));
+ private static readonly MethodInfo _genericCreateSharedTypeSet
+ = typeof(DbSetSource).GetTypeInfo().GetDeclaredMethod(nameof(CreateSharedTypeSetFactory));
+
private static readonly MethodInfo _genericCreateQuery
= typeof(DbSetSource).GetTypeInfo().GetDeclaredMethod(nameof(CreateQueryFactory));
- private readonly ConcurrentDictionary> _cache
- = new ConcurrentDictionary>();
+ private readonly ConcurrentDictionary> _cache
+ = new ConcurrentDictionary>();
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
public virtual object Create(DbContext context, Type type)
- => CreateCore(context, type, _genericCreateSet);
+ => CreateCore(context, type.DisplayName(), type, _genericCreateSet);
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual object CreateSharedTypeSet(DbContext context, string entityTypeName, Type type)
+ => CreateCore(context, entityTypeName, type, _genericCreateSharedTypeSet);
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
public virtual object CreateQuery(DbContext context, Type type)
- => CreateCore(context, type, _genericCreateQuery);
+ => CreateCore(context, type.DisplayName(), type, _genericCreateQuery);
- private object CreateCore(DbContext context, Type type, MethodInfo createMethod)
+ private object CreateCore(DbContext context, string entityTypeName, Type type, MethodInfo createMethod)
=> _cache.GetOrAdd(
- type,
- t => (Func)createMethod
- .MakeGenericMethod(t)
- .Invoke(null, null))(context);
+ entityTypeName,
+ name => (Func)createMethod
+ .MakeGenericMethod(type)
+ .Invoke(null, null))(context, entityTypeName);
+
+ [UsedImplicitly]
+ private static Func CreateSetFactory()
+ where TEntity : class
+ => (ctx, _) => new InternalDbSet(ctx);
[UsedImplicitly]
- private static Func CreateSetFactory()
+ private static Func CreateSharedTypeSetFactory()
where TEntity : class
- => c => new InternalDbSet(c);
+ => (ctx, entityTypeName) => new InternalSharedTypeDbSet(ctx, entityTypeName);
[UsedImplicitly]
- private static Func> CreateQueryFactory()
+ private static Func> CreateQueryFactory()
where TQuery : class
- => c => new InternalDbQuery(c);
+ => (ctx, _) => new InternalDbQuery(ctx);
}
}
diff --git a/src/EFCore/Internal/EntityFinder.cs b/src/EFCore/Internal/EntityFinder.cs
index b2c7dcef3b4..02951fba091 100644
--- a/src/EFCore/Internal/EntityFinder.cs
+++ b/src/EFCore/Internal/EntityFinder.cs
@@ -292,7 +292,9 @@ private IQueryable BuildQueryRoot(IEntityType entityType)
{
var definingEntityType = entityType.DefiningEntityType;
return definingEntityType == null
- ? (IQueryable)_setCache.GetOrAddSet(_setSource, entityType.ClrType)
+ ? entityType.IsSharedType
+ ? (IQueryable)_setCache.GetOrAddSharedTypeSet(_setSource, entityType.Name, entityType.ClrType)
+ : (IQueryable)_setCache.GetOrAddSet(_setSource, entityType.ClrType)
: BuildQueryRoot(definingEntityType, entityType);
}
diff --git a/src/EFCore/Internal/IDbSetCache.cs b/src/EFCore/Internal/IDbSetCache.cs
index da40030c7ef..60532d433c4 100644
--- a/src/EFCore/Internal/IDbSetCache.cs
+++ b/src/EFCore/Internal/IDbSetCache.cs
@@ -17,5 +17,11 @@ public interface IDbSetCache
/// directly from your code. This API may change or be removed in future releases.
///
object GetOrAddSet([NotNull] IDbSetSource source, [NotNull] Type type);
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ object GetOrAddSharedTypeSet([NotNull] IDbSetSource source, [NotNull] string entityTypeName, [NotNull] Type type);
}
}
diff --git a/src/EFCore/Internal/IDbSetSource.cs b/src/EFCore/Internal/IDbSetSource.cs
index 593a9094b28..41846722fcd 100644
--- a/src/EFCore/Internal/IDbSetSource.cs
+++ b/src/EFCore/Internal/IDbSetSource.cs
@@ -17,5 +17,11 @@ public interface IDbSetSource
/// directly from your code. This API may change or be removed in future releases.
///
object Create([NotNull] DbContext context, [NotNull] Type type);
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ object CreateSharedTypeSet([NotNull] DbContext context, [NotNull] string entityTypeName, [NotNull] Type type);
}
}
diff --git a/src/EFCore/Internal/InternalDbSet.cs b/src/EFCore/Internal/InternalDbSet.cs
index be7aa49568e..3e2a6fa916b 100644
--- a/src/EFCore/Internal/InternalDbSet.cs
+++ b/src/EFCore/Internal/InternalDbSet.cs
@@ -25,8 +25,8 @@ public class InternalDbSet :
DbSet, IQueryable, IAsyncEnumerableAccessor, IInfrastructure, IResettableService
where TEntity : class
{
- private readonly DbContext _context;
- private IEntityType _entityType;
+ protected readonly DbContext _context;
+ protected IEntityType _entityType;
private EntityQueryable _entityQueryable;
private LocalView _localView;
@@ -43,7 +43,7 @@ public InternalDbSet([NotNull] DbContext context)
_context = context;
}
- private IEntityType EntityType
+ protected virtual IEntityType EntityType
{
get
{
diff --git a/src/EFCore/Internal/InternalSharedTypeDbSet.cs b/src/EFCore/Internal/InternalSharedTypeDbSet.cs
new file mode 100644
index 00000000000..11523bb3a2c
--- /dev/null
+++ b/src/EFCore/Internal/InternalSharedTypeDbSet.cs
@@ -0,0 +1,53 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Internal
+{
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public class InternalSharedTypeDbSet : InternalDbSet
+ where TEntity : class
+ {
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public InternalSharedTypeDbSet([NotNull] DbContext context, [NotNull] string entityTypeName)
+ :base(context)
+ {
+ Check.NotNull(context, nameof(context));
+
+ EntityTypeName = entityTypeName;
+ }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual string EntityTypeName { get; }
+
+ protected override IEntityType EntityType
+ {
+ get
+ {
+ if (_entityType == null)
+ {
+ _entityType = _context.Model.FindEntityType(EntityTypeName);
+ if (_entityType == null)
+ {
+ throw new InvalidOperationException(CoreStrings.InvalidSharedTypeSet(EntityTypeName));
+ }
+ }
+
+ return _entityType;
+ }
+ }
+ }
+}
diff --git a/src/EFCore/Metadata/IEntityType.cs b/src/EFCore/Metadata/IEntityType.cs
index 6ff8ad7a65f..d2fb3a5f61b 100644
--- a/src/EFCore/Metadata/IEntityType.cs
+++ b/src/EFCore/Metadata/IEntityType.cs
@@ -43,6 +43,12 @@ public interface IEntityType : ITypeBase
/// true if the entity type is a query type; otherwise false.
bool IsQueryType { get; }
+ ///
+ /// Gets whether this entity type can share its ClrType with other entities.
+ ///
+ /// true if the entity type can share its ClrType with other entities; otherwise false.
+ bool IsSharedType { get; }
+
///
///
/// Gets primary key for this entity. Returns null if no primary key is defined.
diff --git a/src/EFCore/Metadata/IMutableEntityType.cs b/src/EFCore/Metadata/IMutableEntityType.cs
index 5fd7afaadda..3c27e3757e9 100644
--- a/src/EFCore/Metadata/IMutableEntityType.cs
+++ b/src/EFCore/Metadata/IMutableEntityType.cs
@@ -41,6 +41,12 @@ public interface IMutableEntityType : IEntityType, IMutableTypeBase
/// true if the entity type is a query type; otherwise false.
new bool IsQueryType { get; set; }
+ ///
+ /// Gets or sets whether this entity type can share its ClrType with other entities.
+ ///
+ /// true if the entity type can share its ClrType with other entities; otherwise false.
+ new bool IsSharedType { get; set; }
+
///
/// Gets the LINQ query used as the default source for queries of this type.
///
diff --git a/src/EFCore/Metadata/IMutableModel.cs b/src/EFCore/Metadata/IMutableModel.cs
index bbd8783d884..17b14415a2b 100644
--- a/src/EFCore/Metadata/IMutableModel.cs
+++ b/src/EFCore/Metadata/IMutableModel.cs
@@ -64,6 +64,16 @@ public interface IMutableModel : IModel, IMutableAnnotatable
[NotNull] string definingNavigationName,
[NotNull] IMutableEntityType definingEntityType);
+ ///
+ /// Adds an entity type to the model which has a CLR-type which can be shared with other entity types.
+ ///
+ /// The name of the entity to be added.
+ /// The CLR class that is used to represent instances of this entity type.
+ /// The new entity type.
+ IMutableEntityType AddSharedTypeEntityType(
+ [NotNull] string name,
+ [NotNull] Type type);
+
///
/// Gets the entity with the given name. Returns null if no entity type with the given name is found
/// or the entity type has a defining navigation.
diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs
index 3591a638f5a..37dd4c56e2f 100644
--- a/src/EFCore/Metadata/Internal/EntityType.cs
+++ b/src/EFCore/Metadata/Internal/EntityType.cs
@@ -120,6 +120,20 @@ public EntityType([NotNull] Type clrType, [NotNull] Model model, ConfigurationSo
DefiningEntityType = definingEntityType;
}
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public EntityType([NotNull] string name,
+ [NotNull] Type clrType,
+ [NotNull] Model model,
+ ConfigurationSource configurationSource)
+ : base(name, clrType, model, configurationSource)
+ {
+ _properties = new SortedDictionary(new PropertyComparer(this));
+ Builder = new InternalEntityTypeBuilder(this, model.Builder);
+ }
+
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
@@ -174,6 +188,12 @@ public virtual LambdaExpression QueryFilter
///
public virtual bool IsQueryType { get; set; }
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual bool IsSharedType { get; set; }
+
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
diff --git a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs
index 702b59c80e9..c813acef0e8 100644
--- a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs
+++ b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs
@@ -33,7 +33,7 @@ public static class EntityTypeExtensions
[DebuggerStepThrough]
public static string ShortName([NotNull] this IEntityType type)
{
- if (type.ClrType != null)
+ if (!type.IsSharedType && type.ClrType != null)
{
return type.ClrType.ShortDisplayName();
}
diff --git a/src/EFCore/Metadata/Internal/Model.cs b/src/EFCore/Metadata/Internal/Model.cs
index 278de5a08e6..2102e4e16c0 100644
--- a/src/EFCore/Metadata/Internal/Model.cs
+++ b/src/EFCore/Metadata/Internal/Model.cs
@@ -154,6 +154,27 @@ public virtual IEnumerable GetEntityTypes()
return AddEntityType(queryType);
}
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual EntityType AddSharedTypeEntityType(
+ [NotNull] string name,
+ [NotNull] Type type,
+ // ReSharper disable once MethodOverloadWithOptionalParameter
+ ConfigurationSource configurationSource = ConfigurationSource.Explicit)
+ {
+ Check.NotEmpty(name, nameof(name));
+ Check.NotNull(type, nameof(type));
+
+ var entityType = new EntityType(name, type, this, configurationSource)
+ {
+ IsSharedType = true
+ };
+
+ return AddEntityType(entityType);
+ }
+
private EntityType AddEntityType(EntityType entityType)
{
var entityTypeName = entityType.Name;
@@ -187,6 +208,20 @@ private EntityType AddEntityType(EntityType entityType)
var added = entityTypesWithSameType.Add(entityType);
Debug.Assert(added);
}
+ else if (entityType.IsSharedType)
+ {
+ if (_entityTypesWithDefiningNavigation.ContainsKey(entityTypeName))
+ {
+ throw new InvalidOperationException(CoreStrings.ClashingWeakEntityType(entityType.DisplayName()));
+ }
+
+ if (_entityTypes.ContainsKey(entityTypeName))
+ {
+ throw new InvalidOperationException(CoreStrings.ClashingSharedTypeEntityType(entityType.DisplayName()));
+ }
+
+ _entityTypes.Add(entityTypeName, entityType);
+ }
else
{
if (_entityTypesWithDefiningNavigation.ContainsKey(entityTypeName))
@@ -663,6 +698,7 @@ protected override Annotation OnAnnotationSet(string name, Annotation annotation
IMutableEntityType IMutableModel.AddEntityType(string name) => AddEntityType(name);
IMutableEntityType IMutableModel.AddEntityType(Type type) => AddEntityType(type);
IMutableEntityType IMutableModel.AddQueryType(Type type) => AddQueryType(type);
+ IMutableEntityType IMutableModel.AddSharedTypeEntityType(string name, Type type) => AddSharedTypeEntityType(name, type);
IMutableEntityType IMutableModel.RemoveEntityType(string name) => RemoveEntityType(name);
IEntityType IModel.FindEntityType(string name, string definingNavigationName, IEntityType definingEntityType)
diff --git a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs
index 894a0bec607..d35070320ba 100644
--- a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs
+++ b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs
@@ -62,9 +62,19 @@ private static PropertyAccessors CreateGeneric(IPropertyBase property
Expression.Property(entryParameter, "Entity"),
entityClrType);
- currentValueExpression = Expression.MakeMemberAccess(
- convertedExpression,
- propertyBase.GetMemberInfo(forConstruction: false, forSet: false));
+ if (propertyBase.IsIndexedProperty)
+ {
+ currentValueExpression = Expression.MakeIndex(
+ convertedExpression,
+ propertyBase.PropertyInfo,
+ new [] { Expression.Constant(propertyBase.Name) });
+ }
+ else
+ {
+ currentValueExpression = Expression.MakeMemberAccess(
+ convertedExpression,
+ propertyBase.GetMemberInfo(forConstruction: false, forSet: false));
+ }
if (currentValueExpression.Type != typeof(TProperty))
{
diff --git a/src/EFCore/Metadata/Internal/TypeBase.cs b/src/EFCore/Metadata/Internal/TypeBase.cs
index a8fe7a79681..77305b182df 100644
--- a/src/EFCore/Metadata/Internal/TypeBase.cs
+++ b/src/EFCore/Metadata/Internal/TypeBase.cs
@@ -50,6 +50,22 @@ protected TypeBase([NotNull] Type clrType, [NotNull] Model model, ConfigurationS
ClrType = clrType;
}
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ protected TypeBase([NotNull] string name, [NotNull] Type clrType,
+ [NotNull] Model model, ConfigurationSource configurationSource)
+ : this(model, configurationSource)
+ {
+ Check.NotEmpty(name, nameof(name));
+ Check.NotNull(clrType, nameof(clrType));
+ Check.NotNull(model, nameof(model));
+
+ Name = name;
+ ClrType = clrType;
+ }
+
private TypeBase([NotNull] Model model, ConfigurationSource configurationSource)
{
Model = model;
diff --git a/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs b/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs
index 1fe72b0d2b2..5b5fe1e706e 100644
--- a/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs
+++ b/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs
@@ -48,11 +48,11 @@ public static bool IsAbstract([NotNull] this ITypeBase type)
public static PropertyInfo EFIndexerProperty([NotNull] this ITypeBase type)
{
var runtimeProperties = type is TypeBase typeBase
- ? typeBase.GetRuntimeProperties().Values // better perf if we've already computed them once
+ ? typeBase.GetRuntimeProperties()?.Values // better perf if we've already computed them once
: type.ClrType.GetRuntimeProperties();
// find the indexer with single argument of type string which returns an object
- return runtimeProperties.FirstOrDefault(p => p.IsEFIndexerProperty());
+ return runtimeProperties?.FirstOrDefault(p => p.IsEFIndexerProperty());
}
}
}
diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs
index 68eed6f4b98..df0cd38cd83 100644
--- a/src/EFCore/Properties/CoreStrings.Designer.cs
+++ b/src/EFCore/Properties/CoreStrings.Designer.cs
@@ -2828,6 +2828,22 @@ public static string ConflictingForeignKeyAttributes([CanBeNull] object property
GetString("ConflictingForeignKeyAttributes", nameof(propertyList), nameof(entityType)),
propertyList, entityType);
+ ///
+ /// The shared-type entity type '{entityType}' cannot be added to the model because an entity type with the same name already exists.
+ ///
+ public static string ClashingSharedTypeEntityType([CanBeNull] object entityType)
+ => string.Format(
+ GetString("ClashingSharedTypeEntityType", nameof(entityType)),
+ entityType);
+
+ ///
+ /// Cannot create a shared-type DbSet with name '{entityTypeName}' because no entity type with that name has been included in the model for this context. Please define a shared-type entity type by calling 'modelBuilder.Model.AddSharedTypeEntityType()' in `OnModelCreating()`.
+ ///
+ public static string InvalidSharedTypeSet([CanBeNull] object entityTypeName)
+ => string.Format(
+ GetString("InvalidSharedTypeSet", nameof(entityTypeName)),
+ entityTypeName);
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx
index 9737cbc3503..82c64c08288 100644
--- a/src/EFCore/Properties/CoreStrings.resx
+++ b/src/EFCore/Properties/CoreStrings.resx
@@ -1138,4 +1138,10 @@
There are multiple ForeignKeyAttributes which are pointing to same set of properties - '{propertyList}' on entity type '{entityType}'.
+
+ The shared-type entity type '{entityType}' cannot be added to the model because an entity type with the same name already exists.
+
+
+ Cannot create a shared-type DbSet with name '{entityTypeName}' because no entity type with that name has been included in the model for this context. Please define a shared-type entity type by calling 'modelBuilder.Model.AddSharedTypeEntityType()' in `OnModelCreating()`.
+
\ No newline at end of file
diff --git a/src/EFCore/breakingchanges.netcore.json b/src/EFCore/breakingchanges.netcore.json
index 36ce4493a0b..c5cd0f1e3d4 100644
--- a/src/EFCore/breakingchanges.netcore.json
+++ b/src/EFCore/breakingchanges.netcore.json
@@ -16,5 +16,25 @@
"TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IPropertyBase : Microsoft.EntityFrameworkCore.Infrastructure.IAnnotatable",
"MemberId": "System.Boolean get_IsIndexedProperty()",
"Kind": "Addition"
+ },
+ {
+ "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IEntityType : Microsoft.EntityFrameworkCore.Metadata.ITypeBase",
+ "MemberId": "System.Boolean get_IsSharedType()",
+ "Kind": "Addition"
+ },
+ {
+ "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IMutableEntityType : Microsoft.EntityFrameworkCore.Metadata.IEntityType, Microsoft.EntityFrameworkCore.Metadata.IMutableTypeBase",
+ "MemberId": "System.Boolean get_IsSharedType()",
+ "Kind": "Addition"
+ },
+ {
+ "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IMutableEntityType : Microsoft.EntityFrameworkCore.Metadata.IEntityType, Microsoft.EntityFrameworkCore.Metadata.IMutableTypeBase",
+ "MemberId": "System.Void set_IsSharedType(System.Boolean value)",
+ "Kind": "Addition"
+ },
+ {
+ "TypeId": "public interface Microsoft.EntityFrameworkCore.Metadata.IMutableModel : Microsoft.EntityFrameworkCore.Metadata.IModel, Microsoft.EntityFrameworkCore.Metadata.IMutableAnnotatable",
+ "MemberId": "Microsoft.EntityFrameworkCore.Metadata.IMutableEntityType AddSharedTypeEntityType(System.String name, System.Type type)",
+ "Kind": "Addition"
}
]
diff --git a/test/EFCore.Tests/ChangeTracking/Internal/SelfDescribingIndexPropertyEntityFinderTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/SelfDescribingIndexPropertyEntityFinderTest.cs
new file mode 100644
index 00000000000..c294907fbb3
--- /dev/null
+++ b/test/EFCore.Tests/ChangeTracking/Internal/SelfDescribingIndexPropertyEntityFinderTest.cs
@@ -0,0 +1,103 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore.Internal;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Xunit;
+
+namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal
+{
+ public class SelfDescribingIndexPropertyEntityFinderTest
+ {
+ [Fact]
+ public void Can_find_EntityType_using_default_EntityTypeNamePropertyName()
+ {
+ var model = new Model();
+ var entityType = model.AddSharedTypeEntityType("TestDictionaryEntityType", typeof(Dictionary));
+ var idProperty = entityType.AddIndexedProperty("Id", typeof(int));
+ idProperty.IsNullable = false;
+ var propA = entityType.AddIndexedProperty("PropA", typeof(string));
+
+ var instance = new Dictionary()
+ {
+ { "__EntityTypeName__", "TestDictionaryEntityType" },
+ { "Id", 0 },
+ { "PropA", "PropAValue"},
+ };
+
+ var finder = new SelfDescribingIndexPropertyEntityFinder(model);
+
+ Assert.Equal(entityType, finder.FindSharedTypeEntityType(instance));
+ }
+
+ [Fact]
+ public void Can_find_EntityType_using_user_defined_EntityTypeNamePropertyName()
+ {
+ var model = new Model();
+ var entityType = model.AddSharedTypeEntityType("TestDictionaryEntityType", typeof(Dictionary));
+ var idProperty = entityType.AddIndexedProperty("Id", typeof(int));
+ idProperty.IsNullable = false;
+ var propA = entityType.AddIndexedProperty("PropA", typeof(string));
+
+ var instance = new Dictionary()
+ {
+ { "SelfDescribingProperty", "TestDictionaryEntityType" },
+ { "Id", 0 },
+ { "PropA", "PropAValue"},
+ };
+
+ var finder = new SelfDescribingIndexPropertyEntityFinder(model);
+ finder.EntityTypeNamePropertyName = "SelfDescribingProperty";
+
+ Assert.Equal(entityType, finder.FindSharedTypeEntityType(instance));
+ }
+
+ [Fact]
+ public void Return_null_if_no_matching_EntityType_found()
+ {
+ var model = new Model();
+ var entityType = model.AddSharedTypeEntityType("DifferentlyNamedEntityType", typeof(Dictionary));
+ var idProperty = entityType.AddIndexedProperty("Id", typeof(int));
+ idProperty.IsNullable = false;
+ var propA = entityType.AddIndexedProperty("PropA", typeof(string));
+
+ var instance = new Dictionary()
+ {
+ { "__EntityTypeName__", "TestDictionaryEntityType" },
+ { "Id", 0 },
+ { "PropA", "PropAValue"},
+ };
+
+ var finder = new SelfDescribingIndexPropertyEntityFinder(model);
+
+ Assert.Null(finder.FindSharedTypeEntityType(instance));
+ }
+ [Fact]
+ public void Return_null_if_find_non_shared_type_EntityType_with_same_name()
+ {
+ var model = new Model();
+ var entityType = model.AddEntityType(typeof(NonSharedTypeEntity));
+ var idProperty = entityType.AddProperty("Id", typeof(int));
+ idProperty.IsNullable = false;
+ var propA = entityType.AddProperty("PropA", typeof(string));
+
+ var instance = new Dictionary()
+ {
+ { "__EntityTypeName__", entityType.Name },
+ { "Id", 0 },
+ { "PropA", "PropAValue"},
+ };
+
+ var finder = new SelfDescribingIndexPropertyEntityFinder(model);
+
+ Assert.Null(finder.FindSharedTypeEntityType(instance));
+ }
+
+ private class NonSharedTypeEntity
+ {
+ public int Id { get; set; }
+ public string PropA { get; set; }
+ }
+ }
+}
diff --git a/test/EFCore.Tests/DbContextTest.cs b/test/EFCore.Tests/DbContextTest.cs
index 8d336055e15..fdfd83258b6 100644
--- a/test/EFCore.Tests/DbContextTest.cs
+++ b/test/EFCore.Tests/DbContextTest.cs
@@ -862,7 +862,7 @@ public async Task It_throws_object_disposed_exception()
await Assert.ThrowsAsync(() => context.FindAsync(typeof(Random), 77));
var methodCount = typeof(DbContext).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Count();
- var expectedMethodCount = 41;
+ var expectedMethodCount = 42;
Assert.True(
methodCount == expectedMethodCount,
userMessage: $"Expected {expectedMethodCount} methods on DbContext but found {methodCount}. " +
diff --git a/test/EFCore.Tests/DbSetSourceTest.cs b/test/EFCore.Tests/DbSetSourceTest.cs
index 9ed5034ee70..38ce21af622 100644
--- a/test/EFCore.Tests/DbSetSourceTest.cs
+++ b/test/EFCore.Tests/DbSetSourceTest.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;
@@ -22,6 +23,19 @@ public void Can_create_new_generic_DbSet()
Assert.IsType>(set);
}
+ [Fact]
+ public void Can_create_new_generic_SharedTypeDbSet()
+ {
+ var context = InMemoryTestHelpers.Instance.CreateContext();
+
+ var factorySource = new DbSetSource();
+
+ var set = factorySource.CreateSharedTypeSet(context, "SharedTypeEntityType", typeof(Dictionary));
+
+ Assert.IsType>>(set);
+ }
+
+
[Fact]
public void Always_creates_a_new_DbSet_instance()
{
@@ -31,5 +45,17 @@ public void Always_creates_a_new_DbSet_instance()
Assert.NotSame(factorySource.Create(context, typeof(Random)), factorySource.Create(context, typeof(Random)));
}
+
+ [Fact]
+ public void Always_creates_a_new_SharedTypeDbSet_instance()
+ {
+ var context = InMemoryTestHelpers.Instance.CreateContext();
+
+ var factorySource = new DbSetSource();
+
+ Assert.NotSame(
+ factorySource.CreateSharedTypeSet(context, "SharedTypeEntityType", typeof(Dictionary)),
+ factorySource.CreateSharedTypeSet(context, "SharedTypeEntityType", typeof(Dictionary)));
+ }
}
}
diff --git a/test/EFCore.Tests/DbSetTest.cs b/test/EFCore.Tests/DbSetTest.cs
index 7c800ab6c09..8c11a33cc41 100644
--- a/test/EFCore.Tests/DbSetTest.cs
+++ b/test/EFCore.Tests/DbSetTest.cs
@@ -114,6 +114,15 @@ public void Direct_use_of_Set_throws_if_context_disposed()
Assert.Throws(() => context.Set());
}
+ [Fact]
+ public void Direct_use_of_SharedTypeSet_throws_if_context_disposed()
+ {
+ var context = new EarlyLearningCenter();
+ context.Dispose();
+
+ Assert.Throws(() => context.SharedTypeSet>("SharedTypeEntityTypeName"));
+ }
+
[Fact]
public void Direct_use_of_Query_throws_if_context_disposed()
{
diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs
index f5251865cc4..738ac5d8602 100644
--- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs
+++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs
@@ -102,6 +102,7 @@ private class FakeEntityType : IEntityType
public LambdaExpression QueryFilter { get; }
public LambdaExpression DefiningQuery { get; }
public bool IsQueryType { get; }
+ public bool IsSharedType { get; }
public IKey FindPrimaryKey() => throw new NotImplementedException();
public IKey FindKey(IReadOnlyList properties) => throw new NotImplementedException();
public IEnumerable GetKeys() => throw new NotImplementedException();
diff --git a/test/EFCore.Tests/Metadata/Internal/PropertyAccessorsFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/PropertyAccessorsFactoryTest.cs
new file mode 100644
index 00000000000..b172242febb
--- /dev/null
+++ b/test/EFCore.Tests/Metadata/Internal/PropertyAccessorsFactoryTest.cs
@@ -0,0 +1,88 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+// ReSharper disable InconsistentNaming
+namespace Microsoft.EntityFrameworkCore.Metadata.Internal
+{
+ public class PropertyAccessorsFactoryTest
+ {
+ [Fact]
+ public void Can_use_PropertyAccessorsFactory_on_indexed_property()
+ {
+ var model = new Model();
+ var entityType = model.AddEntityType(typeof(IndexedClass));
+ var id = entityType.AddProperty("Id", typeof(int));
+ var propertyA = entityType.AddIndexedProperty("PropertyA", typeof(string));
+
+ var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(model);
+ var stateManager = contextServices.GetRequiredService();
+ var factory = contextServices.GetRequiredService();
+
+ var entity = new IndexedClass();
+ var entry = factory.Create(stateManager, entityType, entity);
+
+ var propertyAccessors = new PropertyAccessorsFactory().Create(propertyA);
+ Assert.Equal("ValueA", ((Func)propertyAccessors.CurrentValueGetter)(entry));
+ Assert.Equal("ValueA", ((Func)propertyAccessors.OriginalValueGetter)(entry));
+ Assert.Equal("ValueA", ((Func)propertyAccessors.PreStoreGeneratedCurrentValueGetter)(entry));
+ Assert.Equal("ValueA", ((Func)propertyAccessors.RelationshipSnapshotGetter)(entry));
+
+ var valueBuffer = new ValueBuffer(new object[] { 1, "ValueA" });
+ Assert.Equal("ValueA", ((Func)propertyAccessors.ValueBufferGetter)(valueBuffer));
+ }
+
+ [Fact]
+ public void Can_use_PropertyAccessorsFactory_on_non_indexed_property()
+ {
+ var model = new Model();
+ var entityType = model.AddEntityType(typeof(NonIndexedClass));
+ var id = entityType.AddProperty("Id", typeof(int));
+ var propA = entityType.AddProperty("PropA", typeof(string));
+
+ var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(model);
+ var stateManager = contextServices.GetRequiredService();
+ var factory = contextServices.GetRequiredService();
+
+ var entity = new NonIndexedClass();
+ var entry = factory.Create(stateManager, entityType, entity);
+
+ var propertyAccessors = new PropertyAccessorsFactory().Create(propA);
+ Assert.Equal("ValueA", ((Func)propertyAccessors.CurrentValueGetter)(entry));
+ Assert.Equal("ValueA", ((Func)propertyAccessors.OriginalValueGetter)(entry));
+ Assert.Equal("ValueA", ((Func)propertyAccessors.PreStoreGeneratedCurrentValueGetter)(entry));
+ Assert.Equal("ValueA", ((Func)propertyAccessors.RelationshipSnapshotGetter)(entry));
+
+ var valueBuffer = new ValueBuffer(new object[] { 1, "ValueA" });
+ Assert.Equal("ValueA", ((Func)propertyAccessors.ValueBufferGetter)(valueBuffer));
+ }
+
+ private class IndexedClass
+ {
+ private Dictionary _internalValues = new Dictionary()
+ {
+ { "PropertyA", "ValueA" }
+ };
+
+ internal int Id { get; set; }
+
+ public object this[string name]
+ {
+ get => _internalValues[name];
+ }
+ }
+
+ private class NonIndexedClass
+ {
+ internal int Id { get; set; }
+ public string PropA { get; set; } = "ValueA";
+ }
+ }
+}