Skip to content

Commit

Permalink
Allow service properties typed as object (#29919)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajcvickers committed Dec 23, 2022
1 parent 46fc0e3 commit b7e11eb
Show file tree
Hide file tree
Showing 14 changed files with 1,244 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,9 @@ private void Create(
mainBuilder
.Append("var ").Append(variableName).Append(" = ").Append(parameters.TargetName).AppendLine(".AddServiceProperty(")
.IncrementIndent()
.Append(_code.Literal(property.Name));
.Append(_code.Literal(property.Name))
.AppendLine(",")
.Append("typeof(" + property.ClrType.DisplayName(fullName: true, compilable: true) + ")");

PropertyBaseParameters(property, parameters, skipType: true);

Expand Down
16 changes: 16 additions & 0 deletions src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,22 @@ bool CanHaveIndexerProperty(
MemberInfo memberInfo,
bool fromDataAnnotation = false);

/// <summary>
/// Returns an object that can be used to configure the service property with the given member info.
/// If no matching property exists, then a new property will be added.
/// </summary>
/// <param name="serviceType">The type of the service.</param>
/// <param name="memberInfo">The <see cref="PropertyInfo" /> or <see cref="FieldInfo" /> of the property.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns>
/// An object that can be used to configure the property if it exists on the entity type,
/// <see langword="null" /> otherwise.
/// </returns>
IConventionServicePropertyBuilder? ServiceProperty(
Type serviceType,
MemberInfo memberInfo,
bool fromDataAnnotation = false);

/// <summary>
/// Returns a value indicating whether the given service property can be added to this entity type.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ protected virtual void ProcessPropertyAnnotations(
private static RuntimeServiceProperty Create(IServiceProperty property, RuntimeEntityType runtimeEntityType)
=> runtimeEntityType.AddServiceProperty(
property.Name,
property.ClrType,
property.PropertyInfo,
property.FieldInfo,
property.GetPropertyAccessMode());
Expand Down
9 changes: 9 additions & 0 deletions src/EFCore/Metadata/IConventionEntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,15 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas
/// <returns>The newly created service property.</returns>
IConventionServiceProperty AddServiceProperty(MemberInfo memberInfo, bool fromDataAnnotation = false);

/// <summary>
/// Adds a service property to this entity type.
/// </summary>
/// <param name="serviceType">The type of the service.</param>
/// <param name="memberInfo">The <see cref="PropertyInfo" /> or <see cref="FieldInfo" /> of the property to add.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns>The newly created service property.</returns>
IConventionServiceProperty AddServiceProperty(Type serviceType, MemberInfo memberInfo, bool fromDataAnnotation = false);

/// <summary>
/// Gets the service property with a given name.
/// Returns <see langword="null" /> if no property with the given name is defined.
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore/Metadata/IMutableEntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,14 @@ IMutableProperty AddIndexerProperty(
/// <returns>The newly created service property.</returns>
IMutableServiceProperty AddServiceProperty(MemberInfo memberInfo);

/// <summary>
/// Adds a service property to this entity type.
/// </summary>
/// <param name="serviceType">The type of the service.</param>
/// <param name="memberInfo">The <see cref="PropertyInfo" /> or <see cref="FieldInfo" /> of the property to add.</param>
/// <returns>The newly created service property.</returns>
IMutableServiceProperty AddServiceProperty(Type serviceType, MemberInfo memberInfo);

/// <summary>
/// Gets the service property with a given name.
/// Returns <see langword="null" /> if no property with the given name is defined.
Expand Down
29 changes: 27 additions & 2 deletions src/EFCore/Metadata/Internal/EntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2793,6 +2793,7 @@ public virtual IReadOnlyList<IProperty> ValueGeneratingProperties
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual ServiceProperty AddServiceProperty(
Type serviceType,
MemberInfo memberInfo,
// ReSharper disable once MethodOverloadWithOptionalParameter
ConfigurationSource configurationSource)
Expand All @@ -2816,6 +2817,7 @@ public virtual ServiceProperty AddServiceProperty(
name,
memberInfo as PropertyInfo,
memberInfo as FieldInfo,
serviceType,
this,
configurationSource);

Expand Down Expand Up @@ -5145,7 +5147,17 @@ IEnumerable<IProperty> IEntityType.GetValueGeneratingProperties()
/// </summary>
[DebuggerStepThrough]
IMutableServiceProperty IMutableEntityType.AddServiceProperty(MemberInfo memberInfo)
=> AddServiceProperty(memberInfo, ConfigurationSource.Explicit);
=> AddServiceProperty(memberInfo.GetMemberType(), memberInfo, ConfigurationSource.Explicit);

/// <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>
[DebuggerStepThrough]
IMutableServiceProperty IMutableEntityType.AddServiceProperty(Type serviceType, MemberInfo memberInfo)
=> AddServiceProperty(serviceType, memberInfo, ConfigurationSource.Explicit);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -5155,7 +5167,20 @@ IMutableServiceProperty IMutableEntityType.AddServiceProperty(MemberInfo memberI
/// </summary>
[DebuggerStepThrough]
IConventionServiceProperty IConventionEntityType.AddServiceProperty(MemberInfo memberInfo, bool fromDataAnnotation)
=> AddServiceProperty(memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
=> AddServiceProperty(
memberInfo.GetMemberType(), memberInfo,
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <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>
[DebuggerStepThrough]
IConventionServiceProperty IConventionEntityType.AddServiceProperty(Type serviceType, MemberInfo memberInfo, bool fromDataAnnotation)
=> AddServiceProperty(
serviceType, memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
25 changes: 24 additions & 1 deletion src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,18 @@ public virtual IMutableNavigationBase Navigation(string navigationName)
public virtual InternalServicePropertyBuilder? ServiceProperty(
MemberInfo memberInfo,
ConfigurationSource? configurationSource)
=> ServiceProperty(memberInfo.GetMemberType(), memberInfo, configurationSource);

/// <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 InternalServicePropertyBuilder? ServiceProperty(
Type serviceType,
MemberInfo memberInfo,
ConfigurationSource? configurationSource)
{
var propertyName = memberInfo.GetSimpleMemberName();
List<ServiceProperty>? propertiesToDetach = null;
Expand Down Expand Up @@ -999,7 +1011,7 @@ public virtual IMutableNavigationBase Navigation(string navigationName)
}
}

builder = Metadata.AddServiceProperty(memberInfo, configurationSource.Value).Builder;
builder = Metadata.AddServiceProperty(serviceType, memberInfo, configurationSource.Value).Builder;

if (detachedProperties != null)
{
Expand Down Expand Up @@ -5287,6 +5299,17 @@ IConventionEntityTypeBuilder IConventionEntityTypeBuilder.RemoveUnusedImplicitPr
IConventionServicePropertyBuilder? IConventionEntityTypeBuilder.ServiceProperty(MemberInfo memberInfo, bool fromDataAnnotation)
=> ServiceProperty(memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <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>
[DebuggerStepThrough]
IConventionServicePropertyBuilder? IConventionEntityTypeBuilder.ServiceProperty(
Type serviceType, MemberInfo memberInfo, bool fromDataAnnotation)
=> ServiceProperty(serviceType, memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <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
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public virtual bool CanSetParameterBinding(
public virtual InternalServicePropertyBuilder? Attach(InternalEntityTypeBuilder entityTypeBuilder)
{
var newPropertyBuilder = entityTypeBuilder.ServiceProperty(
Metadata.GetIdentifyingMemberInfo()!, Metadata.GetConfigurationSource());
Metadata.ClrType, Metadata.GetIdentifyingMemberInfo()!, Metadata.GetConfigurationSource());
if (newPropertyBuilder == null)
{
return null;
Expand Down
3 changes: 2 additions & 1 deletion src/EFCore/Metadata/Internal/ServiceProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ public ServiceProperty(
string name,
PropertyInfo? propertyInfo,
FieldInfo? fieldInfo,
Type serviceType,
EntityType declaringEntityType,
ConfigurationSource configurationSource)
: base(name, propertyInfo, fieldInfo, configurationSource)
{
DeclaringEntityType = declaringEntityType;
ClrType = (propertyInfo?.PropertyType ?? fieldInfo?.FieldType)!;
ClrType = serviceType;

_builder = new InternalServicePropertyBuilder(this, declaringEntityType.Model.Builder);
}
Expand Down
18 changes: 18 additions & 0 deletions src/EFCore/Metadata/RuntimeEntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -715,11 +715,29 @@ public virtual RuntimeServiceProperty AddServiceProperty(
PropertyInfo? propertyInfo = null,
FieldInfo? fieldInfo = null,
PropertyAccessMode propertyAccessMode = Internal.Model.DefaultPropertyAccessMode)
=> AddServiceProperty(name, (propertyInfo?.PropertyType ?? fieldInfo?.FieldType)!, propertyInfo, fieldInfo, propertyAccessMode);

/// <summary>
/// Adds a service property to this entity type.
/// </summary>
/// <param name="name">The name of the property to add.</param>
/// <param name="serviceType">The type of the service.</param>
/// <param name="propertyInfo">The corresponding CLR property or <see langword="null" /> for a shadow property.</param>
/// <param name="fieldInfo">The corresponding CLR field or <see langword="null" /> for a shadow property.</param>
/// <param name="propertyAccessMode">The <see cref="PropertyAccessMode" /> used for this property.</param>
/// <returns>The newly created service property.</returns>
public virtual RuntimeServiceProperty AddServiceProperty(
string name,
Type serviceType,
PropertyInfo? propertyInfo = null,
FieldInfo? fieldInfo = null,
PropertyAccessMode propertyAccessMode = Internal.Model.DefaultPropertyAccessMode)
{
var serviceProperty = new RuntimeServiceProperty(
name,
propertyInfo,
fieldInfo,
serviceType,
this,
propertyAccessMode);

Expand Down
3 changes: 2 additions & 1 deletion src/EFCore/Metadata/RuntimeServiceProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ public RuntimeServiceProperty(
string name,
PropertyInfo? propertyInfo,
FieldInfo? fieldInfo,
Type serviceType,
RuntimeEntityType declaringEntityType,
PropertyAccessMode propertyAccessMode)
: base(name, propertyInfo, fieldInfo, propertyAccessMode)
{
Check.NotNull(declaringEntityType, nameof(declaringEntityType));

DeclaringEntityType = declaringEntityType;
ClrType = (propertyInfo?.PropertyType ?? fieldInfo?.FieldType)!;
ClrType = serviceType;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,17 @@ public void Manual_lazy_loading()
assertModel: model =>
{
var lazyConstructorEntity = model.FindEntityType(typeof(LazyConstructorEntity));
var lazyParameterBinding = lazyConstructorEntity.ConstructorBinding.ParameterBindings.Single();
var lazyParameterBinding = lazyConstructorEntity!.ConstructorBinding!.ParameterBindings.Single();
Assert.Equal(typeof(ILazyLoader), lazyParameterBinding.ParameterType);
var lazyPropertyEntity = model.FindEntityType(typeof(LazyPropertyEntity));
var lazyServiceProperty = lazyPropertyEntity.GetServiceProperties().Single();
var lazyServiceProperty = lazyPropertyEntity!.GetServiceProperties().Single();
Assert.Equal(typeof(ILazyLoader), lazyServiceProperty.ClrType);
var lazyPropertyDelegateEntity = model.FindEntityType(typeof(LazyPropertyDelegateEntity));
Assert.Equal(2, lazyPropertyDelegateEntity!.GetServiceProperties().Count());
Assert.Contains(lazyPropertyDelegateEntity!.GetServiceProperties(), p => p.ClrType == typeof(ILazyLoader));
Assert.Contains(lazyPropertyDelegateEntity!.GetServiceProperties(), p => p.ClrType == typeof(Action<object, string>));
});

public class LazyLoadingContext : ContextBase
Expand All @@ -190,6 +196,18 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
base.OnModelCreating(modelBuilder);

modelBuilder.Entity<LazyConstructorEntity>();

modelBuilder.Entity<LazyPropertyDelegateEntity>(
b =>
{
var serviceProperty = (ServiceProperty)b.Metadata.AddServiceProperty(
typeof(ILazyLoader),
typeof(LazyPropertyDelegateEntity).GetAnyProperty("LoaderState")!);
serviceProperty.SetParameterBinding(
new DependencyInjectionParameterBinding(typeof(object), typeof(ILazyLoader), serviceProperty),
ConfigurationSource.Explicit);
});
}
}

Expand All @@ -205,6 +223,7 @@ public LazyConstructorEntity(ILazyLoader loader)
public int Id { get; set; }

public LazyPropertyEntity LazyPropertyEntity { get; set; }
public LazyPropertyDelegateEntity LazyPropertyDelegateEntity { get; set; }
}

public class LazyPropertyEntity
Expand All @@ -217,6 +236,17 @@ public class LazyPropertyEntity
public LazyConstructorEntity LazyConstructorEntity { get; set; }
}

public class LazyPropertyDelegateEntity
{
public object LoaderState { get; set; }
private Action<object, string> LazyLoader { get; set; }

public int Id { get; set; }
public int LazyConstructorEntityId { get; set; }

public LazyConstructorEntity LazyConstructorEntity { get; set; }
}

[ConditionalFact]
public void Lazy_loading_proxies()
=> Test(
Expand Down Expand Up @@ -1302,6 +1332,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba
var context = runtimeEntityType.AddServiceProperty(
""Context"",
typeof(Microsoft.EntityFrameworkCore.DbContext),
propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty(""Context"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly));
var key = runtimeEntityType.AddKey(
Expand Down Expand Up @@ -1430,6 +1461,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba
var context = runtimeEntityType.AddServiceProperty(
""Context"",
typeof(Microsoft.EntityFrameworkCore.DbContext),
propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty(""Context"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly));
var key = runtimeEntityType.AddKey(
Expand Down
Loading

0 comments on commit b7e11eb

Please sign in to comment.