mirror of
https://github.com/zoriya/EntityFrameworkCore.Projectables.git
synced 2026-05-28 04:41:30 +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
|
||||||
{
|
{
|
||||||
@@ -31,7 +28,7 @@ namespace EntityFrameworkCore.Projectables.Services
|
|||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -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