mirror of
https://github.com/zoriya/EntityFrameworkCore.Projectables.git
synced 2025-12-06 05:56:10 +00:00
Add OnlyOnInclude option for the root rewritter
This commit is contained in:
@@ -21,7 +21,7 @@ namespace BasicSample
|
||||
public string FullName { get; set; }
|
||||
private string _FullName => FirstName + " " + LastName;
|
||||
|
||||
[Projectable(UseMemberBody = nameof(_TotalSpent))]
|
||||
[Projectable(UseMemberBody = nameof(_TotalSpent), OnlyOnInclude = true)]
|
||||
public double TotalSpent { get; set; }
|
||||
private double _TotalSpent => Orders.Sum(x => x.PriceSum);
|
||||
|
||||
@@ -154,10 +154,11 @@ namespace BasicSample
|
||||
}
|
||||
|
||||
{
|
||||
var result = dbContext.Users.FirstOrDefault();
|
||||
Console.WriteLine($"Unloaded total: {dbContext.Users.First().TotalSpent}");
|
||||
var result = dbContext.Users.Include(x => x.TotalSpent).FirstOrDefault();
|
||||
Console.WriteLine($"Our first user {result.FullName} has spent {result.TotalSpent}");
|
||||
|
||||
result = dbContext.Users.FirstOrDefault(x => x.TotalSpent > 1);
|
||||
result = dbContext.Users.Include(x => x.TotalSpent).FirstOrDefault(x => x.TotalSpent > 1);
|
||||
Console.WriteLine($"Our first user {result.FullName} has spent {result.TotalSpent}");
|
||||
|
||||
var spent = dbContext.Users.Sum(x => x.TotalSpent);
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EntityFrameworkCore.Projectables
|
||||
{
|
||||
@@ -23,5 +19,12 @@ namespace EntityFrameworkCore.Projectables
|
||||
/// or null to get it from the current member.
|
||||
/// </summary>
|
||||
public string? UseMemberBody { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> will allow you to request for this property by
|
||||
/// explicitly calling .Include(x => x.Property) on the query,
|
||||
/// <c>false</c> will always consider this query to be included.
|
||||
/// </summary>
|
||||
public bool OnlyOnInclude { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using EntityFrameworkCore.Projectables.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
|
||||
@@ -16,6 +17,7 @@ namespace EntityFrameworkCore.Projectables.Services
|
||||
readonly ExpressionArgumentReplacer _expressionArgumentReplacer = new();
|
||||
readonly Dictionary<MemberInfo, LambdaExpression?> _projectableMemberCache = new();
|
||||
private bool _disableRootRewrite;
|
||||
private List<string> _includedProjections = new();
|
||||
private IEntityType? _entityType;
|
||||
|
||||
private readonly MethodInfo _select;
|
||||
@@ -60,6 +62,7 @@ namespace EntityFrameworkCore.Projectables.Services
|
||||
public Expression? Replace(Expression? node)
|
||||
{
|
||||
_disableRootRewrite = false;
|
||||
_includedProjections.Clear();
|
||||
var ret = Visit(node);
|
||||
|
||||
if (_disableRootRewrite)
|
||||
@@ -138,6 +141,28 @@ namespace EntityFrameworkCore.Projectables.Services
|
||||
|
||||
protected override Expression VisitMethodCall(MethodCallExpression node)
|
||||
{
|
||||
if (node.Method.Name == nameof(EntityFrameworkQueryableExtensions.Include))
|
||||
{
|
||||
var include = node.Arguments[1] switch {
|
||||
ConstantExpression { Value: string str } => str,
|
||||
LambdaExpression { Body: MemberExpression member } => member.Member.Name,
|
||||
UnaryExpression { Operand: LambdaExpression { Body: MemberExpression member } } => member.Member.Name,
|
||||
_ => null
|
||||
};
|
||||
// Only rewrite the include if it includes a projectable property (or if we don't know what's happening).
|
||||
var ret = Visit(node.Arguments[0]);
|
||||
// The visit here is needed because we need the _entityType defined on the query root for the condition below.
|
||||
if (
|
||||
include != null
|
||||
&& _entityType?.ClrType
|
||||
?.GetProperty(include)
|
||||
?.GetCustomAttribute<ProjectableAttribute>() != null)
|
||||
{
|
||||
_includedProjections.Add(include);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace MethodGroup arguments with their reflected expressions.
|
||||
// Note that MethodCallExpression.Update returns the original Expression if argument values have not changed.
|
||||
node = node.Update(node.Object, node.Arguments.Select(arg => arg switch {
|
||||
@@ -212,13 +237,13 @@ namespace EntityFrameworkCore.Projectables.Services
|
||||
var updatedBody = _expressionArgumentReplacer.Visit(reflectedExpression.Body);
|
||||
_expressionArgumentReplacer.ParameterArgumentMapping.Clear();
|
||||
|
||||
return base.Visit(
|
||||
return Visit(
|
||||
updatedBody
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.Visit(
|
||||
return Visit(
|
||||
reflectedExpression.Body
|
||||
);
|
||||
}
|
||||
@@ -243,7 +268,14 @@ namespace EntityFrameworkCore.Projectables.Services
|
||||
private Expression _AddProjectableSelect(Expression node, IEntityType entityType)
|
||||
{
|
||||
var projectableProperties = entityType.ClrType.GetProperties()
|
||||
.Where(x => x.IsDefined(typeof(ProjectableAttribute), false))
|
||||
.Where(x => {
|
||||
var attr = x.GetCustomAttribute<ProjectableAttribute>();
|
||||
if (attr == null)
|
||||
return false;
|
||||
if (attr.OnlyOnInclude)
|
||||
return _includedProjections.Contains(x.Name);
|
||||
return true;
|
||||
})
|
||||
.Where(x => x.CanWrite)
|
||||
.ToList();
|
||||
|
||||
@@ -291,7 +323,7 @@ namespace EntityFrameworkCore.Projectables.Services
|
||||
_expressionArgumentReplacer.ParameterArgumentMapping.Add(lambda.Parameters[0], para);
|
||||
var updatedBody = _expressionArgumentReplacer.Visit(lambda.Body);
|
||||
_expressionArgumentReplacer.ParameterArgumentMapping.Clear();
|
||||
return base.Visit(updatedBody);
|
||||
return Visit(updatedBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user