mirror of
https://github.com/zoriya/EntityFrameworkCore.Projectables.git
synced 2026-05-25 20:09:00 +00:00
Add query root rewrite support
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
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;
|
||||
using EntityFrameworkCore.Projectables.Extensions;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
|
||||
namespace EntityFrameworkCore.Projectables.Services
|
||||
{
|
||||
@@ -28,10 +25,10 @@ namespace EntityFrameworkCore.Projectables.Services
|
||||
if (!_projectableMemberCache.TryGetValue(memberInfo, out reflectedExpression))
|
||||
{
|
||||
var projectableAttribute = memberInfo.GetCustomAttribute<ProjectableAttribute>(false);
|
||||
|
||||
reflectedExpression = projectableAttribute is not null
|
||||
|
||||
reflectedExpression = projectableAttribute is not null
|
||||
? _resolver.FindGeneratedExpression(memberInfo)
|
||||
: (LambdaExpression?)null;
|
||||
: null;
|
||||
|
||||
_projectableMemberCache.Add(memberInfo, reflectedExpression);
|
||||
}
|
||||
@@ -50,7 +47,7 @@ namespace EntityFrameworkCore.Projectables.Services
|
||||
{
|
||||
var parameterExpession = reflectedExpression.Parameters[parameterIndex];
|
||||
var mappedArgumentExpression = (parameterIndex, node.Object) switch {
|
||||
(0, not null) => node.Object,
|
||||
(0, not null) => node.Object,
|
||||
(_, not null) => node.Arguments[parameterIndex - 1],
|
||||
(_, null) => node.Arguments.Count > parameterIndex ? node.Arguments[parameterIndex] : null
|
||||
};
|
||||
@@ -60,7 +57,7 @@ namespace EntityFrameworkCore.Projectables.Services
|
||||
_expressionArgumentReplacer.ParameterArgumentMapping.Add(parameterExpession, mappedArgumentExpression);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var updatedBody = _expressionArgumentReplacer.Visit(reflectedExpression.Body);
|
||||
_expressionArgumentReplacer.ParameterArgumentMapping.Clear();
|
||||
|
||||
@@ -110,5 +107,64 @@ namespace EntityFrameworkCore.Projectables.Services
|
||||
|
||||
return base.VisitMember(node);
|
||||
}
|
||||
|
||||
protected override Expression VisitExtension(Expression node)
|
||||
{
|
||||
if (node is not QueryRootExpression root)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
var projectableProperties = root.EntityType.ClrType.GetProperties()
|
||||
.Where(x => x.IsDefined(typeof(ProjectableAttribute), false))
|
||||
.Where(x => x.CanWrite)
|
||||
.ToList();
|
||||
|
||||
if (!projectableProperties.Any())
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
var properties = root.EntityType.GetProperties()
|
||||
.Where(x => !x.IsShadowProperty())
|
||||
.Select(x => x.GetMemberInfo(false, false))
|
||||
// Remove projectable properties from the ef properties. Since properties returned here for auto
|
||||
// properties (like `public string Test {get;set;}`) are generated fields, we also need to take them into account.
|
||||
.Where(x => projectableProperties.All(y => x.Name != y.Name && x.Name != $"<{y.Name}>k__BackingField"));
|
||||
|
||||
// Replace db.Entities to db.Entities.Select(x => new Entity { Property1 = x.Property1, Rewritted = rewrittedProperty })
|
||||
var select = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
|
||||
.Where(x => x.Name == nameof(Queryable.Select))
|
||||
.First(x =>
|
||||
x.GetParameters().Last().ParameterType // Expression<Func<T, Ret>>
|
||||
.GetGenericArguments().First() // Func<T, Ret>
|
||||
.GetGenericArguments().Length == 2 // Separate between Func<T, Ret> and Func<T, int, Ret>
|
||||
)
|
||||
.MakeGenericMethod(root.EntityType.ClrType, root.EntityType.ClrType);
|
||||
var xParam = Expression.Parameter(root.EntityType.ClrType);
|
||||
return Expression.Call(
|
||||
null,
|
||||
select,
|
||||
node,
|
||||
Expression.Lambda(
|
||||
Expression.MemberInit(
|
||||
Expression.New(root.EntityType.ClrType),
|
||||
properties.Select(x => Expression.Bind(x, Expression.MakeMemberAccess(xParam, x)))
|
||||
.Concat(projectableProperties
|
||||
.Select(x => Expression.Bind(x, _ReplaceParam(_resolver.FindGeneratedExpression(x), xParam)))
|
||||
)
|
||||
),
|
||||
xParam
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private Expression _ReplaceParam(LambdaExpression lambda, ParameterExpression para)
|
||||
{
|
||||
_expressionArgumentReplacer.ParameterArgumentMapping.Add(lambda.Parameters[0], para);
|
||||
var updatedBody = _expressionArgumentReplacer.Visit(lambda.Body);
|
||||
_expressionArgumentReplacer.ParameterArgumentMapping.Clear();
|
||||
return updatedBody;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using EntityFrameworkCore.Projectables.Extensions;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||||
|
||||
namespace EntityFrameworkCore.Projectables.Services
|
||||
{
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
SELECT [e].[Id], [e].[Id] * 5
|
||||
FROM [Entity] AS [e]
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Threading.Tasks;
|
||||
using EntityFrameworkCore.Projectables.FunctionalTests.Helpers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using VerifyXunit;
|
||||
using Xunit;
|
||||
|
||||
namespace EntityFrameworkCore.Projectables.FunctionalTests
|
||||
{
|
||||
[UsesVerify]
|
||||
public class QueryRootTests
|
||||
{
|
||||
public record Entity
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[Projectable(UseMemberBody = nameof(Computed2))]
|
||||
public int Computed1 => Id;
|
||||
|
||||
private int Computed2 => Id * 2;
|
||||
|
||||
[Projectable(UseMemberBody = nameof(_ComputedWithBaking))]
|
||||
[NotMapped]
|
||||
public int ComputedWithBacking { get; set; }
|
||||
|
||||
private int _ComputedWithBaking => Id * 5;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task UseMemberPropertyQueryRootExpression()
|
||||
{
|
||||
using var dbContext = new SampleDbContext<Entity>();
|
||||
|
||||
var query = dbContext.Set<Entity>();
|
||||
|
||||
return Verifier.Verify(query.ToQueryString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user