mirror of
https://github.com/zoriya/EntityFrameworkCore.Projectables.git
synced 2026-05-26 12:19:21 +00:00
Add query root rewrite support
This commit is contained in:
@@ -1,14 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
using EntityFrameworkCore.Projectables.Extensions;
|
using EntityFrameworkCore.Projectables.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore.Query;
|
||||||
|
|
||||||
namespace EntityFrameworkCore.Projectables.Services
|
namespace EntityFrameworkCore.Projectables.Services
|
||||||
{
|
{
|
||||||
@@ -28,10 +25,10 @@ namespace EntityFrameworkCore.Projectables.Services
|
|||||||
if (!_projectableMemberCache.TryGetValue(memberInfo, out reflectedExpression))
|
if (!_projectableMemberCache.TryGetValue(memberInfo, out reflectedExpression))
|
||||||
{
|
{
|
||||||
var projectableAttribute = memberInfo.GetCustomAttribute<ProjectableAttribute>(false);
|
var projectableAttribute = memberInfo.GetCustomAttribute<ProjectableAttribute>(false);
|
||||||
|
|
||||||
reflectedExpression = projectableAttribute is not null
|
reflectedExpression = projectableAttribute is not null
|
||||||
? _resolver.FindGeneratedExpression(memberInfo)
|
? _resolver.FindGeneratedExpression(memberInfo)
|
||||||
: (LambdaExpression?)null;
|
: null;
|
||||||
|
|
||||||
_projectableMemberCache.Add(memberInfo, reflectedExpression);
|
_projectableMemberCache.Add(memberInfo, reflectedExpression);
|
||||||
}
|
}
|
||||||
@@ -50,7 +47,7 @@ namespace EntityFrameworkCore.Projectables.Services
|
|||||||
{
|
{
|
||||||
var parameterExpession = reflectedExpression.Parameters[parameterIndex];
|
var parameterExpession = reflectedExpression.Parameters[parameterIndex];
|
||||||
var mappedArgumentExpression = (parameterIndex, node.Object) switch {
|
var mappedArgumentExpression = (parameterIndex, node.Object) switch {
|
||||||
(0, not null) => node.Object,
|
(0, not null) => node.Object,
|
||||||
(_, not null) => node.Arguments[parameterIndex - 1],
|
(_, not null) => node.Arguments[parameterIndex - 1],
|
||||||
(_, null) => node.Arguments.Count > parameterIndex ? node.Arguments[parameterIndex] : null
|
(_, null) => node.Arguments.Count > parameterIndex ? node.Arguments[parameterIndex] : null
|
||||||
};
|
};
|
||||||
@@ -60,7 +57,7 @@ namespace EntityFrameworkCore.Projectables.Services
|
|||||||
_expressionArgumentReplacer.ParameterArgumentMapping.Add(parameterExpession, mappedArgumentExpression);
|
_expressionArgumentReplacer.ParameterArgumentMapping.Add(parameterExpession, mappedArgumentExpression);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var updatedBody = _expressionArgumentReplacer.Visit(reflectedExpression.Body);
|
var updatedBody = _expressionArgumentReplacer.Visit(reflectedExpression.Body);
|
||||||
_expressionArgumentReplacer.ParameterArgumentMapping.Clear();
|
_expressionArgumentReplacer.ParameterArgumentMapping.Clear();
|
||||||
|
|
||||||
@@ -110,5 +107,64 @@ namespace EntityFrameworkCore.Projectables.Services
|
|||||||
|
|
||||||
return base.VisitMember(node);
|
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;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using EntityFrameworkCore.Projectables.Extensions;
|
using EntityFrameworkCore.Projectables.Extensions;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
|
||||||
|
|
||||||
namespace EntityFrameworkCore.Projectables.Services
|
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