Skip to content

Commit c07e516

Browse files
committed
Make parameter binding API public
This allows others to build proxies without using internal code. Fixes #14554 #15252 Internal code is still being used for conventions.
1 parent a5210e3 commit c07e516

File tree

47 files changed

+1045
-947
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1045
-947
lines changed

src/EFCore.Proxies/Proxies/Internal/ProxiesConventionSetCustomizer.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
using JetBrains.Annotations;
55
using Microsoft.EntityFrameworkCore.Diagnostics;
66
using Microsoft.EntityFrameworkCore.Infrastructure;
7+
using Microsoft.EntityFrameworkCore.Metadata;
78
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
89
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
9-
using Microsoft.EntityFrameworkCore.Metadata.Internal;
1010
using Microsoft.Extensions.DependencyInjection;
1111

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

3637
/// <summary>
3738
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -43,12 +44,14 @@ public ProxiesConventionSetCustomizer(
4344
[NotNull] IDbContextOptions options,
4445
[NotNull] IConstructorBindingFactory constructorBindingFactory,
4546
[NotNull] IProxyFactory proxyFactory,
46-
[NotNull] IDiagnosticsLogger<DbLoggerCategory.Model> logger)
47+
[NotNull] IDiagnosticsLogger<DbLoggerCategory.Model> logger,
48+
[NotNull] LazyLoaderParameterBindingFactoryDependencies lazyLoaderParameterBindingFactoryDependencies)
4749
{
4850
_options = options;
4951
_constructorBindingFactory = constructorBindingFactory;
5052
_proxyFactory = proxyFactory;
5153
_logger = logger;
54+
_lazyLoaderParameterBindingFactoryDependencies = lazyLoaderParameterBindingFactoryDependencies;
5255
}
5356

5457
/// <summary>
@@ -61,6 +64,7 @@ public virtual ConventionSet ModifyConventions(ConventionSet conventionSet)
6164
{
6265
conventionSet.ModelBuiltConventions.Add(
6366
new ProxyBindingRewriter(
67+
_lazyLoaderParameterBindingFactoryDependencies,
6468
_proxyFactory,
6569
_constructorBindingFactory,
6670
_logger,

src/EFCore.Proxies/Proxies/Internal/ProxiesOptionsExtension.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using Microsoft.EntityFrameworkCore.Infrastructure;
1010
using Microsoft.EntityFrameworkCore.Internal;
1111
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
12-
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
1312
using Microsoft.Extensions.DependencyInjection;
1413

1514
namespace Microsoft.EntityFrameworkCore.Proxies.Internal

src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ private static readonly PropertyInfo _lazyLoaderProperty
3030
= typeof(IProxyLazyLoader).GetProperty(nameof(IProxyLazyLoader.LazyLoader));
3131

3232
private readonly ConstructorBindingConvention _directBindingConvention;
33+
private readonly LazyLoaderParameterBindingFactoryDependencies _lazyLoaderParameterBindingFactoryDependencies;
3334
private readonly IProxyFactory _proxyFactory;
3435
private readonly ProxiesOptionsExtension _options;
3536

@@ -40,12 +41,14 @@ private static readonly PropertyInfo _lazyLoaderProperty
4041
/// doing so can result in application failures when updating to a new Entity Framework Core release.
4142
/// </summary>
4243
public ProxyBindingRewriter(
44+
[NotNull] LazyLoaderParameterBindingFactoryDependencies lazyLoaderParameterBindingFactoryDependencies,
4345
[NotNull] IProxyFactory proxyFactory,
4446
[NotNull] IConstructorBindingFactory bindingFactory,
4547
[NotNull] IDiagnosticsLogger<DbLoggerCategory.Model> logger,
4648
[CanBeNull] ProxiesOptionsExtension options)
4749
{
4850
_directBindingConvention = new ConstructorBindingConvention(bindingFactory, logger);
51+
_lazyLoaderParameterBindingFactoryDependencies = lazyLoaderParameterBindingFactoryDependencies;
4952
_proxyFactory = proxyFactory;
5053
_options = options;
5154
}
@@ -83,7 +86,7 @@ public virtual InternalModelBuilder Apply(InternalModelBuilder modelBuilder)
8386
{
8487
serviceProperty = entityType.AddServiceProperty(_lazyLoaderProperty, ConfigurationSource.Convention);
8588
serviceProperty.SetParameterBinding(
86-
(ServiceParameterBinding)new LazyLoaderParameterBindingFactory().Bind(
89+
(ServiceParameterBinding)new LazyLoaderParameterBindingFactory(_lazyLoaderParameterBindingFactoryDependencies).Bind(
8790
entityType,
8891
typeof(ILazyLoader),
8992
nameof(IProxyLazyLoader.LazyLoader)));
@@ -104,7 +107,7 @@ public virtual InternalModelBuilder Apply(InternalModelBuilder modelBuilder)
104107
new List<ParameterBinding>
105108
{
106109
new EntityTypeParameterBinding(),
107-
new DefaultServiceParameterBinding(typeof(ILazyLoader), typeof(ILazyLoader), serviceProperty),
110+
new DependencyInjectionParameterBinding(typeof(ILazyLoader), typeof(ILazyLoader), serviceProperty),
108111
new ObjectArrayParameterBinding(binding.ParameterBindings)
109112
},
110113
proxyType);

src/EFCore.Proxies/Proxies/Internal/ProxyFactory.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using Castle.DynamicProxy;
66
using Microsoft.EntityFrameworkCore.Diagnostics;
77
using Microsoft.EntityFrameworkCore.Infrastructure;
8-
using Microsoft.EntityFrameworkCore.Internal;
98
using Microsoft.EntityFrameworkCore.Metadata;
109

1110
namespace Microsoft.EntityFrameworkCore.Proxies.Internal

src/EFCore.Proxies/ProxiesServiceCollectionExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using JetBrains.Annotations;
55
using Microsoft.EntityFrameworkCore.Infrastructure;
66
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
7-
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
87
using Microsoft.EntityFrameworkCore.Proxies.Internal;
98
using Microsoft.EntityFrameworkCore.Utilities;
109

src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices()
315315
.TryAddSingleton<DiagnosticSource>(new DiagnosticListener(DbLoggerCategory.Name));
316316

317317
ServiceCollectionMap.GetInfrastructure()
318+
.AddDependencySingleton<LazyLoaderParameterBindingFactoryDependencies>()
318319
.AddDependencySingleton<DatabaseProviderDependencies>()
319320
.AddDependencySingleton<ResultOperatorHandlerDependencies>()
320321
.AddDependencySingleton<ModelSourceDependencies>()
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq.Expressions;
7+
using JetBrains.Annotations;
8+
using Microsoft.EntityFrameworkCore.Utilities;
9+
10+
namespace Microsoft.EntityFrameworkCore.Metadata
11+
{
12+
/// <summary>
13+
/// Defines how to create an entity instance through the binding of EF model properties to, for
14+
/// example, constructor parameters or parameters of a factory method.
15+
/// </summary>
16+
public abstract class ConstructorBinding
17+
{
18+
/// <summary>
19+
/// Creates a new <see cref="ConstructorBinding" /> instance.
20+
/// </summary>
21+
/// <param name="parameterBindings"> The parameter bindings to use. </param>
22+
protected ConstructorBinding(
23+
[NotNull] IReadOnlyList<ParameterBinding> parameterBindings)
24+
{
25+
Check.NotNull(parameterBindings, nameof(parameterBindings));
26+
27+
ParameterBindings = parameterBindings;
28+
}
29+
30+
/// <summary>
31+
/// Creates an expression tree that represents creating an entity instance from the given binding
32+
/// information. For example, this might be a <see cref="NewExpression" /> to call a constructor,
33+
/// or a <see cref="MethodCallExpression" /> to call a factory method.
34+
/// </summary>
35+
/// <param name="bindingInfo"> Information needed to create the expression. </param>
36+
/// <returns> The expression tree. </returns>
37+
public abstract Expression CreateConstructorExpression(ParameterBindingInfo bindingInfo);
38+
39+
/// <summary>
40+
/// The collection of <see cref="ParameterBinding" /> instances used.
41+
/// </summary>
42+
public virtual IReadOnlyList<ParameterBinding> ParameterBindings { get; }
43+
44+
/// <summary>
45+
/// The type that will be created from the expression tree created for this binding.
46+
/// </summary>
47+
public abstract Type RuntimeType { get; }
48+
}
49+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Linq.Expressions;
6+
using JetBrains.Annotations;
7+
using Microsoft.EntityFrameworkCore.Storage;
8+
using Microsoft.EntityFrameworkCore.Utilities;
9+
10+
namespace Microsoft.EntityFrameworkCore.Metadata
11+
{
12+
/// <summary>
13+
/// Describes the binding of a <see cref="DbContext"/>, which may or may not also have and associated
14+
/// <see cref="IServiceProperty" />, to a parameter in a constructor, factory method, or similar.
15+
/// </summary>
16+
public class ContextParameterBinding : ServiceParameterBinding
17+
{
18+
/// <summary>
19+
/// Creates a new <see cref="ServiceParameterBinding" /> instance for the given service type.
20+
/// </summary>
21+
/// <param name="contextType"> The <see cref="DbContext"/> CLR type. </param>
22+
/// <param name="serviceProperty"> The associated <see cref="IServiceProperty" />, or null. </param>
23+
public ContextParameterBinding(
24+
[NotNull] Type contextType,
25+
[CanBeNull] IPropertyBase serviceProperty = null)
26+
: base(contextType, contextType, serviceProperty)
27+
{
28+
}
29+
30+
/// <summary>
31+
/// Creates an expression tree representing the binding of the value of a property from a
32+
/// materialization expression to a parameter of the constructor, factory method, etc.
33+
/// </summary>
34+
/// <param name="materializationExpression"> The expression representing the materialization context. </param>
35+
/// <param name="entityTypeExpression"> The expression representing the <see cref="IEntityType" /> constant. </param>
36+
/// <returns> The expression tree. </returns>
37+
public override Expression BindToParameter(
38+
Expression materializationExpression,
39+
Expression entityTypeExpression)
40+
{
41+
Check.NotNull(materializationExpression, nameof(materializationExpression));
42+
Check.NotNull(entityTypeExpression, nameof(entityTypeExpression));
43+
44+
var propertyExpression
45+
= Expression.Property(
46+
materializationExpression,
47+
MaterializationContext.ContextProperty);
48+
49+
return ServiceType != typeof(DbContext)
50+
? (Expression)Expression.TypeAs(propertyExpression, ServiceType)
51+
: propertyExpression;
52+
}
53+
}
54+
}

src/EFCore/Metadata/CoreAnnotationNames.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public static class CoreAnnotationNames
5151
public const string OwnedTypes = "OwnedTypes";
5252

5353
/// <summary>
54-
/// Indicates the <see cref="Internal.ConstructorBinding" /> to use for the annotated item.
54+
/// Indicates the <see cref="Metadata.ConstructorBinding" /> to use for the annotated item.
5555
/// </summary>
5656
public const string ConstructorBinding = "ConstructorBinding";
5757

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Linq.Expressions;
8+
using System.Reflection;
9+
using JetBrains.Annotations;
10+
using Microsoft.EntityFrameworkCore.Utilities;
11+
12+
namespace Microsoft.EntityFrameworkCore.Metadata
13+
{
14+
/// <summary>
15+
/// Describes the binding from a method on an EF internal dependency injection service, which may or may not
16+
/// also have and associated <see cref="IServiceProperty" />, to a parameter in a constructor,
17+
/// factory method, or similar.
18+
/// </summary>
19+
public class DependencyInjectionMethodParameterBinding : DependencyInjectionParameterBinding
20+
{
21+
/// <summary>
22+
/// Creates a new <see cref="DependencyInjectionParameterBinding" /> instance for the given method
23+
/// of the given service type.
24+
/// </summary>
25+
/// <param name="parameterType"> The parameter CLR type. </param>
26+
/// <param name="serviceType"> The service CLR types, as resolved from dependency injection </param>
27+
/// <param name="method"> The method of the service to bind to. </param>
28+
/// <param name="serviceProperty"> The associated <see cref="IServiceProperty" />, or null. </param>
29+
public DependencyInjectionMethodParameterBinding(
30+
[NotNull] Type parameterType,
31+
[NotNull] Type serviceType,
32+
[NotNull] MethodInfo method,
33+
[CanBeNull] IPropertyBase serviceProperty = null)
34+
: base(parameterType, serviceType, serviceProperty)
35+
{
36+
Check.NotNull(method, nameof(method));
37+
38+
Method = method;
39+
}
40+
41+
/// <summary>
42+
/// The method being bound to, as defined on the dependency injection service interface.
43+
/// </summary>
44+
public virtual MethodInfo Method { get; }
45+
46+
/// <summary>
47+
/// Creates an expression tree representing the binding of the value of a property from a
48+
/// materialization expression to a parameter of the constructor, factory method, etc.
49+
/// </summary>
50+
/// <param name="materializationExpression"> The expression representing the materialization context. </param>
51+
/// <param name="entityTypeExpression"> The expression representing the <see cref="IEntityType" /> constant. </param>
52+
/// <returns> The expression tree. </returns>
53+
public override Expression BindToParameter(
54+
Expression materializationExpression,
55+
Expression entityTypeExpression)
56+
{
57+
Check.NotNull(materializationExpression, nameof(materializationExpression));
58+
Check.NotNull(entityTypeExpression, nameof(entityTypeExpression));
59+
60+
var parameters = Method.GetParameters().Select(
61+
(p, i) => Expression.Parameter(p.ParameterType, "param" + i)).ToArray();
62+
63+
var serviceVariable = Expression.Variable(ServiceType, "service");
64+
var delegateVariable = Expression.Variable(ParameterType, "delegate");
65+
66+
return Expression.Block(
67+
new[]
68+
{
69+
serviceVariable, delegateVariable
70+
},
71+
new List<Expression>
72+
{
73+
Expression.Assign(
74+
serviceVariable,
75+
base.BindToParameter(materializationExpression, entityTypeExpression)),
76+
Expression.Assign(
77+
delegateVariable,
78+
Expression.Condition(
79+
Expression.ReferenceEqual(serviceVariable, Expression.Constant(null)),
80+
Expression.Constant(null, ParameterType),
81+
Expression.Lambda(
82+
Expression.Call(
83+
serviceVariable,
84+
Method,
85+
parameters),
86+
parameters))),
87+
delegateVariable
88+
});
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)