But.. there are coders who simply love to write more code, perhaps they feel that the more convoluted-looking their code are the smarter they are. Hence most of them even when the code can sometimes use and be made more readable with Linq, will still use lambda instead. They feel they have elite skills just by using lambda.
var itemsOwner = items .Join (people, i => i.OwnerId, p => p.PersonId, (i, p) => new { Item = i, Person = p }) .Join (countries, ip => ip.Person.CountryId, c => c.CountryId, (ip, c) => new { ItemPerson = ip, Country = c }) .Select (x => new { x.ItemPerson.Item.ItemName, x.ItemPerson.Person.PersonName, x.Country.CountryName });
Those coders need to be in the movie Crank, let's see how they can write multiple joins in no time, let's see if they can still breath after two joins.
Then the continent name needed be shown on the output:
var itemsOwner = items .Join (people, i => i.OwnerId, p => p.PersonId, (i, p) => new { Item = i, Person = p }) .Join (countries, ip => ip.Person.CountryId, c => c.CountryId, (ip, c) => new { ItemPerson = ip, Country = c }) .Join (continents, ipc => ipc.Country.CountryId, z => z.ContinentId, (ipc, z) => new { ItemPersonCountry = ipc, Continent = z }) .Select (x => new { x.ItemPersonCountry.ItemPerson.Item.ItemName, x.ItemPersonCountry.ItemPerson.Person.PersonName, x.ItemPersonCountry.Country.CountryName, x.Continent.ContinentName });
That's the problem with lambda joins, the aliases of multiple lambda joins cascades to the aliases of the subsequent joins. The aliases become unwieldy and deeply nested.
And there's too many variations of that lambda join, some would prefer the code below, deciding early what properties to project during lambda join. Ironically, though Linq and lambda promotes the use of deferred execution, but here we are, we have a coder deciding upfront that it's better to project user-shown properties right there in the lambda joins.
var itemsOwner = items .Join (people, i => i.OwnerId, p => p.PersonId, (i, p) => new { i.ItemName, p.PersonName, p.CountryId }) .Join (countries, ipc => ipc.CountryId, c => c.CountryId, (ipc, c) => new { ipc.ItemName, ipc.PersonName, c.CountryName });
And then you need to include the brand, oh the DRY headache!
var itemsOwner = items .Join (people, i => i.OwnerId, p => p.PersonId, (i, p) => new { i.ItemName, p.PersonName, p.CountryId, i.BrandId }) .Join (brands, ipcb => ipcb.BrandId, b => b.BrandId, (ipcb, b) => new { ipcb.ItemName, ipcb.PersonName, ipcb.CountryId, b.BrandName }) .Join (countries, ipcb => ipcb.CountryId, c => c.CountryId, (ipcb, c) => new { ipcb.ItemName, ipcb.PersonName, c.CountryName, ipcb.BrandName });
See the problem with lambda joins? Aside from too many variations (deciding upfront to project the user-shown properties during joinings vs deferring that decision on final join or select), there's also the hard problem of giving a proper alias for the subsequent joins.
Then there's a change in the requirement to show the brand name after the item name, you feel the aliases should reflect that fact, so you got to change the alias too:
var itemsOwner = items .Join (people, i => i.OwnerId, p => p.PersonId, (i, p) => new { i.ItemName, i.BrandId, p.PersonName, p.CountryId }) .Join (brands, ibpc => ibpc.BrandId, b => b.BrandId, (ibpc, b) => new { ibpc.ItemName, b.BrandName, ibpc.PersonName, ibpc.CountryId }) .Join (countries, ibpc => ibpc.CountryId, c => c.CountryId, (ibpc, c) => new { ibpc.ItemName, ipcb.BrandName, ibpc.PersonName, c.CountryName });
The highfalutin coder experiences the problems with unwieldy alias names cascading to subsequent joins, nested aliases, and giving good names for aliases, he decided to wise up; he decided he can avoid all the headaches above just by giving a generic (e.g., src) name for all aliases:
var itemsOwner = items .Join (people, src => src.OwnerId, p => p.PersonId, (src, p) => new { src.ItemName, src.BrandId, p.PersonName, p.CountryId }) .Join (brands, src => src.BrandId, b => b.BrandId, (src, b) => new { src.ItemName, b.BrandName, src.PersonName, src.CountryId }) .Join (countries, src => src.CountryId, c => c.CountryId, (src, c) => new { src.ItemName, src.BrandName, src.PersonName, c.CountryName });
By the time all the original developers in the project are replaced, new developers will be left scratching their heads why the old developers didn't bother to give self-describing names for aliases.
Let's say you want to switch the join order (MySQL is notorious on requiring certain order on joins to gain performance) of people and brands..
var itemsOwner = items .Join (people, src => src.OwnerId, p => p.PersonId, (src, p) => new { src.ItemName, src.BrandId, p.PersonName, p.CountryId }) .Join (brands, src => src.BrandId, b => b.BrandId, (src, b) => new { src.ItemName, b.BrandName, src.PersonName, src.CountryId }) .Join (countries, src => src.CountryId, c => c.CountryId, (src, c) => new { src.ItemName, src.BrandName, src.PersonName, c.CountryName });
..you cannot nilly-willy re-order the joins without incurring changes on code, you can not just cut-and-paste the code, this doesn't compile:
var itemsOwner = items .Join (brands, src => src.BrandId, b => b.BrandId, (src, b) => new { src.ItemName, b.BrandName, src.OwnerId }) .Join (people, src => src.OwnerId, p => p.PersonId, (src, p) => new { src.ItemName, src.BrandName, p.PersonName, p.CountryId }) .Join (countries, src => src.CountryId, c => c.CountryId, (src, c) => new { src.ItemName, src.BrandName, src.PersonName, c.CountryName });
Now contrast the humble developer's Linq join code to codes above. The code is very readable:
var itemsOwner = from i in items join p in people on i.OwnerId equals p.PersonId join b in brands on i.BrandId equals b.BrandId join c in countries on p.CountryId equals c.CountryId select new { i.ItemName, b.BrandName, p.PersonName, c.CountryName };
Then switch the join order of people and brands, the changes in Linq-using code is not invasive compared to lambda-using code, you can just cut-and-paste the code, this compiles:
var itemsOwner = from i in items join b in brands on i.BrandId equals b.BrandId join p in people on i.OwnerId equals p.PersonId join c in countries on p.CountryId equals c.CountryId select new { i.ItemName, b.BrandName, p.PersonName, c.CountryName };
If the coder doesn't see problems with lambda joins and still prefer to write joins using it instead of Linq, he need to star in the movie Crank. I would hazard a guess he would die by the time he is in the third or fourth join; or worse yet was required to insert another join between the existing joins, good luck breathing with that task!
Another lambda join variation, not compounding objects in one alias, giving each joined object their own alias. Drawback is, subsequent joins must repeat the objects from the previous joins, a WET code:
var itemsOwner = items .Join (people, src => src.OwnerId, p => p.PersonId, (src, p) => new { Item = src, Person = p }) .Join (brands, src => src.Item.BrandId, b => b.BrandId, (src, b) => new { src.Item, src.Person, Brand = b }) .Join (countries, src => src.Person.CountryId, c => c.CountryId, (src, c) => new { src.Item, src.Person, src.Brand, Country = c }) .Select (x => new { x.Item.ItemName, x.Person.PersonName, x.Brand.BrandName, x.Country.CountryName });
The coder forgot he is using MySQL, it's optimal to join on brands first, then people. As an exercise, try switching the join order of people and brands of the code above, good luck breathing! http://dotnetfiddle.net/3bvQDy
Another variation, this requires investing another class to assist you in your adventure on joining using lambda, this also requires you to violate DRY principle:
var itemsOwner = items .Join (people, src => src.OwnerId, p => p.PersonId, (src, p) => new JoinHelper { Item = src, Person = p } ) .Join (brands, src => src.Item.BrandId, b => b.BrandId, (src, b) => new JoinHelper { Item = src.Item, Person = src.Person, Brand = b }) .Join (countries, src => src.Person.CountryId, c => c.CountryId, (src, c) => new JoinHelper { Item = src.Item, Person = src.Person, Brand = src.Brand, Country = c } ) .Select (x => new { x.Item.ItemName, x.Person.PersonName, x.Brand.BrandName, x.Country.CountryName });
Do you still want to showboat your lambda join skills?
Time to repent your perceived superiority, use Linq to avoid highfalutin lambda code.
The competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague -- Edsger Dijkstra
There are cases that lambda is an unnecessary trick, just showboating.
Wise
ReplyDelete