Reputation: 1554
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.
Upvotes: 3
Views: 240
Reputation: 1554
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
Upvotes: 1