mirror of
https://github.com/zoriya/EntityFrameworkCore.Projectables.git
synced 2025-12-06 05:56:10 +00:00
Handle cases like .First(where) and .Sum
This commit is contained in:
@@ -134,11 +134,31 @@ namespace BasicSample
|
|||||||
{
|
{
|
||||||
Console.WriteLine($"User name: {u.FullName}");
|
Console.WriteLine($"User name: {u.FullName}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var u in dbContext.Users.ToList())
|
||||||
|
{
|
||||||
|
Console.WriteLine($"User name: {u.FullName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var u in dbContext.Users.OrderBy(x => x.FullName))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"User name: {u.FullName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
foreach (var u in dbContext.Users.Where(x => x.TotalSpent >= 1))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"User name: {u.FullName}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var result = dbContext.Users.FirstOrDefault();
|
var result = dbContext.Users.FirstOrDefault();
|
||||||
Console.WriteLine($"Our first user {result.FullName} has spent {result.TotalSpent}");
|
Console.WriteLine($"Our first user {result.FullName} has spent {result.TotalSpent}");
|
||||||
|
|
||||||
|
result = dbContext.Users.FirstOrDefault(x => x.TotalSpent > 1);
|
||||||
|
Console.WriteLine($"Our first user {result.FullName} has spent {result.TotalSpent}");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections;
|
||||||
|
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;
|
||||||
@@ -17,9 +18,26 @@ namespace EntityFrameworkCore.Projectables.Services
|
|||||||
private bool _disableRootRewrite;
|
private bool _disableRootRewrite;
|
||||||
private IEntityType? _entityType;
|
private IEntityType? _entityType;
|
||||||
|
|
||||||
|
private readonly MethodInfo _select;
|
||||||
|
private readonly MethodInfo _where;
|
||||||
|
|
||||||
public ProjectableExpressionReplacer(IProjectionExpressionResolver projectionExpressionResolver)
|
public ProjectableExpressionReplacer(IProjectionExpressionResolver projectionExpressionResolver)
|
||||||
{
|
{
|
||||||
_resolver = projectionExpressionResolver;
|
_resolver = projectionExpressionResolver;
|
||||||
|
_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>
|
||||||
|
);
|
||||||
|
_where = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
|
||||||
|
.Where(x => x.Name == nameof(Queryable.Where))
|
||||||
|
.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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TryGetReflectedExpression(MemberInfo memberInfo, [NotNullWhen(true)] out LambdaExpression? reflectedExpression)
|
bool TryGetReflectedExpression(MemberInfo memberInfo, [NotNullWhen(true)] out LambdaExpression? reflectedExpression)
|
||||||
@@ -45,6 +63,7 @@ namespace EntityFrameworkCore.Projectables.Services
|
|||||||
|
|
||||||
if (_disableRootRewrite)
|
if (_disableRootRewrite)
|
||||||
{
|
{
|
||||||
|
// This boolean is enabled when a "Select" is encountered
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,10 +72,62 @@ namespace EntityFrameworkCore.Projectables.Services
|
|||||||
// Probably a First() or ToList()
|
// Probably a First() or ToList()
|
||||||
case MethodCallExpression { Arguments.Count: > 0, Object: null } call when _entityType != null:
|
case MethodCallExpression { Arguments.Count: > 0, Object: null } call when _entityType != null:
|
||||||
{
|
{
|
||||||
|
// if return type != IQueryable {
|
||||||
|
// if return type is IEnuberable {
|
||||||
|
// // case of a ToList()
|
||||||
|
// return (ret.arg[0]).Select(...).ToList() or the other method
|
||||||
|
// } else {
|
||||||
|
// // case of a Max()
|
||||||
|
// return ret;
|
||||||
|
// }
|
||||||
|
// } else if retrun type == entitytype {
|
||||||
|
// // case of a first()
|
||||||
|
// return obj.MyMap(x => new Obj {});
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
if (call.Method.ReturnType.IsAssignableTo(typeof(IQueryable)))
|
||||||
|
{
|
||||||
|
// Generic case where the return type is still a IQueryable<T>
|
||||||
|
return _AddProjectableSelect(call, _entityType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (call.Method.ReturnType == _entityType.ClrType)
|
||||||
|
{
|
||||||
|
// case of a .First(), .SingleAsync()
|
||||||
|
if (call.Arguments.Count != 1 && true /* Add && arg.count == 1 exist */)
|
||||||
|
{
|
||||||
|
// .First(x => whereCondition), since we need to add a select after the last condition but
|
||||||
|
// before the query become executed by EF (before the .First()), we rewrite the .First(where)
|
||||||
|
// as .Where(where).Select(x => ...).First()
|
||||||
|
|
||||||
|
var where = Expression.Call(null, _where.MakeGenericMethod(_entityType.ClrType), call.Arguments);
|
||||||
|
// The call instance is based on the wrong polymorphied method.
|
||||||
|
var first = call.Method.DeclaringType?.GetMethods()
|
||||||
|
.FirstOrDefault(x => x.Name == call.Method.Name && x.GetParameters().Length == 1);
|
||||||
|
if (first == null)
|
||||||
|
{
|
||||||
|
// Unknown case that should not happen.
|
||||||
|
return call;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Expression.Call(null, first.MakeGenericMethod(_entityType.ClrType), _AddProjectableSelect(where, _entityType));
|
||||||
|
}
|
||||||
|
|
||||||
|
// .First() without arguments is the same case as bellow so we let it fallthrough
|
||||||
|
}
|
||||||
|
else if (!call.Method.ReturnType.IsAssignableTo(typeof(IEnumerable)))
|
||||||
|
{
|
||||||
|
// case of something like a .Max(), .Sum()
|
||||||
|
return call;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return type is IEnumerable<EntityType> or EntityType (in case of fallthrough from a .First())
|
||||||
|
|
||||||
|
// case of something like .ToList(), .ToArrayAsync()
|
||||||
var self = _AddProjectableSelect(call.Arguments.First(), _entityType);
|
var self = _AddProjectableSelect(call.Arguments.First(), _entityType);
|
||||||
return call.Update(null, call.Arguments.Skip(1).Prepend(self));
|
return call.Update(null, call.Arguments.Skip(1).Prepend(self));
|
||||||
}
|
}
|
||||||
// Probably a foreach call
|
|
||||||
case QueryRootExpression root:
|
case QueryRootExpression root:
|
||||||
return _AddProjectableSelect(root, root.EntityType);
|
return _AddProjectableSelect(root, root.EntityType);
|
||||||
default:
|
default:
|
||||||
@@ -170,14 +241,7 @@ namespace EntityFrameworkCore.Projectables.Services
|
|||||||
.Where(x => projectableProperties.All(y => x.Name != y.Name && x.Name != $"<{y.Name}>k__BackingField"));
|
.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 })
|
// Replace db.Entities to db.Entities.Select(x => new Entity { Property1 = x.Property1, Rewritted = rewrittedProperty })
|
||||||
var select = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
|
var select = _select.MakeGenericMethod(entityType.ClrType, entityType.ClrType);
|
||||||
.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(entityType.ClrType, entityType.ClrType);
|
|
||||||
var xParam = Expression.Parameter(entityType.ClrType);
|
var xParam = Expression.Parameter(entityType.ClrType);
|
||||||
return Expression.Call(
|
return Expression.Call(
|
||||||
null,
|
null,
|
||||||
|
|||||||
Reference in New Issue
Block a user