Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] - Shared-Type Entities Update Pipeline #14057

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/EFCore/ChangeTracking/Internal/ISharedTypeEntityFinder.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
public interface ISharedTypeEntityFinder
{
/// <summary>
/// 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.
/// </summary>
/// <returns> the shared-type EntityType corresponding to this instance if found in the model, or null if not. </returns>
IEntityType FindSharedTypeEntityType([NotNull] object entityInstance);
}
Copy link
Contributor Author

@lajones lajones Dec 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering about making this a Chain-Of-Responsibility pattern by adding a NextFinder getter/setter. That way if you are using multiple different patterns of identifying a Shared-Type-EntityType from instance in your context you can set up multiple of these chained together in the order you want to apply them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that would be necessary, this logic shouldn't be replaced or extended and it shouldn't need to change often. Furthermore it's in a perf-sensitive part of the code and keeping all the patterns for finding the EntityType in one class will be slightly faster.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see your point. Don't agree that it shouldn't be replaced or extended. I'd be really surprised if different users of this functionality are completely happy with just the one way of doing it. It was not even agreed among us that the index property was the only way to go. That said I take your point on perf (though we do cache the result of course) and maybe we could wait on Chain-Of-Responsibility til we get feedback from customers.

}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
public class SelfDescribingIndexPropertyEntityFinder : ISharedTypeEntityFinder
{
public const string DefaultEntityTypeNamePropertyName = "__EntityTypeName__";

/// <summary>
/// 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.
/// </summary>
public SelfDescribingIndexPropertyEntityFinder([NotNull] IModel model)
{
Check.NotNull(model, nameof(model));

Model = model;
EntityTypeNamePropertyName = DefaultEntityTypeNamePropertyName;
}

/// <summary>
/// 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.
/// </summary>
public virtual string EntityTypeNamePropertyName { get; [NotNull]set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to be writable

Copy link
Contributor Author

@lajones lajones Dec 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually thought was a nice feature, It just allows you to pick a different argument to the indexer - other than "__EntityTypeName__" - which I kind of picked at random.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One would need to do that in the constructor, allowing to do it afterwards could easily lead to inconsistency in the model.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also since it's overridable derived classes can just replace it with a different value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is true that users could get themselves in trouble if they changed the value half way through. I was just trying to make it simple - so they didn't have to write a whole new class just to change that one value.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we really want to make this simple to configure we should add sugar API that doesn't require messing with services at all, perhaps something on DbContextOptions


/// <summary>
/// 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.
/// </summary>
public virtual IModel Model { get; }

/// <summary>
/// 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.
/// </summary>
/// <returns> the shared-type EntityType corresponding to this instance if found in the model, or null if not. </returns>
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;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could add a FindSharedTypeEntityType(string name) method on Model and use that instead of the above?

}
}
5 changes: 4 additions & 1 deletion src/EFCore/ChangeTracking/Internal/StateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class StateManager : IStateManager
private readonly IModel _model;
private readonly IDatabase _database;
private readonly IConcurrencyDetector _concurrencyDetector;
private readonly ISharedTypeEntityFinder _sharedTypeEntityFinder;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
Expand All @@ -72,6 +73,7 @@ public StateManager([NotNull] StateManagerDependencies dependencies)

UpdateLogger = dependencies.UpdateLogger;
_changeTrackingLogger = dependencies.ChangeTrackingLogger;
_sharedTypeEntityFinder = dependencies.SharedTypeEntityFinder;
}

/// <summary>
Expand Down Expand Up @@ -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());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_model.FindRuntimeEntityType(entity.GetType()) can be moved into the SharedTypeEntityFinder implementation and it could be renamed to EntityTypeFinder.
Are you planning to implement other type finding strategies? Is there a tracking issue that lists them?

Copy link
Contributor Author

@lajones lajones Dec 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I'm following. This service is meant to find Shared-Type EntiyTypes only. I did have an idea for another one of those - which is for a CLR-type which is shared, and has an indexer for the other properties, but could have a known, fixed-name CLR-property for the EntityType name. But I hadn't discussed that with @ajcvickers or @divega yet and I seem to remember one of them had a 3rd idea on how we should do this. Basically I can see people wanting to identify Shared-Type entities in various different ways and wanted to make it as easy as possible to write your own service if you wanted to.

But I did not intend to change or make into a service the existing functionality to find EntityTypes using their CLR-type. There are 6 methods already existing on Model which do that in various different ways. Did you want to encapsulate all of that somehow?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I would like to avoid having to special case Shared type entity types as much as possible and here this can by accomplished by making the service look for non-shared types as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also implement the strategy that uses a normal property and have the name passed in the constructor or a overridable readonly property.

if (entityType == null)
{
if (_model.HasEntityTypeWithDefiningNavigation(entity.GetType()))
Expand Down
76 changes: 61 additions & 15 deletions src/EFCore/ChangeTracking/Internal/StateManagerDependencies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ public sealed class StateManagerDependencies
[NotNull] IEntityMaterializerSource entityMaterializerSource,
[NotNull] ILoggingOptions loggingOptions,
[NotNull] IDiagnosticsLogger<DbLoggerCategory.Update> updateLogger,
[NotNull] IDiagnosticsLogger<DbLoggerCategory.ChangeTracking> changeTrackingLogger)
[NotNull] IDiagnosticsLogger<DbLoggerCategory.ChangeTracking> changeTrackingLogger,
[NotNull] ISharedTypeEntityFinder sharedEntityTypeFinder)
{
InternalEntityEntryFactory = internalEntityEntryFactory;
InternalEntityEntrySubscriber = internalEntityEntrySubscriber;
Expand All @@ -76,6 +77,7 @@ public sealed class StateManagerDependencies
LoggingOptions = loggingOptions;
UpdateLogger = updateLogger;
ChangeTrackingLogger = changeTrackingLogger;
SharedTypeEntityFinder = sharedEntityTypeFinder;
}

/// <summary>
Expand Down Expand Up @@ -162,6 +164,12 @@ public sealed class StateManagerDependencies
/// </summary>
public IDiagnosticsLogger<DbLoggerCategory.ChangeTracking> ChangeTrackingLogger { get; }

/// <summary>
/// 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.
/// </summary>
public ISharedTypeEntityFinder SharedTypeEntityFinder { get; }

/// <summary>
/// Clones this dependency parameter object with one service replaced.
/// </summary>
Expand All @@ -182,7 +190,8 @@ public StateManagerDependencies With([NotNull] IInternalEntityEntryFactory inter
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
ChangeTrackingLogger);
ChangeTrackingLogger,
SharedTypeEntityFinder);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -204,7 +213,8 @@ public StateManagerDependencies With([NotNull] IInternalEntityEntrySubscriber in
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
ChangeTrackingLogger);
ChangeTrackingLogger,
SharedTypeEntityFinder);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -226,7 +236,8 @@ public StateManagerDependencies With([NotNull] IInternalEntityEntryNotifier inte
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
ChangeTrackingLogger);
ChangeTrackingLogger,
SharedTypeEntityFinder);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -248,7 +259,8 @@ public StateManagerDependencies With([NotNull] ValueGenerationManager valueGener
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
ChangeTrackingLogger);
ChangeTrackingLogger,
SharedTypeEntityFinder);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -270,7 +282,8 @@ public StateManagerDependencies With([NotNull] IModel model)
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
ChangeTrackingLogger);
ChangeTrackingLogger,
SharedTypeEntityFinder);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -292,7 +305,8 @@ public StateManagerDependencies With([NotNull] IDatabase database)
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
ChangeTrackingLogger);
ChangeTrackingLogger,
SharedTypeEntityFinder);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -314,7 +328,8 @@ public StateManagerDependencies With([NotNull] IConcurrencyDetector concurrencyD
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
ChangeTrackingLogger);
ChangeTrackingLogger,
SharedTypeEntityFinder);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -336,7 +351,8 @@ public StateManagerDependencies With([NotNull] ICurrentDbContext currentContext)
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
ChangeTrackingLogger);
ChangeTrackingLogger,
SharedTypeEntityFinder);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -358,7 +374,8 @@ public StateManagerDependencies With([NotNull] IEntityFinderSource entityFinderS
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
ChangeTrackingLogger);
ChangeTrackingLogger,
SharedTypeEntityFinder);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -380,7 +397,8 @@ public StateManagerDependencies With([NotNull] IDbSetSource setSource)
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
ChangeTrackingLogger);
ChangeTrackingLogger,
SharedTypeEntityFinder);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -402,7 +420,8 @@ public StateManagerDependencies With([NotNull] IEntityMaterializerSource entityM
entityMaterializerSource,
LoggingOptions,
UpdateLogger,
ChangeTrackingLogger);
ChangeTrackingLogger,
SharedTypeEntityFinder);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -424,7 +443,8 @@ public StateManagerDependencies With([NotNull] ILoggingOptions loggingOptions)
EntityMaterializerSource,
loggingOptions,
UpdateLogger,
ChangeTrackingLogger);
ChangeTrackingLogger,
SharedTypeEntityFinder);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -446,7 +466,8 @@ public StateManagerDependencies With([NotNull] IDiagnosticsLogger<DbLoggerCatego
EntityMaterializerSource,
LoggingOptions,
updateLogger,
ChangeTrackingLogger);
ChangeTrackingLogger,
SharedTypeEntityFinder);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
Expand All @@ -468,6 +489,31 @@ public StateManagerDependencies With([NotNull] IDiagnosticsLogger<DbLoggerCatego
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
changeTrackingLogger);
changeTrackingLogger,
SharedTypeEntityFinder);

/// <summary>
/// Clones this dependency parameter object with one service replaced.
/// </summary>
/// <param name="sharedTypeEntityFinder"> A replacement for the current dependency of this type. </param>
/// <returns> A new parameter object with the given service replaced. </returns>
public StateManagerDependencies With([NotNull] ISharedTypeEntityFinder sharedTypeEntityFinder)
=> new StateManagerDependencies(
InternalEntityEntryFactory,
InternalEntityEntrySubscriber,
InternalEntityEntryNotifier,
ValueGenerationManager,
Model,
Database,
ConcurrencyDetector,
CurrentContext,
EntityFinderSource,
SetSource,
EntityMaterializerSource,
LoggingOptions,
UpdateLogger,
ChangeTrackingLogger,
sharedTypeEntityFinder);

}
}
44 changes: 44 additions & 0 deletions src/EFCore/DbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public class DbContext :
IDbContextPoolable
{
private IDictionary<Type, object> _sets;
private IDictionary<string, object> _sharedTypeSets;
private IDictionary<Type, object> _queries;
private readonly DbContextOptions _options;

Expand Down Expand Up @@ -215,6 +216,28 @@ object IDbSetCache.GetOrAddSet(IDbSetSource source, Type type)
return set;
}

/// <summary>
/// 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.
/// </summary>
object IDbSetCache.GetOrAddSharedTypeSet(IDbSetSource source, string entityTypeName, Type clrType)
{
CheckDisposed();

if (_sharedTypeSets == null)
{
_sharedTypeSets = new Dictionary<string, object>();
}

if (!_sharedTypeSets.TryGetValue(entityTypeName, out var set))
{
set = source.CreateSharedTypeSet(this, entityTypeName, clrType);
_sharedTypeSets[entityTypeName] = set;
}

return set;
}

/// <summary>
/// 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.
Expand Down Expand Up @@ -246,6 +269,16 @@ public virtual DbSet<TEntity> Set<TEntity>()
where TEntity : class
=> (DbSet<TEntity>)((IDbSetCache)this).GetOrAddSet(DbContextDependencies.SetSource, typeof(TEntity));

/// <summary>
/// Creates a <see cref="DbSet{TEntity}" /> that can be used to query and save instances of <typeparamref name="TEntity" />.
/// </summary>
/// <typeparam name="TEntity"> The type of entity for which a set should be returned. </typeparam>
/// <param name="entityTypeName"> The name of the entity type as defined by <see cref="IMutableModel.AddSharedTypeEntityType"/>. </param>
/// <returns> A set for the given entity type. </returns>
public virtual DbSet<TEntity> SharedTypeSet<TEntity>(string entityTypeName)
where TEntity : class
=> (DbSet<TEntity>)((IDbSetCache)this).GetOrAddSharedTypeSet(DbContextDependencies.SetSource, entityTypeName, typeof(TEntity));

/// <summary>
/// Creates a <see cref="DbQuery{TQuery}" /> that can be used to query instances of <typeparamref name="TQuery" />.
/// </summary>
Expand Down Expand Up @@ -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)
Expand Down