Skip to content

Commit 91b6759

Browse files
Improve invocation support (#188)
1 parent babeb13 commit 91b6759

3 files changed

Lines changed: 125 additions & 8 deletions

File tree

src/LinqKit.Core/ExpressionExpander.cs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,7 @@ protected LambdaExpression EvaluateTarget(Expression target)
3434
}
3535
}
3636

37-
var lambda = target.EvaluateExpression() as LambdaExpression;
38-
39-
if (lambda == null)
40-
{
41-
throw new InvalidOperationException($"Invoke cannot evaluate LambdaExpression from '{target}'. Ensure that your function/property/member returns LambdaExpression");
42-
}
43-
44-
return lambda;
37+
return target.EvaluateExpression() as LambdaExpression;
4538
}
4639

4740
/// <summary>
@@ -53,6 +46,10 @@ protected override Expression VisitInvocation(InvocationExpression iv)
5346
var target = iv.Expression;
5447

5548
var lambda = EvaluateTarget(target);
49+
if (lambda == null)
50+
{
51+
return base.VisitInvocation(iv);
52+
}
5653

5754
var body = ExpressionReplacer.GetBody(lambda, iv.Arguments);
5855

@@ -118,6 +115,11 @@ protected override Expression VisitMethodCall(MethodCallExpression m)
118115
var target = m.Arguments[0];
119116
var lambda = EvaluateTarget(target);
120117

118+
if (lambda == null)
119+
{
120+
throw new InvalidOperationException($"Invoke cannot evaluate LambdaExpression from '{target}'. Ensure that your function/property/member returns LambdaExpression");
121+
}
122+
121123
var replaceVars = new Dictionary<Expression, Expression>();
122124
for (int i = 0; i < lambda.Parameters.Count; i++)
123125
{
@@ -129,6 +131,25 @@ protected override Expression VisitMethodCall(MethodCallExpression m)
129131
return Visit(body);
130132
}
131133

134+
if (m.Method.Name == nameof(Action.Invoke)
135+
&& m.Method.DeclaringType.GetTypeInfo().IsSubclassOf(typeof(Delegate)))
136+
{
137+
var lambda = EvaluateTarget(m.Object);
138+
139+
if (lambda != null)
140+
{
141+
var replaceVars = new Dictionary<Expression, Expression>();
142+
for (int i = 0; i < lambda.Parameters.Count; i++)
143+
{
144+
replaceVars.Add(lambda.Parameters[i], Visit(m.Arguments[i]));
145+
}
146+
147+
var body = ExpressionReplacer.Replace(lambda.Body, replaceVars);
148+
149+
return Visit(body);
150+
}
151+
}
152+
132153
if (GetExpandLambda(m.Method, out var methodLambda))
133154
{
134155
var replaceVars = new Dictionary<Expression, Expression>();

tests/LinqKit.Tests.Net452/ExpandableAttributeTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.ComponentModel.DataAnnotations.Schema;
34
using System.Linq;
45
using System.Linq.Expressions;
56
using Xunit;
@@ -247,4 +248,37 @@ public void ExpandableAttribute_FilteredDetailsNoImpl()
247248
}
248249

249250
}
251+
252+
public class MyEntity
253+
{
254+
public Guid ID { get; set; }
255+
public string Code { get; set; }
256+
public string Description { get; set; }
257+
[NotMapped]
258+
[Expandable(nameof(CodeAndDescriptionExpr))]
259+
public string CodeAndDescription => Code + " / " + Description;
260+
internal static Expression<Func<MyEntity, string>> CodeAndDescriptionExpr() => e => e.Code + " / " + e.Description;
261+
}
262+
263+
public class ExpressionBuilderTest {
264+
265+
[Fact]
266+
public void ExpressionIsAppliedToMappedProperties()
267+
{
268+
var predicate = PredicateBuilder.New<MyEntity>(true);
269+
var keywords = "?";
270+
271+
predicate = predicate.And(x => x.CodeAndDescription.ToLower().Replace("İ", "i").Replace("ı", "i").Contains(keywords));
272+
predicate = predicate.Or(x => MyEntity.CodeAndDescriptionExpr().Invoke(x).ToLower().Contains(keywords));
273+
274+
Assert.Equal(
275+
$"x => (x.CodeAndDescription.ToLower().Replace(\"İ\", \"i\").Replace(\"ı\", \"i\").Contains(value(LinqKit.Tests.Net452.ExpressionBuilderTest+<>c__DisplayClass0_0).keywords) OrElse CodeAndDescriptionExpr().Invoke(x).ToLower().Contains(value(LinqKit.Tests.Net452.ExpressionBuilderTest+<>c__DisplayClass0_0).keywords))",
276+
predicate.ToString());
277+
278+
var expanded = predicate.Expand();
279+
Assert.Equal(
280+
$"x => (((x.Code + \" / \") + x.Description).ToLower().Replace(\"İ\", \"i\").Replace(\"ı\", \"i\").Contains(value({typeof(ExpressionBuilderTest).FullName}+<>c__DisplayClass0_0).keywords) OrElse ((x.Code + \" / \") + x.Description).ToLower().Contains(value({typeof(ExpressionBuilderTest).FullName}+<>c__DisplayClass0_0).keywords))",
281+
expanded.ToString());
282+
}
283+
}
250284
}

tests/LinqKit.Tests.Net452/ExpressionExpanderTests.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,68 @@ public void ExpressionExpander_Expression_Block()
6464
Assert.Equal(lambda.Invoke(42), expandedLambda.Invoke(42));
6565
}
6666

67+
[Fact]
68+
public void ExpressionExpander_Expression_InvokeExpressionRemoved()
69+
{
70+
Expression<Func<object, object>> lambda = o => o;
71+
72+
var expandedLambda = Linq.Expr((object o) => lambda.Invoke(o))
73+
.Expand();
74+
Assert.Equal(ExpressionType.Parameter, expandedLambda.Body.NodeType);
75+
Assert.Equal(lambda.ToString(), expandedLambda.ToString());
76+
Assert.Equal(lambda.Invoke(42), expandedLambda.Invoke(42));
77+
}
78+
79+
[Fact]
80+
public void ExpressionExpander_Expression_CompileAndInvokeExpressionRemoved()
81+
{
82+
Expression<Func<object, object>> lambda = o => o;
83+
84+
var expandedLambda = Linq.Expr((object o) => lambda.Compile()(o))
85+
.Expand();
86+
Assert.Equal(ExpressionType.Parameter, expandedLambda.Body.NodeType);
87+
Assert.Equal(lambda.ToString(), expandedLambda.ToString());
88+
Assert.Equal(lambda.Invoke(42), expandedLambda.Invoke(42));
89+
}
90+
91+
[Fact]
92+
public void ExpressionExpander_Expression_CompileAndInvokeOnExpressionRemoved()
93+
{
94+
Expression<Func<object, object>> lambda = o => o;
95+
96+
var expandedLambda = Linq.Expr((object o) => lambda.Compile().Invoke(o))
97+
.Expand();
98+
Assert.Equal(ExpressionType.Parameter, expandedLambda.Body.NodeType);
99+
Assert.Equal(lambda.ToString(), expandedLambda.ToString());
100+
Assert.Equal(lambda.Invoke(42), expandedLambda.Invoke(42));
101+
}
102+
103+
[Fact]
104+
public void ExpressionExpander_Expression_InvokeDelegate()
105+
{
106+
Func<object, string> func = o => o.ToString();
107+
108+
Expression<Func<object, string>> lambda = o => func(o);
109+
110+
var expandedLambda = Linq.Expr((object o) => lambda.Invoke(o))
111+
.Expand();
112+
Assert.Equal(lambda.ToString(), expandedLambda.ToString());
113+
Assert.Equal(lambda.Invoke(42), expandedLambda.Invoke(42));
114+
}
115+
116+
[Fact]
117+
public void ExpressionExpander_Expression_InvokeOnDelegate()
118+
{
119+
Func<object, string> func = o => o.ToString();
120+
121+
Expression<Func<object, string>> lambda = o => func.Invoke(o);
122+
123+
var expandedLambda = Linq.Expr((object o) => lambda.Invoke(o))
124+
.Expand();
125+
Assert.Equal(lambda.ToString(), expandedLambda.ToString());
126+
Assert.Equal(lambda.Invoke(42), expandedLambda.Invoke(42));
127+
}
128+
67129
[Fact]
68130
public void ExpressionExpander_Expression_Throw()
69131
{

0 commit comments

Comments
 (0)