Hi Patrick,
I'll try one last string of arguments why I think that existence guards are the
right way to go. After that, I'll shut up - because it is obvious that the
semantics in Linq2Objects-transgressing scenarios is arbitrary, so you can
define it as you want!
In contrast to (||-4), (||-5) gives you the following (I say simple) semantics:
* Some condition using properties of an object can only be true if the object
"is there".
* No "projection anomaly".
But, as I and you noted, for Linq2Objects-transgressing expressions we get:
* "Inverse operator anomaly": !(...==...) is not (...!=...)
There are more anomalies, like the &&-commutativity and ||-commutativity, which
are apparently accepted by all ...
I would be easier to convince if you gave me a somewhat thorough treatment of
your logic. At least you should demonstrate what happens in (||-4) with
De'Morgan and associativity - obviously, I'll have to do the same for (||-5)
(for SQL (triple valued) logic, we all know how to do that; therefore, it
should also be done for another query logic).
If you really want to convince me (and probably many others), you have to write
down the *disadvantages* of our proposal. If they are visible - i.e., there are
examples for them -, then one can see and accept them.
-----
One item which would might me to give in to you:
There is actually *one* implicit assumption which I never questioned - and
which you apparently do not share: That very simple conditions like
.Where(a => a.B.C.P == ...)
should use inner joins. I grew up in a world where inner joins were
significantly cheaper than outer joins ... that's why. If we really use outer
joins *all the time*, then there is no or-sum-anomaly, because simple
conditions like
.Where(a => a.B.C.P == null)
would return objects where B or B.C is null. (||-4) might be a way to go then
...
-----
Here are some attempts to reply to your arguments. I'll maybe use quite blunt
words - please do not take them as attacks, but as a hopefully clear
formulation of what I consider right or wrong.
> More and more I believe that just using outer joins is the best
> technique. Here are the latest reasons:
> 1. Significantly simpler implementation.
Trat could be the case --> let's try it (although I'm not yet convinced - see
below after 5.).
> 2. Same semantics when null references are not present.
That's true for both (||-4) and (||-5) - all the anomalies vanish then!
Therefore, that's no argument for one over the other.
> 3. SQL semantics when null references are present (as opposed to
> semantics that are neither SQL nor Object)
I think this is wrong reasoning. Linq (like other, older OQLs) is a
*navigational query language*. There is no "SQL semantics" in it - never and
nowhere. For example, in Linq it is true that null == null (and therefore I
expect that SQL providers do translate the expression a.P == a.Q as
(...P = ...Q OR ...P IS NULL AND ...Q IS NULL)
- everything else is obviously wrong (under BEHAV-1 ... BEHAV-4); I have not
tested whether NHib's Linq and EF's provider is correct in this respect). The
arguments about "SQL semantics" come (a) from some misguided and unfortunate
sentences in the C# docs when Nullables were introduced; and (b) that
"implementation thinking" where SQL semantics is allowed to "hi-jack" the Linq
semantics because "this is 'simpler'". But it is only "simpler" if you toggle
between Linq and SQL semantics erratically for trivial examples. Instead, one
should think about whole *classes of queries* (that's how I found the
projection anomaly) and consider the semantics.
Again: There is no SQL semantics for a.B.C.P == null - simply because there is
no "navigation a.B.C" in SQL.
> 4. If existence guards are added to protect against NREs manually, it
> behaves like Linq to Objects.
That's the same for both semantics.
> 5. Simpler queries, likely leading to performance improvements, and
> at very least being easier to understand.
>
Well - I should march up my 20 developers, shouldn't I ;-) ? Do you have a
group of people who have programmed against (||-4) and are satisfied with it =
did not come to you [or whoever else] and ask about why they find objects they
are not expected to see? I did not invent that (||-5) semantics as a theorist
... all that has been used for a few years, and therefore I say that it is
"simpler", "more efficient" (many inner joins) and "easier to understand".
But I do not see a constructive way how we can settle our different opinions
what is "simpler" and "easier to understand". Do you???
> I'm actually quite interested in seeing a version of your code that
> just uses the outer joins
That should be easy ...
> and optimizes to inner joins when possible.
... this is not that easy - but I'll try it out!: In the moment where there is
a navigation ("a member expression at depth > 1"), the outer join must remain -
otherwise you get the or-sum-anomaly.
> My hope is that you could indeed drop significant portions of it
> without the existence guards.
>
> BTW, feel free to add data and tests to the
> NHibernate.Linq.Development project. I'll quickly integrate pull
> requests.
... only if I need different data, then I have to learn to work with EF ...
wouldn't be that bad!!!, but - you know - that thing called time ... I'll try!
Regards
Harald
--
Empfehlen Sie GMX DSL Ihren Freunden und Bekannten und wir
belohnen Sie mit bis zu 50,- Euro! https://freundschaftswerbung.gmx.de