F# left join with multiple conditions, as a query expression - c#

In C# i can do a left join query, with multiple conditions. Like this C# example that runs fine:
using (var db = new CompanyContext())
{
var q =
from d in db.deps
from e in db.emps.Where(e => d.id==e.dep_id && d.start_time<e.modified).DefaultIfEmpty() // left join
select new { d, e };
var result = q.ToList();
// ...
}
I've tried to translate this to the following in F#:
use db = new CompanyContext()
let q = query {
for d in db.deps do
for e in db.emps.Where(fun e -> d.id=e.dep_id && d.start_time<e.modified).DefaultIfEmpty() do // left join
select ( d, e )}
let result = q.ToList()
// ...
But the F# version results in an Exception:
System.InvalidOperationException: 'variable '_arg1' of type
'MyLib.dep' referenced from scope '', but it is not defined'
What is wrong with my F# query?
EDIT: using an anonymous record select {| d = d; e = e |} instead of select ( d, e ) as suggested, results in the same Exception
EDIT:
Full Exception (There is no inner exception. It's null):
System.InvalidOperationException: variable '_arg1' of type 'MyLib.dep' referenced from scope '', but it is not defined
at System.Linq.Expressions.Compiler.VariableBinder.Reference(ParameterExpression node, VariableStorageKind storage)
at System.Linq.Expressions.Compiler.VariableBinder.VisitParameter(ParameterExpression node)
at System.Linq.Expressions.Compiler.VariableBinder.Visit(Expression node)
at System.Linq.Expressions.Compiler.VariableBinder.VisitUnary(UnaryExpression node)
at System.Linq.Expressions.Compiler.VariableBinder.Visit(Expression node)
at System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection`1 nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitNewArray(NewArrayExpression node)
at System.Linq.Expressions.Compiler.VariableBinder.Visit(Expression node)
at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.Compiler.VariableBinder.Visit(Expression node)
at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.Compiler.VariableBinder.Visit(Expression node)
at System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection`1 nodes)
at System.Linq.Expressions.Compiler.VariableBinder.VisitLambda[T](Expression`1 node)
at System.Linq.Expressions.Compiler.VariableBinder.Visit(Expression node)
at System.Linq.Expressions.Compiler.LambdaCompiler.Compile(LambdaExpression lambda, DebugInfoGenerator debugInfoGenerator)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.GetLambdaExpression(Expression argument)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.GetLambdaExpression(MethodCallExpression callExpression, Int32 argumentOrdinal)
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.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.DefaultIfEmptyTranslator.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.TranslateExpression(Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input)
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.SelectManyTranslator.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.TranslateExpression(Expression linq)
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.SelectTranslator.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.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__DisplayClass41_0.<GetResults>b__1()
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__DisplayClass41_0.<GetResults>b__0()
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__31_0()
at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at ConsoleApplication1.main(String[] argv) in C:\Temp\ConsoleApplication1\ConsoleApplication1\Program.fs:line 780
groupJoin doesn't seem to compile with my extra condition. Visual Studio complains that it doesn't know d, and underlines it in d.start_time<e.modified:
use db = new CompanyContext()
let q = query {
for d in db.deps do
groupJoin e in db.emps.Where(fun e -> d.start_time<e.modified) on (d.id=e.dep_id) into es // left join
for e in es.DefaultIfEmpty() do
select ( d, e )}
let result = q.ToList()
FS0039 The value, namespace, type or module 'd' is not defined.

I think i've found a solution. This seems to produce the expected result, with groupJoin:
use db = new CompanyContext()
let q = query {
for d in db.deps do
groupJoin e in db.emps on (d.id=e.dep_id) into es
for e in es.Where(fun e -> d.start_time<e.modified).DefaultIfEmpty() do
select ( d , e )}
let result = q.ToList()
You don't have to use the on clause, by using a dummy condition that's always true, like 0=0:
use db = new CompanyContext()
let q = query {
for d in db.deps do
groupJoin e in db.emps on (0=0) into es
for e in es.Where(fun e -> d.id=e.dep_id && d.start_time<e.modified).DefaultIfEmpty() do
select ( d , e )}
let result = q.ToList()
The examples also work with leftOuterJoin instead of groupJoin.
Opinion:
I don't like how leftOuterJoin implicitly applies .DefaultIfEmpty() to what is declared by the into part (into es in the above examples). Because then if you want to add more conditions in a following for clause like me with .Where, then you still need to apply .DefaultIfEmpty() so that there is at least 1 result again anyway, because for is a next and separate operation. Might as well use groupJoin that always needs .DefaultIfEmpty() to do a left join

Related

ToList thows Exception when building Expression tree using concat multiple Expressions

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;
}

Multisorting IQueryable using a string column name within a generic extension method?

I need to sort collection by a few columns. I have list of objects where every object contain columnName and sortDir(Asc or Desc). So, how i can do it? I have tried to do it by reflection, but i have the error:
Message: `LINQ to Entities does not recognize the method 'Int32 Parse(System.String)' method, and this method cannot be translated into a store expression.`
StackTrace: ` at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.DefaultTranslator.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.EqualsTranslator.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.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.Linq.SystemCore_EnumerableDebugView`1.get_Items()`
I read some articles this and this, but their solutions doesn't help me. Maybe somebody have some ideas for me?
Code:
public static IQueryable<T> SortByParams<T>(IQueryable<T> collection, List<SortBy> sorting)
{
bool firstOrder = true;
IOrderedQueryable<T> sortedRes = null;
foreach (SortBy sort in sorting)
{
try
{
if (firstOrder)
{
if (sort.sortDir.ToLower() == "desc")
{
sortedRes = collection.OrderByDescending(obj => obj.GetType().GetProperty(sort.columnName).GetValue(obj, null));
}
else
{
sortedRes = collection.OrderBy(obj => obj.GetType().GetProperty(sort.columnName).GetValue(obj, null));
}
firstOrder = false;
}
else
{
if (sort.sortDir.ToLower() == "desc")
{
sortedRes = sortedRes.ThenByDescending(obj => obj.GetType().GetProperty(sort.columnName).GetValue(obj, null));
}
else
{
sortedRes = sortedRes.ThenBy(obj => obj.GetType().GetProperty(sort.columnName).GetValue(obj, null));
}
}
}
catch (Exception) { }
collection = sortedRes;
}
return collection;
}
Expressions like this
obj => obj.GetType().GetProperty(sort.columnName).GetValue(obj, null)
might be applicable in LINQ To Objects context, but definitely not in LINQ to Entities.
In general you cannot use reflection (and many other methods) inside the LINQ to Entities query expression because they cannot be translated to SQL (or query provider does not recognize and handle them, hence NotSupportedException).
Instead of reflection, you should build the expression using System.Linq.Expressions.
For instance, like this:
public static IQueryable<T> SortByParams<T>(this IQueryable<T> source, List<SortBy> sorting)
{
var queryExpr = source.Expression;
string methodAsc = "OrderBy";
string methodDesc = "OrderByDescending";
foreach (var item in sorting)
{
var selectorParam = Expression.Parameter(typeof(T), "e");
var selector = Expression.Lambda(Expression.PropertyOrField(selectorParam, item.columnName), selectorParam);
var method = string.Equals(item.sortDir, "desc", StringComparison.OrdinalIgnoreCase) ? methodDesc : methodAsc;
queryExpr = Expression.Call(typeof(Queryable), method,
new Type[] { selectorParam.Type, selector.Body.Type },
queryExpr, Expression.Quote(selector));
methodAsc = "ThenBy";
methodDesc = "ThenByDescending";
}
return source.Provider.CreateQuery<T>(queryExpr);
}

EntityFramework LINQ string order comparison

I have a varchar(5) column (named name) in my table (named codes). This SQL query is what I'd like to accomplish via EF, without first converting my IQueryable to IEnumerable (in memory):
SELECT * FROM codes WHERE name >= 'J0000' AND name <= 'J9999'
I've tried performing a query using this method, like this:
var results = db.Codes.Where(c=>c.Name.CompareTo("J0000") >=0
&& c.Name.CompareTo("J9999") <=0)
However the CompareTo can't be translated into sql and an exception is thrown. In order for CompareTo to work, you have to have an IEnumerable, meaning all the records are already pulled from the database into memory. When try to execute this I get:
{"The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities."}
With stack trace:
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.NotSupportedTranslator.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.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
My only other option that I can think of is to use .SqlQuery() on my db.Codes DbSet in order to execute the actual select statement using >= and <=.
Are there any non-sql options that will allow me to perform a query like this?
EDIT: In my actual code I was doing a .CompareTo(codes[0]) where codes was an array of string. This array was breaking the EF sql translation. My solution was to get the string value into a new string variable first, and then pass the new string variable to .CompareTo() instead of the array variable / index specified.
You can use CompareTo to compare your strings. Entity Framework converts these into >, <, >= and <=.
var results = db.Codes.Where(c => c.Name.CompareTo("J0000") >= 0);
Or using this syntax:
var results = from c in db.Codes
where c.Name.CompareTo("J0000") >= 0
select c;
This will produce SQL output similar to this:
WHERE [Extent1].[Name] >= N'J0000'
EDIT
After you gave your error message it seems you are using an indexed property instead of string literals in your Linq query. To fix this, copy the values to temporary variables. So this:
var results = db.Codes.Where(c=> c.Name.CompareTo(somearray[0]) >=0
&& c.Name.CompareTo(somearray[1]) <=0)
Becomes this:
var lowerBound = somearray[0];
var upperBound = somearray[1];
var results = db.Codes.Where(c=> c.Name.CompareTo(lowerBound) >=0
&& c.Name.CompareTo(upperBound) <=0)

Entity Framework throws NotSupportedException after calling Any or Count

I've got the following code:
private void Import(DbSet<DBEntity> dbEntities, IEnumerable<ExcelEntity> entities, ClapEntities context)
{
foreach (var me in entities)
{
try
{
var dbe = dbEntities.Where(IsEqualRecord(me, context));
bool hasElement = dbe.Any(); //Line which throws the exception
}
catch (Exception ex)
{
//...
}
}
}
protected abstract Expression<Func<DBEntity, bool>> IsEqualRecord(ExcelEntity entity, ClapEntities context);
The Any() extension throws a NotSupportedException:
System.Exception: Could not map entity to database. Mapper: [CuttingToolImport]; Rownumber: [153] ---> System.NotSupportedException: The method 'First' can only be used as a final query operation. Consider using the method 'FirstOrDefault' in this instance instead.
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.FirstTranslator.TranslateUnary(ExpressionConverter parent, DbExpression operand, MethodCallExpression call)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.EqualsTranslator.TypedTranslate(ExpressionConverter parent, BinaryExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.BinaryTranslator.TypedTranslate(ExpressionConverter parent, BinaryExpression 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.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.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.AggregateTranslator.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.Convert()
at System.Data.Entity.Core.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery1.<>c_DisplayClassb.b_a()
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
at System.Data.Entity.Core.Objects.ObjectQuery1.<>c_DisplayClassb.b_9()
at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func1 operation)
at System.Data.Entity.Core.Objects.ObjectQuery1.GetResults(Nullable1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery1..GetEnumerator>b__0()
at System.Lazy1.CreateValue()
at System.Lazy1.LazyInitValue()
at System.Data.Entity.Internal.LazyEnumerator1.MoveNext()
at System.Linq.Enumerable.Single[TSource](IEnumerable1 source)
at System.Linq.Queryable.Count[TSource](IQueryable1 source)
at CLAP.Models.Import.ImportMapping.ImportBase2.Import(DbSet1 dbEntities, IEnumerable1 entities, ClapEntities context)
--- End of inner exception stack trace ---
As you can see, the Any() extension (or the Count() extension) uses the First() extension internal which is not allowed. I have no idea how I can fix that error.
Exactly this code has worked for many months with Entity Framework version 5 or 6 and
Microsoft SQL Server 2012 - 11.0.2100.60 (X64) Express Edition (64-bit) on
Windows NT 6.1 <X64> (Build 7601: Service Pack 1)
Does anyone has an idea on how to solve that error?
You should use .FirstOrDefault() instead which will return NULL when there is no entities in dataset:
bool hasElement = dbe.FirstOrDefault() != null;
Another possible issue in your code is that you're using custom IsEqualRecord method which should contain only code that can be translated into SQL.

NotSupportedException using ToList() in LINQ and EntityFramework

I have a query that looks like this:
var q = from x in db.Table1
where x.Timestamp.CompareTo(fromDate) >= 0
&& x.Timestamp.CompareTo(toDate) < 0
select x;
var q_list = q.ToList(); // this works fine
var g = q.Where(z=> z.Table2.Equals(ns)); // ns is instance of Table2 not Table1
var g_list = g.ToList(); // this throws exception
The exception:
**
System.NotSupportedException was unhandled
Message=Unable to create a constant value of type ...
I have a lot of functions like that and I do not want to repeat queries, I need to use the Where, GroupBy ...etc and other functions.
FULL exception trace:
**
System.NotSupportedException was unhandled
Message=Unable to create a constant value of type 'DataAccessLayer.Model.Table2. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
Source=System.Data.Entity
StackTrace:
at System.Data.Objects.ELinq.ExpressionConverter.ConstantTranslator.TypedTranslate(ExpressionConverter parent, ConstantExpression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.EqualsTranslator.TypedTranslate(ExpressionConverter parent, BinaryExpression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input, DbExpressionBinding& binding)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, DbExpression& source, DbExpressionBinding& sourceBinding, DbExpression& lambda)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.SequenceMethodTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, SequenceMethod sequenceMethod)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.Convert()
at System.Data.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
at System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
at System.Data.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
at System.Data.Entity.Internal.Linq.InternalQuery`1.GetEnumerator()
at System.Data.Entity.Infrastructure.DbQuery`1.System.Collections.Generic.IEnumerable<TResult>.GetEnumerator()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
**
The problem is that EntityFramework doesn't understand types that are not primitive. What you have to do is this:
var g = q.Where(z => z.field.Id.Equals(ns.Id));
Keep in mind that if you have a custom implementation of Equals, there's no way it can be translated to proper t-sql. If you want to execute that implementation anyway you'll need to do it in memory:
// note it's using q_list instead of q
var g = q_list.Where(z => z.field.Equals(ns));

Categories

Resources