I'm building a routine to concatenate several entities in order to filter by a string. I create my expressions using the following code, which works properly because I use the same code to sort.
private void SetExpression<T>(string property, ref Type type, ref ParameterExpression arg, ref Expression expr)
{
string[] props = property.Split('.');
foreach (string prop in props)
{
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
}
I then use 2 more methods to build the tree. I use GenerateConcat to make the concatenation portion.
private Expression<Func<T, string>> GenerateConcat(ParameterExpression parameterExpression, params Expression[] expressions)
{
var parameter = parameterExpression;
var separator = Expression.Constant(" ");
var concatArgs = expressions;
var concatCall = Expression.Call(typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string), typeof(string) }), concatArgs);
return Expression.Lambda<Func<T, string>>(concatCall, parameter);
}
And I use GenerateContains to create the contains method.
private Expression<Func<T, bool>> GenerateContains(Expression<Func<T, string>> member, string value)
{
var containsCall = Expression.Call(member.Body, "Contains", Type.EmptyTypes, Expression.Constant(value));
return Expression.Lambda<Func<T, bool>>(containsCall, member.Parameters);
}
When run, all appears well and my predicate looks like it is formed properly. However, when ToList() is called the following exception is thrown.
System.InvalidOperationException: The parameter 'x' was not bound in the specified LINQ to Entities query expression.
Result StackTrace:
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.ParameterTranslator.TypedTranslate(ExpressionConverter parent, ParameterExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MemberAccessTranslator.TypedTranslate(ExpressionConverter parent, MemberExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MemberAccessTranslator.TypedTranslate(ExpressionConverter parent, MemberExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.StringTranslatorUtil.ConvertToString(ExpressionConverter parent, Expression linqExpression)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.StringTranslatorUtil.<>c__DisplayClass1c.<ConcatArgs>b__1a(Expression arg)
at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.StringTranslatorUtil.ConcatArgs(ExpressionConverter parent, Expression linq, Expression[] linqArgs)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.StringConcatTranslator.Translate(ExpressionConverter parent, MethodCallExpression call)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateFunctionIntoLike(MethodCallExpression call, Boolean insertPercentAtStart, Boolean insertPercentAtEnd, Func`5 defaultTranslator)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.StringContainsTranslator.Translate(ExpressionConverter parent, MethodCallExpression call)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.BinaryTranslator.TypedTranslate(ExpressionConverter parent, BinaryExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.BinaryTranslator.TypedTranslate(ExpressionConverter parent, BinaryExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input, DbExpressionBinding& binding)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, DbExpression& source, DbExpressionBinding& sourceBinding, DbExpression& lambda)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.SequenceMethodTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, SequenceMethod sequenceMethod)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.Convert()
at System.Data.Entity.Core.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClass7.<GetResults>b__6()
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClass7.<GetResults>b__5()
at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation)
at System.Data.Entity.Core.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<System.Collections.Generic.IEnumerable<T>.GetEnumerator>b__0()
at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Triton.Repository.Test.UnitTest1.ComplexTypeTest() in C:\BitBucket\triton-mvc-5\Triton\Triton.Repository.Test\UnitTest1.cs:line 56
Result Message:
Test method Triton.Repository.Test.UnitTest1.ComplexTypeTest threw exception:
System.InvalidOperationException: The parameter 'x' was not bound in the specified LINQ to Entities query expression.
I'm guessing but the problem is how I'm building the Concat portion (GenerateConcat), but I can't seem to figure out what's wrong. Am I missing anything perhaps with respect to the ParameterExpression? I made sure it is the same for all the Expressions. Thanks in advance.
Edit 1: Here's the code that creates the expression
I use the Initialize block to create the expression based off of the property name. Then I use the Append method to get the expression from the other properties. My error was when I created the sub properties I didn't properly assign the ParameterExpression. So I overloaded Intialize and added a ParamerterExpression parameter to keep the expression the same for all sub properties. That solved it.
public ExpressionBuilder Initialize<T>(string property)
{
if (string.IsNullOrEmpty(property))
throw new ArgumentNullException(property);
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
SetExpression<T>(property, ref type, ref arg, ref expr);
ExpressionModel = new ExpressionModel() { Expression = expr, ParameterExpression = arg, Type = type };
IsIntialized = true;
return this;
}
public ExpressionBuilder Append<T>(string property)
{
if (string.IsNullOrEmpty(property))
throw new ArgumentNullException(property);
if (IsIntialized == false)
throw new Exception("ExpressionBuilder has not been initialized.");
Type type = ExpressionModel.Type;
ParameterExpression arg = ExpressionModel.ParameterExpression;
Expression expr = ExpressionModel.Expression;
SetExpression<T>(property, ref type, ref arg, ref expr);
ExpressionModel.Expression = expr;
ExpressionModel.ParameterExpression = arg;
ExpressionModel.Type = type;
return this;
}
The ParameterExpression for the sub properties have to be the same as the parent. So I overloaded the function that creates Expression in order to be able to set the ParameterExpression explicitly.
public ExpressionBuilder Initialize<T>(string property, ParameterExpression parameterExpression)
{
if (string.IsNullOrEmpty(property))
throw new ArgumentNullException(property);
Type type = typeof(T);
ParameterExpression arg = parameterExpression;
Expression expr = arg;
SetExpression<T>(property, ref type, ref arg, ref expr);
ExpressionModel = new ExpressionModel() { Expression = expr, ParameterExpression = arg, Type = type };
IsIntialized = true;
return this;
}