Skip to content

Commit

Permalink
Issue koenbeuk#71
Browse files Browse the repository at this point in the history
  • Loading branch information
Алексей Пыжов committed Mar 27, 2024
1 parent ac67d12 commit cd0319e
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 20 deletions.
2 changes: 1 addition & 1 deletion samples/BasicSample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class User

[Projectable(UseMemberBody = nameof(_FullName))]
public string FullName { get; set; }
private string _FullName => FirstName + " " + LastName;
public string _FullName => FirstName + " " + LastName;

[Projectable(UseMemberBody = nameof(_TotalSpent))]
public double TotalSpent { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ namespace EntityFrameworkCore.Projectables
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class ProjectableAttribute : Attribute
{
public ProjectableAttribute() { }

public ProjectableAttribute(string useMemberBody, object memberBodyParameterValue)
{
UseMemberBody = useMemberBody;
MemberBodyParameterValues = new[] { memberBodyParameterValue };
}
public ProjectableAttribute(string useMemberBody, string memberBodyParameterValue) : this(useMemberBody, (object)memberBodyParameterValue) { }

/// <summary>
/// Get or set how null-conditional operators are handeled
/// </summary>
Expand All @@ -23,5 +32,10 @@ public sealed class ProjectableAttribute : Attribute
/// or null to get it from the current member.
/// </summary>
public string? UseMemberBody { get; set; }

/// <summary>
/// Parameters values for UseMemberBody.
/// </summary>
public object[]? MemberBodyParameterValues { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ public static Expression ExpandQuaryables(this Expression expression)
/// Replaces all calls to properties and methods that are marked with the <C>Projectable</C> attribute with their respective expression tree
/// </summary>
public static Expression ExpandProjectables(this Expression expression)
=> new ProjectableExpressionReplacer(new ProjectionExpressionResolver()).Replace(expression);
=> new ProjectableExpressionReplacer(new ProjectionExpressionResolver()).Visit(expression);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ public TResult ExecuteAsync<TResult>(Expression query, CancellationToken cancell
=> _decoratedQueryCompiler.ExecuteAsync<TResult>(Expand(query), cancellationToken);

Expression Expand(Expression expression)
=> _projectableExpressionReplacer.Replace(expression);
=> _projectableExpressionReplacer.Visit(expression);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ bool TryGetReflectedExpression(MemberInfo memberInfo, [NotNullWhen(true)] out La

reflectedExpression = projectableAttribute is not null
? _resolver.FindGeneratedExpression(memberInfo)
: null;
: (LambdaExpression?)null;

_projectableMemberCache.Add(memberInfo, reflectedExpression);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo

var expression = GetExpressionFromGeneratedType(projectableMemberInfo);

if (expression is null && projectableAttribute.UseMemberBody is not null && projectableAttribute.MemberBodyParameterValues is null)
{
expression = GetExpressionFromGeneratedType(projectableMemberInfo, true, projectableAttribute.UseMemberBody);
}

if (expression is null && projectableAttribute.UseMemberBody is not null)
{
expression = GetExpressionFromMemberBody(projectableMemberInfo, projectableAttribute.UseMemberBody);
expression = GetExpressionFromMemberBody(projectableMemberInfo, projectableAttribute.UseMemberBody, projectableAttribute.MemberBodyParameterValues);
}

if (expression is null)
Expand All @@ -33,17 +38,22 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo

return expression;

static LambdaExpression? GetExpressionFromMemberBody(MemberInfo projectableMemberInfo, string memberName)
static LambdaExpression? GetExpressionFromMemberBody(MemberInfo projectableMemberInfo, string memberName, object[]? memberParameters)
{
var declaringType = projectableMemberInfo.DeclaringType ?? throw new InvalidOperationException("Expected a valid type here");
var exprProperty = declaringType.GetProperty(memberName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var exprProperty = declaringType.GetProperty(memberName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
?? declaringType.BaseType?.GetProperty(memberName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var lambda = exprProperty?.GetValue(null) as LambdaExpression;

if (lambda is not null)
{
if (projectableMemberInfo is PropertyInfo property &&
lambda.Parameters.Count == 1 &&
lambda.Parameters[0].Type == declaringType && lambda.ReturnType == property.PropertyType)
if (projectableMemberInfo is PropertyInfo property && (lambda.Parameters.Count == (1 + memberParameters?.Length ?? 0))
&& (lambda.Parameters[0].Type == declaringType || lambda.Parameters[0].Type == declaringType.BaseType)
&& lambda.ReturnType == property.PropertyType
&& (memberParameters?.Any() != true
|| lambda.Parameters.Skip(1)
.Select((Parameter, Index) => new { Parameter, Index })
.All(p => p.Parameter.Type == memberParameters[p.Index].GetType())))
{
return lambda;
}
Expand All @@ -59,12 +69,14 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo
return null;
}

static LambdaExpression? GetExpressionFromGeneratedType(MemberInfo projectableMemberInfo)
static LambdaExpression? GetExpressionFromGeneratedType(MemberInfo projectableMemberInfo, bool useLocalType = false, string methodName = "Expression")
{
var declaringType = projectableMemberInfo.DeclaringType ?? throw new InvalidOperationException("Expected a valid type here");
var generatedContainingTypeName = ProjectionExpressionClassNameGenerator.GenerateFullName(declaringType.Namespace, declaringType.GetNestedTypePath().Select(x => x.Name), projectableMemberInfo.Name);

var expressionFactoryType = declaringType.Assembly.GetType(generatedContainingTypeName);
var expressionFactoryType = !useLocalType
? declaringType.Assembly.GetType(generatedContainingTypeName)
: declaringType;

if (expressionFactoryType is not null)
{
Expand All @@ -73,7 +85,7 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo
expressionFactoryType = expressionFactoryType.MakeGenericType(declaringType.GenericTypeArguments);
}

var expressionFactoryMethod = expressionFactoryType.GetMethod("Expression", BindingFlags.Static | BindingFlags.NonPublic);
var expressionFactoryMethod = expressionFactoryType.GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic);

var methodGenericArguments = projectableMemberInfo switch {
MethodInfo methodInfo => methodInfo.GetGenericArguments(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public void VisitMember_SimpleProperty()
);
var subject = new ProjectableExpressionReplacer(resolver);

var actual = subject.Replace(input);
var actual = subject.Visit(input);

Assert.Equal(expected.ToString(), actual.ToString());
}
Expand All @@ -77,7 +77,7 @@ public void VisitMember_SimpleMethod()
);
var subject = new ProjectableExpressionReplacer(resolver);

var actual = subject.Replace(input);
var actual = subject.Visit(input);

Assert.Equal(expected.ToString(), actual.ToString());
}
Expand All @@ -93,7 +93,7 @@ public void VisitMember_SimpleMethodWithArguments()
);
var subject = new ProjectableExpressionReplacer(resolver);

var actual = subject.Replace(input);
var actual = subject.Visit(input);

Assert.Equal(expected.ToString(), actual.ToString());
}
Expand All @@ -109,7 +109,7 @@ public void VisitMember_SimpleStatefullProperty()
);
var subject = new ProjectableExpressionReplacer(resolver);

var actual = subject.Replace(input);
var actual = subject.Visit(input);

Assert.Equal(expected.ToString(), actual.ToString());
}
Expand All @@ -125,7 +125,7 @@ public void VisitMember_SimpleStatefullMethod()
);
var subject = new ProjectableExpressionReplacer(resolver);

var actual = subject.Replace(input);
var actual = subject.Visit(input);

Assert.Equal(expected.ToString(), actual.ToString());
}
Expand All @@ -141,7 +141,7 @@ public void VisitMember_SimpleStaticMethod()
);
var subject = new ProjectableExpressionReplacer(resolver);

var actual = subject.Replace(input);
var actual = subject.Visit(input);

Assert.Equal(expected.ToString(), actual.ToString());
}
Expand All @@ -157,7 +157,7 @@ public void VisitMember_SimpleStaticMethodWithArguments()
);
var subject = new ProjectableExpressionReplacer(resolver);

var actual = subject.Replace(input);
var actual = subject.Visit(input);

Assert.Equal(expected.ToString(), actual.ToString());
}
Expand Down

0 comments on commit cd0319e

Please sign in to comment.