> We try to keep the re-linq front-end as general as possible. The fact that
> DefaultIfEmpty causes a GroupJoin to turn into a left-outer join in your
> example is true, but it's not as simple generally speaking.
> Consider the following slightly modified version of your query:
>
> var q = from c in ctxt.Order
> join o in ctxt.Order on c.CustomerId equals o.Customer.CustomerId
> into co // A
> from x in co.DefaultIfEmpty() //B
> from y in co //C
> select c;
>
> Here, B uses the group join in a left-outer join fashion, but C uses it in
> an ordinary cross join fashion.
Is that really the case? 'y' is an order, same as 'x', but 'co' is
the same set as when the DefaultIfEmpty() call is made.
I do understand why you're saying it, from the perspective of
sequences, it makes sense, however from the perspective of sets and SQL, I
don't really know how to formulate the query above, because one has to join
co again with... co ? (which can't be done).
At the moment I follow the same steps, but it's a cumbersome path
IMHO, where if you look at 'intent', it can be made simpler, IMHO. But I
understand why adding this to the front end might make situations like the
one above harder to deal with (although I'm interested in what you'd think
the query above does, how it looks in SQL :))
> The simplification you suggested (keeping A
> and flagging it as a left join) won't work here. And my opinion is that if
> we can't apply it generally, we shouldn't apply it at all. (As a
predefined
> transformation in the re-linq front-end, that is. Any LINQ provider can of
> course choose to build this simplification if it helps.)
IMHO the sole purpose of 'DefaultIfEmpty' is to use a sequence which
can contain nulls due to a left join, and in fact, it's the only mechanism
to force a left join into the final query (navigator traversal might result
in left joins due to nullable FK fields, but that's not enforceable) using a
'join' operator or a navigator traversal which would otherwise result in an
inner join.
I.o.w.: if you see a DefaultIfEmpty, the sequence it is called on is
a result of a left join, and you can treat that sequence as such. Dealing
with it 'on the fly' otherwise is a very cumbersome task as it's 'too late':
either group joins are already handled in some trees or other clauses refer
to the defaultifempty sequence and do that by its own alias (and not the
alias of the side the defaultifempty refers to)
> In addition, as Stefan said, DefaultIfEmpty can be applied to any
arbitrary
> sequence in a query, not only group joins. A LINQ provider striving to
> support DefaultIfEmpty should try to find a solution that works in all
> cases, no matter where the DefaultIfEmpty operator is applied.
yes, that's true. The second query I gave is indeed an example of
that. Though is it really a matter of 'a lot of cases' btw? Aren't there
just 2: 1) a group join result used with DefaultIfEMpty -> make groupjoin a
left join and 2) the random sequence on which DefaultIfEmpty() is called,
which also would result in a left join? (x=> c.Orders.DefaultIfEmpty())
The part I'm struggling with is the query you gave above, which to
me is unspecifyable in SQL, or at least I don't really see the 'intent' of
what such a construct might be. Could you elaborate on that a bit please?
> I've written a blog post about how I'd handle the DefaultIfEmpty query
> operator in a SQL-translating LINQ provider here:
> https://www.re-motion.org/blogs/mix/archive/2010/09/04/handling-the-
> defaultifempty-result-operator-in-a-re-linq-based-provider.aspx
> . This describes more or less how we deal with DefaultIfEmpty in re-
linq's
> SQL backend, and I think you'll agree that with an approach such as the
one
> explained in the blog post, DefaultIfEmpty isn't any longer difficult to
> implement.
.. except that you have a tremendous amount of work ahead of you:
optimize the query you're producing :). This is rather difficult to do
though, Linq to SQL (the best linq provider out there still) uses a lot of
visitors for this, which can take significant amount of time during query
production.
I do like the approach though, it makes things easier on the
expression tree front, at the expense of optimizing it later (something the
EF linq provider also does) with the assumption that optimizing queries is
easier. It's at least more straight forward! ;)
What I learned from the demoscene so many years ago though, was that
if you can do things up front, it will save you a lot of time later on.
I.o.w.: if you run into a defaultifempty, you can also think: why is it
there? My point is then: what if the answer to that question is: "to make a
join a left join"? This info is then usable to rework the tree a bit to
reflect that info so the expression tree handling is easier and you don't
need optimization afterwards. The question of course is: is it indeed only
there for that purpose or not? (in the context of database targeting
queries, as not all IQueryable operators make sense in every location of
such a query when you see them in the context of db targeting queries).
I have no clear answer to that. THe query you gave above as an
example clearly proves me wrong, so we can look into other ways perhaps to
deal with this, but on the other hand, the query construct also might not
make much sense in the context of a db query, which may mitigate the
argument. IF we can find evidence DefaultIfEmpty is there for making joins
left joins, things will get much easier to implement IMHO (including group
join).
Disclaimer: I haven't looked at these constructs for quite a while,
(read: more 1.5 years) so I might overlook an important aspect.
FB
>
> Cheers,
> Fabian
>
> On Sep 4, 12:02 pm, "Frans Bouma" <[email protected]> wrote:
> > > > from timeHeader in metaData.NonPresentTimeHeader where
> > > > timeHeader.Id == 6 join time in metaData.NonPresentTime on
> > > > timeHeader.Id equals time.HeaderId into timeJoin from joinedTime
> > > > in
> > > > timeJoin.DefaultIfEmpty() select joinedTime
> >
> > > > What might be a solution is to add a 'jointype' to the groupjoin
> > > > class of re-linq and switch it to 'left join' when you handle the
> > > > p=>DefaultIfEmpty() construct of a re-linq tree.
> >
> > > Hi Frans,
> >
> > > we could transform simple left-join-via-DefaultIfEmpty to left join
> > clauses
> > > in re-linq's QueryModel, but that would be a very simplistic
> > > solution that could not support any but the most straightforward LINQ
> clauses. (i.e.
> > only
> > > those where someone implements left join as they found out via
> > > Google, but for instance not any scenario where the query further
> > > references the intermediate join clause (timeJoin in your sample).
> >
> > A simple, stupid, but illustrative example: ('ctxt' is
> > session.Linq... ) var q = from c in ctxt.Order
> > join o in ctxt.Order on c.CustomerId equals
> > o.Customer.CustomerId Into co // A
> > from x in co.DefaultIfEmpty() //B
> > select c;
> >
> > Lines A and B form a SelectMany, due to the from in B. At the left
> > side, you'll have the group join in A and on the right side you have
> > the right side of the group join in A.
> >
> > This can be transformed into keeping A, and using B to adjust the
> > group join. The main advantage is that the group join contains the
> > selectors and in C#'s case also the projection.
> >
> > So if re-linq could transform this into (pseudo code!) var q = from c
> > in ctxt.Order
> > (left) join o in ctxt.Order on c.CustomerId equals
> > o.Customer.CustomerId Into co select c;
> >
> > it would be a big win. Big problem is the code which references the
> > DefaultIfEmpty result as that has to be changed to references to the
> > right-side of the join.
> >
> > It would help a lot, because it would make a join handler in a linq
> > provider much easier, in fact, an existing join handler (which can
> > handle the normal,
> > 'join') would likely already work.
> >
> > > We think that DefaultIfEmpty is best solved in each back-end
> > > individually, but of course nothing stops anyone from extending the
> > > front-end to create that kind of QueryModel via an optional
> > > transformation step. Just be aware that this only takes you so far,
> > > it would probably result in the LINQ provider rejecting every use of
> > > DefaultIfEmtpy in joins that cannot be reduced to that pattern.
> >
> > You mean:
> > var q = from c in ctxt.Order
> > from o in c.Orders.DefaultIfEmpty()
> > select c;
> >
> > ?
> >
> > FB- Hide quoted text -
> >
> > - Show quoted text-