Skip to content

Commit

Permalink
Make parameter binding API public
Browse files Browse the repository at this point in the history
This allows others to build proxies without using internal code.

Fixes #14554 #15252

Internal code is still being used for conventions.
  • Loading branch information
ajcvickers committed May 8, 2019
1 parent a5210e3 commit c07e516
Show file tree
Hide file tree
Showing 47 changed files with 1,045 additions and 947 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.EntityFrameworkCore.Proxies.Internal
Expand All @@ -32,6 +32,7 @@ public class ProxiesConventionSetCustomizer : IConventionSetCustomizer
private readonly IConstructorBindingFactory _constructorBindingFactory;
private readonly IProxyFactory _proxyFactory;
private readonly IDiagnosticsLogger<DbLoggerCategory.Model> _logger;
private readonly LazyLoaderParameterBindingFactoryDependencies _lazyLoaderParameterBindingFactoryDependencies;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -43,12 +44,14 @@ public ProxiesConventionSetCustomizer(
[NotNull] IDbContextOptions options,
[NotNull] IConstructorBindingFactory constructorBindingFactory,
[NotNull] IProxyFactory proxyFactory,
[NotNull] IDiagnosticsLogger<DbLoggerCategory.Model> logger)
[NotNull] IDiagnosticsLogger<DbLoggerCategory.Model> logger,
[NotNull] LazyLoaderParameterBindingFactoryDependencies lazyLoaderParameterBindingFactoryDependencies)
{
_options = options;
_constructorBindingFactory = constructorBindingFactory;
_proxyFactory = proxyFactory;
_logger = logger;
_lazyLoaderParameterBindingFactoryDependencies = lazyLoaderParameterBindingFactoryDependencies;
}

/// <summary>
Expand All @@ -61,6 +64,7 @@ public virtual ConventionSet ModifyConventions(ConventionSet conventionSet)
{
conventionSet.ModelBuiltConventions.Add(
new ProxyBindingRewriter(
_lazyLoaderParameterBindingFactoryDependencies,
_proxyFactory,
_constructorBindingFactory,
_logger,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.EntityFrameworkCore.Proxies.Internal
Expand Down
7 changes: 5 additions & 2 deletions src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ private static readonly PropertyInfo _lazyLoaderProperty
= typeof(IProxyLazyLoader).GetProperty(nameof(IProxyLazyLoader.LazyLoader));

private readonly ConstructorBindingConvention _directBindingConvention;
private readonly LazyLoaderParameterBindingFactoryDependencies _lazyLoaderParameterBindingFactoryDependencies;
private readonly IProxyFactory _proxyFactory;
private readonly ProxiesOptionsExtension _options;

Expand All @@ -40,12 +41,14 @@ private static readonly PropertyInfo _lazyLoaderProperty
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public ProxyBindingRewriter(
[NotNull] LazyLoaderParameterBindingFactoryDependencies lazyLoaderParameterBindingFactoryDependencies,
[NotNull] IProxyFactory proxyFactory,
[NotNull] IConstructorBindingFactory bindingFactory,
[NotNull] IDiagnosticsLogger<DbLoggerCategory.Model> logger,
[CanBeNull] ProxiesOptionsExtension options)
{
_directBindingConvention = new ConstructorBindingConvention(bindingFactory, logger);
_lazyLoaderParameterBindingFactoryDependencies = lazyLoaderParameterBindingFactoryDependencies;
_proxyFactory = proxyFactory;
_options = options;
}
Expand Down Expand Up @@ -83,7 +86,7 @@ public virtual InternalModelBuilder Apply(InternalModelBuilder modelBuilder)
{
serviceProperty = entityType.AddServiceProperty(_lazyLoaderProperty, ConfigurationSource.Convention);
serviceProperty.SetParameterBinding(
(ServiceParameterBinding)new LazyLoaderParameterBindingFactory().Bind(
(ServiceParameterBinding)new LazyLoaderParameterBindingFactory(_lazyLoaderParameterBindingFactoryDependencies).Bind(
entityType,
typeof(ILazyLoader),
nameof(IProxyLazyLoader.LazyLoader)));
Expand All @@ -104,7 +107,7 @@ public virtual InternalModelBuilder Apply(InternalModelBuilder modelBuilder)
new List<ParameterBinding>
{
new EntityTypeParameterBinding(),
new DefaultServiceParameterBinding(typeof(ILazyLoader), typeof(ILazyLoader), serviceProperty),
new DependencyInjectionParameterBinding(typeof(ILazyLoader), typeof(ILazyLoader), serviceProperty),
new ObjectArrayParameterBinding(binding.ParameterBindings)
},
proxyType);
Expand Down
1 change: 0 additions & 1 deletion src/EFCore.Proxies/Proxies/Internal/ProxyFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Castle.DynamicProxy;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Microsoft.EntityFrameworkCore.Proxies.Internal
Expand Down
1 change: 0 additions & 1 deletion src/EFCore.Proxies/ProxiesServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
using Microsoft.EntityFrameworkCore.Proxies.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices()
.TryAddSingleton<DiagnosticSource>(new DiagnosticListener(DbLoggerCategory.Name));

ServiceCollectionMap.GetInfrastructure()
.AddDependencySingleton<LazyLoaderParameterBindingFactoryDependencies>()
.AddDependencySingleton<DatabaseProviderDependencies>()
.AddDependencySingleton<ResultOperatorHandlerDependencies>()
.AddDependencySingleton<ModelSourceDependencies>()
Expand Down
49 changes: 49 additions & 0 deletions src/EFCore/Metadata/ConstructorBinding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Metadata
{
/// <summary>
/// Defines how to create an entity instance through the binding of EF model properties to, for
/// example, constructor parameters or parameters of a factory method.
/// </summary>
public abstract class ConstructorBinding
{
/// <summary>
/// Creates a new <see cref="ConstructorBinding" /> instance.
/// </summary>
/// <param name="parameterBindings"> The parameter bindings to use. </param>
protected ConstructorBinding(
[NotNull] IReadOnlyList<ParameterBinding> parameterBindings)
{
Check.NotNull(parameterBindings, nameof(parameterBindings));

ParameterBindings = parameterBindings;
}

/// <summary>
/// Creates an expression tree that represents creating an entity instance from the given binding
/// information. For example, this might be a <see cref="NewExpression" /> to call a constructor,
/// or a <see cref="MethodCallExpression" /> to call a factory method.
/// </summary>
/// <param name="bindingInfo"> Information needed to create the expression. </param>
/// <returns> The expression tree. </returns>
public abstract Expression CreateConstructorExpression(ParameterBindingInfo bindingInfo);

/// <summary>
/// The collection of <see cref="ParameterBinding" /> instances used.
/// </summary>
public virtual IReadOnlyList<ParameterBinding> ParameterBindings { get; }

/// <summary>
/// The type that will be created from the expression tree created for this binding.
/// </summary>
public abstract Type RuntimeType { get; }
}
}
54 changes: 54 additions & 0 deletions src/EFCore/Metadata/ContextParameterBinding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Metadata
{
/// <summary>
/// Describes the binding of a <see cref="DbContext"/>, which may or may not also have and associated
/// <see cref="IServiceProperty" />, to a parameter in a constructor, factory method, or similar.
/// </summary>
public class ContextParameterBinding : ServiceParameterBinding
{
/// <summary>
/// Creates a new <see cref="ServiceParameterBinding" /> instance for the given service type.
/// </summary>
/// <param name="contextType"> The <see cref="DbContext"/> CLR type. </param>
/// <param name="serviceProperty"> The associated <see cref="IServiceProperty" />, or null. </param>
public ContextParameterBinding(
[NotNull] Type contextType,
[CanBeNull] IPropertyBase serviceProperty = null)
: base(contextType, contextType, serviceProperty)
{
}

/// <summary>
/// Creates an expression tree representing the binding of the value of a property from a
/// materialization expression to a parameter of the constructor, factory method, etc.
/// </summary>
/// <param name="materializationExpression"> The expression representing the materialization context. </param>
/// <param name="entityTypeExpression"> The expression representing the <see cref="IEntityType" /> constant. </param>
/// <returns> The expression tree. </returns>
public override Expression BindToParameter(
Expression materializationExpression,
Expression entityTypeExpression)
{
Check.NotNull(materializationExpression, nameof(materializationExpression));
Check.NotNull(entityTypeExpression, nameof(entityTypeExpression));

var propertyExpression
= Expression.Property(
materializationExpression,
MaterializationContext.ContextProperty);

return ServiceType != typeof(DbContext)
? (Expression)Expression.TypeAs(propertyExpression, ServiceType)
: propertyExpression;
}
}
}
2 changes: 1 addition & 1 deletion src/EFCore/Metadata/CoreAnnotationNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static class CoreAnnotationNames
public const string OwnedTypes = "OwnedTypes";

/// <summary>
/// Indicates the <see cref="Internal.ConstructorBinding" /> to use for the annotated item.
/// Indicates the <see cref="Metadata.ConstructorBinding" /> to use for the annotated item.
/// </summary>
public const string ConstructorBinding = "ConstructorBinding";

Expand Down
91 changes: 91 additions & 0 deletions src/EFCore/Metadata/DependencyInjectionMethodParameterBinding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// 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.Linq;
using System.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Metadata
{
/// <summary>
/// Describes the binding from a method on an EF internal dependency injection service, which may or may not
/// also have and associated <see cref="IServiceProperty" />, to a parameter in a constructor,
/// factory method, or similar.
/// </summary>
public class DependencyInjectionMethodParameterBinding : DependencyInjectionParameterBinding
{
/// <summary>
/// Creates a new <see cref="DependencyInjectionParameterBinding" /> instance for the given method
/// of the given service type.
/// </summary>
/// <param name="parameterType"> The parameter CLR type. </param>
/// <param name="serviceType"> The service CLR types, as resolved from dependency injection </param>
/// <param name="method"> The method of the service to bind to. </param>
/// <param name="serviceProperty"> The associated <see cref="IServiceProperty" />, or null. </param>
public DependencyInjectionMethodParameterBinding(
[NotNull] Type parameterType,
[NotNull] Type serviceType,
[NotNull] MethodInfo method,
[CanBeNull] IPropertyBase serviceProperty = null)
: base(parameterType, serviceType, serviceProperty)
{
Check.NotNull(method, nameof(method));

Method = method;
}

/// <summary>
/// The method being bound to, as defined on the dependency injection service interface.
/// </summary>
public virtual MethodInfo Method { get; }

/// <summary>
/// Creates an expression tree representing the binding of the value of a property from a
/// materialization expression to a parameter of the constructor, factory method, etc.
/// </summary>
/// <param name="materializationExpression"> The expression representing the materialization context. </param>
/// <param name="entityTypeExpression"> The expression representing the <see cref="IEntityType" /> constant. </param>
/// <returns> The expression tree. </returns>
public override Expression BindToParameter(
Expression materializationExpression,
Expression entityTypeExpression)
{
Check.NotNull(materializationExpression, nameof(materializationExpression));
Check.NotNull(entityTypeExpression, nameof(entityTypeExpression));

var parameters = Method.GetParameters().Select(
(p, i) => Expression.Parameter(p.ParameterType, "param" + i)).ToArray();

var serviceVariable = Expression.Variable(ServiceType, "service");
var delegateVariable = Expression.Variable(ParameterType, "delegate");

return Expression.Block(
new[]
{
serviceVariable, delegateVariable
},
new List<Expression>
{
Expression.Assign(
serviceVariable,
base.BindToParameter(materializationExpression, entityTypeExpression)),
Expression.Assign(
delegateVariable,
Expression.Condition(
Expression.ReferenceEqual(serviceVariable, Expression.Constant(null)),
Expression.Constant(null, ParameterType),
Expression.Lambda(
Expression.Call(
serviceVariable,
Method,
parameters),
parameters))),
delegateVariable
});
}
}
}
62 changes: 62 additions & 0 deletions src/EFCore/Metadata/DependencyInjectionParameterBinding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Metadata
{
/// <summary>
/// Describes the binding from an EF internal dependency injection service, which may or may not
/// also have and associated <see cref="IServiceProperty" />, to a parameter in a constructor,
/// factory method, or similar.
/// </summary>
public class DependencyInjectionParameterBinding : ServiceParameterBinding
{
private static readonly MethodInfo _getServiceMethod
= typeof(InternalAccessorExtensions).GetMethod(nameof(InternalAccessorExtensions.GetService));

/// <summary>
/// Creates a new <see cref="DependencyInjectionParameterBinding" /> instance for the given service type.
/// </summary>
/// <param name="parameterType"> The parameter CLR type. </param>
/// <param name="serviceType"> The service CLR types, as resolved from dependency injection </param>
/// <param name="serviceProperty"> The associated <see cref="IServiceProperty" />, or null. </param>
public DependencyInjectionParameterBinding(
[NotNull] Type parameterType,
[NotNull] Type serviceType,
[CanBeNull] IPropertyBase serviceProperty = null)
: base(parameterType, serviceType, serviceProperty)
{
}

/// <summary>
/// Creates an expression tree representing the binding of the value of a property from a
/// materialization expression to a parameter of the constructor, factory method, etc.
/// </summary>
/// <param name="materializationExpression"> The expression representing the materialization context. </param>
/// <param name="entityTypeExpression"> The expression representing the <see cref="IEntityType" /> constant. </param>
/// <returns> The expression tree. </returns>
public override Expression BindToParameter(
Expression materializationExpression,
Expression entityTypeExpression)
{
Check.NotNull(materializationExpression, nameof(materializationExpression));
Check.NotNull(entityTypeExpression, nameof(entityTypeExpression));

return Expression.Call(
_getServiceMethod.MakeGenericMethod(ServiceType),
Expression.Convert(
Expression.Property(
materializationExpression,
MaterializationContext.ContextProperty),
typeof(IInfrastructure<IServiceProvider>)));
}
}
}
Loading

0 comments on commit c07e516

Please sign in to comment.