mirror of
https://github.com/zoriya/EntityFrameworkCore.Projectables.git
synced 2025-12-18 11:35:12 +00:00
Improved full compat mode perf
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user