mirror of
https://github.com/zoriya/EntityFrameworkCore.Projectables.git
synced 2025-12-06 05:56:10 +00:00
Improved full compat mode perf
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.0" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0" />
|
||||
</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
|
||||
{
|
||||
readonly IEnumerable<(ParameterExpression parameter, Expression argument)>? _parameterArgumentMapping;
|
||||
|
||||
public ExpressionArgumentReplacer(IEnumerable<(ParameterExpression, Expression)>? parameterArgumentMapping = null)
|
||||
{
|
||||
_parameterArgumentMapping = parameterArgumentMapping;
|
||||
}
|
||||
public Dictionary<ParameterExpression, Expression> ParameterArgumentMapping { get; } = new();
|
||||
|
||||
protected override Expression VisitParameter(ParameterExpression node)
|
||||
{
|
||||
var mappedArgument = _parameterArgumentMapping?
|
||||
.Where(x => x.parameter == node)
|
||||
.Select(x => x.argument)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (mappedArgument is not null)
|
||||
if (ParameterArgumentMapping.TryGetValue(node, out var mappedArgument))
|
||||
{
|
||||
return mappedArgument;
|
||||
}
|
||||
|
||||
@@ -1,43 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace EntityFrameworkCore.Projectables.Services
|
||||
{
|
||||
public sealed class ProjectableExpressionReplacer : ExpressionVisitor
|
||||
{
|
||||
readonly IProjectionExpressionResolver _resolver;
|
||||
readonly ExpressionArgumentReplacer _expressionArgumentReplacer = new();
|
||||
readonly Dictionary<MemberInfo, LambdaExpression?> _projectableMemberCache = new();
|
||||
|
||||
public ProjectableExpressionReplacer(IProjectionExpressionResolver 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)
|
||||
{
|
||||
if (node.Method.GetCustomAttributes(false).OfType<ProjectableAttribute>().Any())
|
||||
if (TryGetReflectedExpression(node.Method, out var reflectedExpression))
|
||||
{
|
||||
var reflectedExpression = _resolver.FindGeneratedExpression(node.Method);
|
||||
|
||||
var parameterArgumentMapping = node.Object is not null
|
||||
? Enumerable.Repeat((reflectedExpression.Parameters[0], node.Object), 1)
|
||||
: Enumerable.Empty<(ParameterExpression, Expression)>();
|
||||
|
||||
if (reflectedExpression.Parameters.Count > 0)
|
||||
for (var parameterIndex = 0; parameterIndex < reflectedExpression.Parameters.Count; parameterIndex++)
|
||||
{
|
||||
parameterArgumentMapping = parameterArgumentMapping.Concat(
|
||||
node.Object is not null
|
||||
? reflectedExpression.Parameters.Skip(1).Zip(node.Arguments, (parameter, argument) => (parameter, argument))
|
||||
: reflectedExpression.Parameters.Zip(node.Arguments, (parameter, argument) => (parameter, argument))
|
||||
);
|
||||
}
|
||||
var parameterExpession = reflectedExpression.Parameters[parameterIndex];
|
||||
var mappedArgumentExpression = (parameterIndex, node.Object) switch {
|
||||
(0, not null) => node.Object,
|
||||
(_, 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(
|
||||
expressionArgumentReplacer.Visit(reflectedExpression.Body)
|
||||
updatedBody
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,17 +66,16 @@ namespace EntityFrameworkCore.Projectables.Services
|
||||
|
||||
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)
|
||||
{
|
||||
var expressionArgumentReplacer = new ExpressionArgumentReplacer(
|
||||
Enumerable.Repeat((reflectedExpression.Parameters[0], node.Expression), 1)
|
||||
);
|
||||
_expressionArgumentReplacer.ParameterArgumentMapping.Add(reflectedExpression.Parameters[0], node.Expression);
|
||||
var updatedBody = _expressionArgumentReplacer.Visit(reflectedExpression.Body);
|
||||
_expressionArgumentReplacer.ParameterArgumentMapping.Clear();
|
||||
|
||||
return Visit(
|
||||
expressionArgumentReplacer.Visit(reflectedExpression.Body)
|
||||
updatedBody
|
||||
);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace EntityFrameworkCore.Projectables.FunctionalTests.ExtensionMethods
|
||||
[Fact]
|
||||
public Task ExtensionMethodAcceptingDbContext()
|
||||
{
|
||||
using var dbContext = new SampleDbContext<Entity>(Infrastructure.CompatibilityMode.Full);
|
||||
using var dbContext = new SampleDbContext<Entity>();
|
||||
|
||||
var sampleQuery = dbContext.Set<Entity>()
|
||||
.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());
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void MultipleInvocations()
|
||||
{
|
||||
|
||||
@@ -16,7 +16,11 @@ namespace EntityFrameworkCore.Projectables.Tests.Services
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(int));
|
||||
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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user