From fafe52946530cbf8ed95ed2bd6bb4eb3f314f540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=9F=D1=8B?= =?UTF-8?q?=D0=B6=D0=BE=D0=B2?= Date: Mon, 29 Jan 2024 10:26:20 +0300 Subject: [PATCH 1/8] Merge --- samples/BasicSample/Program.cs | 2 +- .../ProjectableAttribute.cs | 14 +++++++++ .../Extensions/ExpressionExtensions.cs | 2 +- .../Internal/CustomQueryCompiler.cs | 2 +- .../Services/ProjectableExpressionReplacer.cs | 2 +- .../Services/ProjectionExpressionResolver.cs | 30 +++++++++++++------ .../ProjectableExpressionReplacerTests.cs | 14 ++++----- 7 files changed, 46 insertions(+), 20 deletions(-) diff --git a/samples/BasicSample/Program.cs b/samples/BasicSample/Program.cs index d724d99..44659ad 100644 --- a/samples/BasicSample/Program.cs +++ b/samples/BasicSample/Program.cs @@ -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; } diff --git a/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs b/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs index 4c3082b..551f697 100644 --- a/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs +++ b/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs @@ -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) { } + /// /// Get or set how null-conditional operators are handeled /// @@ -23,5 +32,10 @@ public sealed class ProjectableAttribute : Attribute /// or null to get it from the current member. /// public string? UseMemberBody { get; set; } + + /// + /// Parameters values for UseMemberBody. + /// + public object[]? MemberBodyParameterValues { get; set; } } } diff --git a/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs b/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs index 49da220..565e784 100644 --- a/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs +++ b/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs @@ -18,6 +18,6 @@ public static Expression ExpandQuaryables(this Expression expression) /// Replaces all calls to properties and methods that are marked with the Projectable attribute with their respective expression tree /// public static Expression ExpandProjectables(this Expression expression) - => new ProjectableExpressionReplacer(new ProjectionExpressionResolver()).Replace(expression); + => new ProjectableExpressionReplacer(new ProjectionExpressionResolver()).Visit(expression); } } diff --git a/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs index 2a85c9c..5a300a2 100644 --- a/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs +++ b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs @@ -34,6 +34,6 @@ public TResult ExecuteAsync(Expression query, CancellationToken cancell => _decoratedQueryCompiler.ExecuteAsync(Expand(query), cancellationToken); Expression Expand(Expression expression) - => _projectableExpressionReplacer.Replace(expression); + => _projectableExpressionReplacer.Visit(expression); } } diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs index 3eedc6d..6d9db24 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs @@ -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); } diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs index b9062dd..d3e52f7 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs @@ -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) @@ -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; } @@ -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) { @@ -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(), diff --git a/tests/EntityFrameworkCore.Projectables.Tests/Services/ProjectableExpressionReplacerTests.cs b/tests/EntityFrameworkCore.Projectables.Tests/Services/ProjectableExpressionReplacerTests.cs index a9ed5dd..133baeb 100644 --- a/tests/EntityFrameworkCore.Projectables.Tests/Services/ProjectableExpressionReplacerTests.cs +++ b/tests/EntityFrameworkCore.Projectables.Tests/Services/ProjectableExpressionReplacerTests.cs @@ -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()); } @@ -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()); } @@ -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()); } @@ -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()); } @@ -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()); } @@ -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()); } @@ -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()); } From cd0319ec3fc724249062eb61c47b388da8236127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=9F=D1=8B?= =?UTF-8?q?=D0=B6=D0=BE=D0=B2?= Date: Mon, 29 Jan 2024 10:26:20 +0300 Subject: [PATCH 2/8] Issue #71 --- samples/BasicSample/Program.cs | 2 +- .../ProjectableAttribute.cs | 14 +++++++++ .../Extensions/ExpressionExtensions.cs | 2 +- .../Internal/CustomQueryCompiler.cs | 2 +- .../Services/ProjectableExpressionReplacer.cs | 2 +- .../Services/ProjectionExpressionResolver.cs | 30 +++++++++++++------ .../ProjectableExpressionReplacerTests.cs | 14 ++++----- 7 files changed, 46 insertions(+), 20 deletions(-) diff --git a/samples/BasicSample/Program.cs b/samples/BasicSample/Program.cs index d724d99..44659ad 100644 --- a/samples/BasicSample/Program.cs +++ b/samples/BasicSample/Program.cs @@ -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; } diff --git a/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs b/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs index 4c3082b..551f697 100644 --- a/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs +++ b/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs @@ -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) { } + /// /// Get or set how null-conditional operators are handeled /// @@ -23,5 +32,10 @@ public sealed class ProjectableAttribute : Attribute /// or null to get it from the current member. /// public string? UseMemberBody { get; set; } + + /// + /// Parameters values for UseMemberBody. + /// + public object[]? MemberBodyParameterValues { get; set; } } } diff --git a/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs b/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs index 49da220..565e784 100644 --- a/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs +++ b/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs @@ -18,6 +18,6 @@ public static Expression ExpandQuaryables(this Expression expression) /// Replaces all calls to properties and methods that are marked with the Projectable attribute with their respective expression tree /// public static Expression ExpandProjectables(this Expression expression) - => new ProjectableExpressionReplacer(new ProjectionExpressionResolver()).Replace(expression); + => new ProjectableExpressionReplacer(new ProjectionExpressionResolver()).Visit(expression); } } diff --git a/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs index 2a85c9c..5a300a2 100644 --- a/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs +++ b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs @@ -34,6 +34,6 @@ public TResult ExecuteAsync(Expression query, CancellationToken cancell => _decoratedQueryCompiler.ExecuteAsync(Expand(query), cancellationToken); Expression Expand(Expression expression) - => _projectableExpressionReplacer.Replace(expression); + => _projectableExpressionReplacer.Visit(expression); } } diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs index 3eedc6d..6d9db24 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs @@ -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); } diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs index b9062dd..d3e52f7 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs @@ -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) @@ -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; } @@ -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) { @@ -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(), diff --git a/tests/EntityFrameworkCore.Projectables.Tests/Services/ProjectableExpressionReplacerTests.cs b/tests/EntityFrameworkCore.Projectables.Tests/Services/ProjectableExpressionReplacerTests.cs index a9ed5dd..133baeb 100644 --- a/tests/EntityFrameworkCore.Projectables.Tests/Services/ProjectableExpressionReplacerTests.cs +++ b/tests/EntityFrameworkCore.Projectables.Tests/Services/ProjectableExpressionReplacerTests.cs @@ -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()); } @@ -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()); } @@ -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()); } @@ -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()); } @@ -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()); } @@ -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()); } @@ -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()); } From 9246a5f9f62ef704dbfdfd3ca16f579ad193ee7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=9F=D1=8B?= =?UTF-8?q?=D0=B6=D0=BE=D0=B2?= Date: Thu, 28 Mar 2024 17:54:37 +0300 Subject: [PATCH 3/8] fix --- .../Extensions/ExpressionExtensions.cs | 2 +- .../Infrastructure/Internal/CustomQueryCompiler.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs b/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs index 565e784..49da220 100644 --- a/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs +++ b/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs @@ -18,6 +18,6 @@ public static Expression ExpandQuaryables(this Expression expression) /// Replaces all calls to properties and methods that are marked with the Projectable attribute with their respective expression tree /// public static Expression ExpandProjectables(this Expression expression) - => new ProjectableExpressionReplacer(new ProjectionExpressionResolver()).Visit(expression); + => new ProjectableExpressionReplacer(new ProjectionExpressionResolver()).Replace(expression); } } diff --git a/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs index 5a300a2..2a85c9c 100644 --- a/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs +++ b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs @@ -34,6 +34,6 @@ public TResult ExecuteAsync(Expression query, CancellationToken cancell => _decoratedQueryCompiler.ExecuteAsync(Expand(query), cancellationToken); Expression Expand(Expression expression) - => _projectableExpressionReplacer.Visit(expression); + => _projectableExpressionReplacer.Replace(expression); } } From f0c2359739815832e42f24286b280578b6d8c809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=9F=D1=8B?= =?UTF-8?q?=D0=B6=D0=BE=D0=B2?= Date: Fri, 29 Mar 2024 12:41:28 +0300 Subject: [PATCH 4/8] fix --- .../Services/ProjectionExpressionResolver.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs index d3e52f7..3e667e2 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs @@ -47,13 +47,22 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo if (lambda is not null) { - if (projectableMemberInfo is PropertyInfo property && (lambda.Parameters.Count == (1 + memberParameters?.Length ?? 0)) - && (lambda.Parameters[0].Type == declaringType || lambda.Parameters[0].Type == declaringType.BaseType) + + 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()))) + .Select((Parameter, Index) => + new { Parameter, Index }) + .All(p => p.Parameter.Type == + memberParameters[p.Index] + .GetType()))) + + { return lambda; } From 22e3ec07baf8a3a36d29b260236b1973b6f46fbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=9F=D1=8B?= =?UTF-8?q?=D0=B6=D0=BE=D0=B2?= Date: Wed, 3 Apr 2024 09:14:30 +0300 Subject: [PATCH 5/8] issue #71 --- .../ProjectableAttribute.cs | 1 + .../Services/ProjectableExpressionReplacer.cs | 15 +++- .../Services/ProjectionExpressionResolver.cs | 8 +- ...rkCore.Projectables.FunctionalTests.csproj | 1 + .../ExtensionsMethods/ExtensionMethodTests.cs | 7 +- .../Helpers/SampleBodyParamDbContext.cs | 75 +++++++++++++++++++ .../MemberBodyParameterValueTests.cs | 66 ++++++++++++++++ 7 files changed, 159 insertions(+), 14 deletions(-) create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/Helpers/SampleBodyParamDbContext.cs create mode 100644 tests/EntityFrameworkCore.Projectables.FunctionalTests/MemberBodyParameterValueTests.cs diff --git a/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs b/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs index 551f697..3433adc 100644 --- a/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs +++ b/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs @@ -19,6 +19,7 @@ public ProjectableAttribute(string useMemberBody, object memberBodyParameterValu { UseMemberBody = useMemberBody; MemberBodyParameterValues = new[] { memberBodyParameterValue }; + } public ProjectableAttribute(string useMemberBody, string memberBodyParameterValue) : this(useMemberBody, (object)memberBodyParameterValue) { } diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs index 6d9db24..63d9eb2 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs @@ -209,19 +209,30 @@ PropertyInfo property when nodeExpression is not null if (nodeExpression is not null) { _expressionArgumentReplacer.ParameterArgumentMapping.Add(reflectedExpression.Parameters[0], nodeExpression); + if (reflectedExpression.Parameters.Count > 1) + { + var projectableAttribute = nodeMember.GetCustomAttribute(false)!; + foreach (var prm in reflectedExpression.Parameters.Skip(1).Select((Parameter, Index) => new { Parameter, Index })) + { + var value = projectableAttribute!.MemberBodyParameterValues![prm.Index]; + _expressionArgumentReplacer.ParameterArgumentMapping.Add(prm.Parameter, Expression.Constant(value)); + } + } + var updatedBody = _expressionArgumentReplacer.Visit(reflectedExpression.Body); _expressionArgumentReplacer.ParameterArgumentMapping.Clear(); - return base.Visit( + return Visit( updatedBody ); } else { - return base.Visit( + return Visit( reflectedExpression.Body ); } + } return base.VisitMember(node); diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs index 3e667e2..518455d 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs @@ -19,12 +19,10 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo { expression = GetExpressionFromGeneratedType(projectableMemberInfo, true, projectableAttribute.UseMemberBody); } - if (expression is null && projectableAttribute.UseMemberBody is not null) { expression = GetExpressionFromMemberBody(projectableMemberInfo, projectableAttribute.UseMemberBody, projectableAttribute.MemberBodyParameterValues); } - if (expression is null) { var declaringType = projectableMemberInfo.DeclaringType ?? throw new InvalidOperationException("Expected a valid type here"); @@ -41,13 +39,14 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo 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) ?? 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 + (memberParameters?.Length ?? 0))) && (lambda.Parameters[0].Type == declaringType || @@ -74,7 +73,6 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo return lambda; } } - return null; } @@ -82,7 +80,6 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo { 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 = !useLocalType ? declaringType.Assembly.GetType(generatedContainingTypeName) : declaringType; @@ -111,7 +108,6 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo return expressionFactoryMethod.Invoke(null, null) as LambdaExpression ?? throw new InvalidOperationException("Expected lambda"); } } - return null; } } diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/EntityFrameworkCore.Projectables.FunctionalTests.csproj b/tests/EntityFrameworkCore.Projectables.FunctionalTests/EntityFrameworkCore.Projectables.FunctionalTests.csproj index 0c895e5..92c375f 100644 --- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/EntityFrameworkCore.Projectables.FunctionalTests.csproj +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/EntityFrameworkCore.Projectables.FunctionalTests.csproj @@ -8,6 +8,7 @@ + diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionsMethods/ExtensionMethodTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionsMethods/ExtensionMethodTests.cs index 723ceee..0c3849b 100644 --- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionsMethods/ExtensionMethodTests.cs +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/ExtensionsMethods/ExtensionMethodTests.cs @@ -1,12 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Linq; using System.Threading.Tasks; using EntityFrameworkCore.Projectables.FunctionalTests.Helpers; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; -using ScenarioTests; using VerifyXunit; using Xunit; diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/Helpers/SampleBodyParamDbContext.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/Helpers/SampleBodyParamDbContext.cs new file mode 100644 index 0000000..32a08ca --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/Helpers/SampleBodyParamDbContext.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using EntityFrameworkCore.Projectables.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Order = EntityFrameworkCore.Projectables.FunctionalTests.MemberBodyParameterValueTests.Order; +using OrderItem = EntityFrameworkCore.Projectables.FunctionalTests.MemberBodyParameterValueTests.OrderItem; + +namespace EntityFrameworkCore.Projectables.FunctionalTests.Helpers +{ + public class SampleBodyParamDbContext : DbContext + { + readonly CompatibilityMode _compatibilityMode; + + public SampleBodyParamDbContext(CompatibilityMode compatibilityMode = CompatibilityMode.Full) + { + _compatibilityMode = compatibilityMode; + + var _orders = new List() { + new() { + Id = 1, + + }, + new() { + Id = 2, + + } + }; + + var _orders_items = new List() { + new() { + Id = 1, + OrderId = 1, + Name = "Order_1" + }, + new() { + Id = 2, + OrderId = 1, + Name = "Order_2" + }, + new() { + Id = 3, + OrderId = 2, + Name = "Order_3" + }, + new() { + Id = 4, + OrderId = 2, + Name = "Order_4" + }, + }; + + Order!.AddRange(_orders); + OrderItem!.AddRange(_orders_items); + SaveChanges(); + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseInMemoryDatabase("TestDb"); + optionsBuilder.UseProjectables(options => { + options.CompatibilityMode(_compatibilityMode); // Needed by our ComplexModelTests + }); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasKey(x => x.Id); + modelBuilder.Entity().HasKey(x => x.Id); + + } + + public DbSet? Order { get; set; } + public DbSet? OrderItem { get; set; } + } +} diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/MemberBodyParameterValueTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MemberBodyParameterValueTests.cs new file mode 100644 index 0000000..7483a28 --- /dev/null +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MemberBodyParameterValueTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Linq.Expressions; +using EntityFrameworkCore.Projectables.FunctionalTests.Helpers; +using Microsoft.EntityFrameworkCore; +using Xunit; + +namespace EntityFrameworkCore.Projectables.FunctionalTests +{ + public class MemberBodyParameterValueTests + { + public class Order + { + [Key, DatabaseGenerated(DatabaseGeneratedOption.None)] + public int Id { get; set; } + public List OrderItem { get; set; } = new List(); + + [Projectable(nameof(GetOrderItemNameExpression), 1)] + public string FirstOrderPropName => GetOrderItemName(1); + + + [Projectable(UseMemberBody = nameof(GetOrderItemNameInnerExpression))] + public string GetOrderItemName(int id) + => OrderItem.Where(av => av.Id == id) + .Select(av => av.Name) + .FirstOrDefault() ?? throw new ArgumentException("Argument matching identifier not found"); + + private static Expression> GetOrderItemNameInnerExpression() + => (@this, id) => @this.OrderItem + .Where(av => av.Id == id) + .Select(av => av.Name) + .FirstOrDefault() ?? string.Empty; + + public static Expression> GetOrderItemNameExpression + => (order, id) => order.GetOrderItemName(id); + } + + public class OrderItem + { + [Key, DatabaseGenerated(DatabaseGeneratedOption.None)] + public int Id { get; set; } + public int OrderId { get; set; } + public string Name { get; set; } = string.Empty; + } + + [Fact] + public void UseBodyParameterValue() + { + //Arrange + using var dbContext = new SampleBodyParamDbContext(); + + // Act + var query = dbContext + .Set() + .Include(a => a.OrderItem) + .FirstOrDefault(d => d.FirstOrderPropName == "Order_1"); + + // Assert + Assert.NotNull(query); + Assert.True(query!.FirstOrderPropName == "Order_1"); + } + } +} From dd22b546639beccdf9c88cf4ec81e9285670cd19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=9F=D1=8B?= =?UTF-8?q?=D0=B6=D0=BE=D0=B2?= Date: Wed, 3 Apr 2024 10:15:37 +0300 Subject: [PATCH 6/8] add inmemory for test --- Directory.Packages.props | 1 + .../EntityFrameworkCore.Projectables.FunctionalTests.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/Directory.Packages.props b/Directory.Packages.props index d25f1f4..0985e0b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,6 +7,7 @@ + diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/EntityFrameworkCore.Projectables.FunctionalTests.csproj b/tests/EntityFrameworkCore.Projectables.FunctionalTests/EntityFrameworkCore.Projectables.FunctionalTests.csproj index bb5b0c5..c47188e 100644 --- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/EntityFrameworkCore.Projectables.FunctionalTests.csproj +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/EntityFrameworkCore.Projectables.FunctionalTests.csproj @@ -9,6 +9,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + From 0e64451cb82ecde6a8ecae0e112aa62cc382f56a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=9F=D1=8B?= =?UTF-8?q?=D0=B6=D0=BE=D0=B2?= Date: Wed, 3 Apr 2024 10:33:38 +0300 Subject: [PATCH 7/8] fix merge --- Directory.Packages.props | 2 +- .../Extensions/ExpressionExtensions.cs | 2 +- .../Infrastructure/Internal/CustomQueryCompiler.cs | 11 ++--------- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 0985e0b..6e3e005 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,7 +7,7 @@ - + diff --git a/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs b/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs index 565e784..49da220 100644 --- a/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs +++ b/src/EntityFrameworkCore.Projectables/Extensions/ExpressionExtensions.cs @@ -18,6 +18,6 @@ public static Expression ExpandQuaryables(this Expression expression) /// Replaces all calls to properties and methods that are marked with the Projectable attribute with their respective expression tree /// public static Expression ExpandProjectables(this Expression expression) - => new ProjectableExpressionReplacer(new ProjectionExpressionResolver()).Visit(expression); + => new ProjectableExpressionReplacer(new ProjectionExpressionResolver()).Replace(expression); } } diff --git a/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs index 5a300a2..c5f9c5b 100644 --- a/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs +++ b/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Transactions; +using System.Linq.Expressions; using EntityFrameworkCore.Projectables.Services; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.Internal; @@ -34,6 +27,6 @@ public TResult ExecuteAsync(Expression query, CancellationToken cancell => _decoratedQueryCompiler.ExecuteAsync(Expand(query), cancellationToken); Expression Expand(Expression expression) - => _projectableExpressionReplacer.Visit(expression); + => _projectableExpressionReplacer.Replace(expression); } } From 80b153ad31c416f85be4f5530178d59fcdf20074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=9F=D1=8B?= =?UTF-8?q?=D0=B6=D0=BE=D0=B2?= Date: Mon, 22 Apr 2024 15:38:45 +0300 Subject: [PATCH 8/8] fix after rewiev --- .../ProjectableAttribute.cs | 12 +++--------- .../Services/ProjectableExpressionReplacer.cs | 6 +++--- .../Services/ProjectionExpressionResolver.cs | 6 +++--- .../MemberBodyParameterValueTests.cs | 2 +- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs b/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs index 3433adc..ab808fd 100644 --- a/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs +++ b/src/EntityFrameworkCore.Projectables.Abstractions/ProjectableAttribute.cs @@ -13,16 +13,10 @@ 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) + public ProjectableAttribute() { - UseMemberBody = useMemberBody; - MemberBodyParameterValues = new[] { memberBodyParameterValue }; - - } - public ProjectableAttribute(string useMemberBody, string memberBodyParameterValue) : this(useMemberBody, (object)memberBodyParameterValue) { } + } /// /// Get or set how null-conditional operators are handeled /// @@ -37,6 +31,6 @@ public ProjectableAttribute(string useMemberBody, string memberBodyParameterValu /// /// Parameters values for UseMemberBody. /// - public object[]? MemberBodyParameterValues { get; set; } + public object[]? UseMemberBodyArguments { get; set; } } } diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs index 9267acc..33a7438 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectableExpressionReplacer.cs @@ -212,10 +212,10 @@ PropertyInfo property when nodeExpression is not null if (reflectedExpression.Parameters.Count > 1) { var projectableAttribute = nodeMember.GetCustomAttribute(false)!; - foreach (var prm in reflectedExpression.Parameters.Skip(1).Select((Parameter, Index) => new { Parameter, Index })) + foreach (var parameterWithIndex in reflectedExpression.Parameters.Skip(1).Select((Parameter, Index) => new { Parameter, Index })) { - var value = projectableAttribute!.MemberBodyParameterValues![prm.Index]; - _expressionArgumentReplacer.ParameterArgumentMapping.Add(prm.Parameter, Expression.Constant(value)); + var value = projectableAttribute!.UseMemberBodyArguments![parameterWithIndex.Index]; + _expressionArgumentReplacer.ParameterArgumentMapping.Add(parameterWithIndex.Parameter, Expression.Constant(value)); } } diff --git a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs index 518455d..0b36ce4 100644 --- a/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs +++ b/src/EntityFrameworkCore.Projectables/Services/ProjectionExpressionResolver.cs @@ -13,15 +13,15 @@ public LambdaExpression FindGeneratedExpression(MemberInfo projectableMemberInfo var projectableAttribute = projectableMemberInfo.GetCustomAttribute() ?? throw new InvalidOperationException("Expected member to have a Projectable attribute. None found"); - var expression = GetExpressionFromGeneratedType(projectableMemberInfo); + var expression = projectableAttribute.UseMemberBody is null? GetExpressionFromGeneratedType(projectableMemberInfo): null; - if (expression is null && projectableAttribute.UseMemberBody is not null && projectableAttribute.MemberBodyParameterValues is null) + if (expression is null && projectableAttribute.UseMemberBody is not null && projectableAttribute.UseMemberBodyArguments is null) { expression = GetExpressionFromGeneratedType(projectableMemberInfo, true, projectableAttribute.UseMemberBody); } if (expression is null && projectableAttribute.UseMemberBody is not null) { - expression = GetExpressionFromMemberBody(projectableMemberInfo, projectableAttribute.UseMemberBody, projectableAttribute.MemberBodyParameterValues); + expression = GetExpressionFromMemberBody(projectableMemberInfo, projectableAttribute.UseMemberBody, projectableAttribute.UseMemberBodyArguments); } if (expression is null) { diff --git a/tests/EntityFrameworkCore.Projectables.FunctionalTests/MemberBodyParameterValueTests.cs b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MemberBodyParameterValueTests.cs index 7483a28..5982767 100644 --- a/tests/EntityFrameworkCore.Projectables.FunctionalTests/MemberBodyParameterValueTests.cs +++ b/tests/EntityFrameworkCore.Projectables.FunctionalTests/MemberBodyParameterValueTests.cs @@ -18,7 +18,7 @@ public class Order public int Id { get; set; } public List OrderItem { get; set; } = new List(); - [Projectable(nameof(GetOrderItemNameExpression), 1)] + [Projectable(UseMemberBody = nameof(GetOrderItemNameExpression), UseMemberBodyArguments = new object[]{ 1 } )] public string FirstOrderPropName => GetOrderItemName(1);