Improved full compat mode perf

This commit is contained in:
Koen
2022-10-13 00:40:27 +01:00
parent dc0eb04842
commit df1ed1759a
7 changed files with 90 additions and 39 deletions

View File

@@ -7,7 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.0" /> <PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0" />
</ItemGroup> </ItemGroup>

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;
using EntityFrameworkCore.Projectables.Services;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;
namespace EntityFrameworkCore.Projectables.Infrastructure.Internal
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "Needed")]
public sealed class CustomQueryCompiler : IQueryCompiler
{
readonly IQueryCompiler _decoratedQueryCompiler;
readonly ProjectableExpressionReplacer _projectableExpressionReplacer;
public CustomQueryCompiler(IQueryCompiler decoratedQueryCompiler)
{
_decoratedQueryCompiler = decoratedQueryCompiler;
_projectableExpressionReplacer = new ProjectableExpressionReplacer(new ProjectionExpressionResolver());
}
public Func<QueryContext, TResult> CreateCompiledAsyncQuery<TResult>(Expression query)
=> _decoratedQueryCompiler.CreateCompiledAsyncQuery<TResult>(Expand(query));
public Func<QueryContext, TResult> CreateCompiledQuery<TResult>(Expression query)
=> _decoratedQueryCompiler.CreateCompiledQuery<TResult>(Expand(query));
public TResult Execute<TResult>(Expression query)
=> _decoratedQueryCompiler.Execute<TResult>(Expand(query));
public TResult ExecuteAsync<TResult>(Expression query, CancellationToken cancellationToken)
=> _decoratedQueryCompiler.ExecuteAsync<TResult>(Expand(query), cancellationToken);
Expression Expand(Expression expression)
=> _projectableExpressionReplacer.Visit(expression);
}
}

View File

@@ -9,21 +9,11 @@ namespace EntityFrameworkCore.Projectables.Services
{ {
public sealed class ExpressionArgumentReplacer : ExpressionVisitor public sealed class ExpressionArgumentReplacer : ExpressionVisitor
{ {
readonly IEnumerable<(ParameterExpression parameter, Expression argument)>? _parameterArgumentMapping; public Dictionary<ParameterExpression, Expression> ParameterArgumentMapping { get; } = new();
public ExpressionArgumentReplacer(IEnumerable<(ParameterExpression, Expression)>? parameterArgumentMapping = null)
{
_parameterArgumentMapping = parameterArgumentMapping;
}
protected override Expression VisitParameter(ParameterExpression node) protected override Expression VisitParameter(ParameterExpression node)
{ {
var mappedArgument = _parameterArgumentMapping? if (ParameterArgumentMapping.TryGetValue(node, out var mappedArgument))
.Where(x => x.parameter == node)
.Select(x => x.argument)
.FirstOrDefault();
if (mappedArgument is not null)
{ {
return mappedArgument; return mappedArgument;
} }

View File

@@ -1,43 +1,63 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq;
namespace EntityFrameworkCore.Projectables.Services namespace EntityFrameworkCore.Projectables.Services
{ {
public sealed class ProjectableExpressionReplacer : ExpressionVisitor public sealed class ProjectableExpressionReplacer : ExpressionVisitor
{ {
readonly IProjectionExpressionResolver _resolver; readonly IProjectionExpressionResolver _resolver;
readonly ExpressionArgumentReplacer _expressionArgumentReplacer = new();
readonly Dictionary<MemberInfo, LambdaExpression?> _projectableMemberCache = new();
public ProjectableExpressionReplacer(IProjectionExpressionResolver projectionExpressionResolver) public ProjectableExpressionReplacer(IProjectionExpressionResolver projectionExpressionResolver)
{ {
_resolver = projectionExpressionResolver; _resolver = projectionExpressionResolver;
} }
bool TryGetReflectedExpression(MemberInfo memberInfo, [NotNullWhen(true)] out LambdaExpression? reflectedExpression)
{
if (!_projectableMemberCache.TryGetValue(memberInfo, out reflectedExpression))
{
var projectableAttribute = memberInfo.GetCustomAttribute<ProjectableAttribute>(false);
reflectedExpression = projectableAttribute is not null
? _resolver.FindGeneratedExpression(memberInfo)
: (LambdaExpression?)null;
_projectableMemberCache.Add(memberInfo, reflectedExpression);
}
return reflectedExpression is not null;
}
protected override Expression VisitMethodCall(MethodCallExpression node) protected override Expression VisitMethodCall(MethodCallExpression node)
{ {
if (node.Method.GetCustomAttributes(false).OfType<ProjectableAttribute>().Any()) if (TryGetReflectedExpression(node.Method, out var reflectedExpression))
{ {
var reflectedExpression = _resolver.FindGeneratedExpression(node.Method); for (var parameterIndex = 0; parameterIndex < reflectedExpression.Parameters.Count; parameterIndex++)
var parameterArgumentMapping = node.Object is not null
? Enumerable.Repeat((reflectedExpression.Parameters[0], node.Object), 1)
: Enumerable.Empty<(ParameterExpression, Expression)>();
if (reflectedExpression.Parameters.Count > 0)
{ {
parameterArgumentMapping = parameterArgumentMapping.Concat( var parameterExpession = reflectedExpression.Parameters[parameterIndex];
node.Object is not null var mappedArgumentExpression = (parameterIndex, node.Object) switch {
? reflectedExpression.Parameters.Skip(1).Zip(node.Arguments, (parameter, argument) => (parameter, argument)) (0, not null) => node.Object,
: reflectedExpression.Parameters.Zip(node.Arguments, (parameter, argument) => (parameter, argument)) (_, not null) => node.Arguments[parameterIndex - 1],
); (_, null) => node.Arguments[parameterIndex]
} };
_expressionArgumentReplacer.ParameterArgumentMapping.Add(parameterExpession, mappedArgumentExpression);
}
var updatedBody = _expressionArgumentReplacer.Visit(reflectedExpression.Body);
_expressionArgumentReplacer.ParameterArgumentMapping.Clear();
var expressionArgumentReplacer = new ExpressionArgumentReplacer(parameterArgumentMapping);
return Visit( return Visit(
expressionArgumentReplacer.Visit(reflectedExpression.Body) updatedBody
); );
} }
@@ -46,17 +66,16 @@ namespace EntityFrameworkCore.Projectables.Services
protected override Expression VisitMember(MemberExpression node) protected override Expression VisitMember(MemberExpression node)
{ {
if (node.Member.GetCustomAttributes(false).OfType<ProjectableAttribute>().Any()) if (TryGetReflectedExpression(node.Member, out var reflectedExpression))
{ {
var reflectedExpression = _resolver.FindGeneratedExpression(node.Member);
if (node.Expression is not null) if (node.Expression is not null)
{ {
var expressionArgumentReplacer = new ExpressionArgumentReplacer( _expressionArgumentReplacer.ParameterArgumentMapping.Add(reflectedExpression.Parameters[0], node.Expression);
Enumerable.Repeat((reflectedExpression.Parameters[0], node.Expression), 1) var updatedBody = _expressionArgumentReplacer.Visit(reflectedExpression.Body);
); _expressionArgumentReplacer.ParameterArgumentMapping.Clear();
return Visit( return Visit(
expressionArgumentReplacer.Visit(reflectedExpression.Body) updatedBody
); );
} }
else else

View File

@@ -52,7 +52,7 @@ namespace EntityFrameworkCore.Projectables.FunctionalTests.ExtensionMethods
[Fact] [Fact]
public Task ExtensionMethodAcceptingDbContext() public Task ExtensionMethodAcceptingDbContext()
{ {
using var dbContext = new SampleDbContext<Entity>(Infrastructure.CompatibilityMode.Full); using var dbContext = new SampleDbContext<Entity>();
var sampleQuery = dbContext.Set<Entity>() var sampleQuery = dbContext.Set<Entity>()
.Select(x => dbContext.Set<Entity>().Where(y => y.Id > x.Id).FirstOrDefault()); .Select(x => dbContext.Set<Entity>().Where(y => y.Id > x.Id).FirstOrDefault());

View File

@@ -28,7 +28,6 @@ namespace EntityFrameworkCore.Projectables.FunctionalTests.Generics
return Verifier.Verify(query.ToQueryString()); return Verifier.Verify(query.ToQueryString());
} }
[Fact] [Fact]
public void MultipleInvocations() public void MultipleInvocations()
{ {

View File

@@ -16,7 +16,11 @@ namespace EntityFrameworkCore.Projectables.Tests.Services
{ {
var parameter = Expression.Parameter(typeof(int)); var parameter = Expression.Parameter(typeof(int));
var argument = Expression.Constant(1); var argument = Expression.Constant(1);
var subject = new ExpressionArgumentReplacer(new[] { (parameter, (Expression)argument) }); var subject = new ExpressionArgumentReplacer() {
ParameterArgumentMapping = {
{ parameter, argument }
}
};
var result = subject.Visit(parameter); var result = subject.Visit(parameter);