You are on page 1of 7

1/28/2017

WhyLINQbeatsSQL

LINQPad
Home

Download

Purchase

Upgrade

FAQ

Resources

Resellers

Support

WhyLINQbeatsSQL
Ifyou'renotaLINQaddict,youmightwonderwhatthefussisabout.SQLisn'tbroken,sowhyxit?
Whydoweneedanotherqueryinglanguage?
ThepopularansweristhatLINQisINtegratedwithC#(orVB),therebyeliminangtheimpedance
mismatchbetweenprogramminglanguagesanddatabases,aswellasprovidingasinglequerying
interfaceforamultudeofdatasources.Whilethat'strue,it'sonlypartofthestory.Moreimportantly:
whenitcomestoqueryingdatabases,LINQisinmostcasesasignicantlymoreproducvequerying
languagethanSQL.
ComparedtoSQL,LINQissimpler,dier,andhigherlevel.It'sratherlikecomparingC#toC++.Sure,
therearemeswhenit'ssllbesttouseC++(asisthecasewithSQL),butinmostsituaons,workingin
amoderndylanguageandnothavingtoworryaboutlowerleveldetailsisabigwin.
SQLisaveryoldlanguageinventedin1974.Sincethenit'sbeenextendedendlessly,butnever
redesigned.ThishasmadethelanguagemessyratherlikeVB6orVisualFoxPro.Youmighthave
becomesoaccustomedtothisthatyoucan'tseeanythingwrong!
Let'stakeanexample.Youwanttowriteasimplequerythatretrievescustomersasfollows:
SELECTUPPER(Name)
FROMCustomer
WHERENameLIKE'A%'
ORDERBYName

Thatdoesn'tlooktoobad,right?Butnowsupposetheseresultsarefeedingawebpage,andwewant
toretrievejustrows2130.Suddenly,youneedasubquery:
SELECTUPPER(Name)FROM
(
SELECT*,RN=row_number()
OVER(ORDERBYName)
FROMCustomer
WHERENameLIKE'A%'
)A
WHERERNBETWEEN21AND30
ORDERBYName

Andifyouneedtosupportolderdatabases(priortoSQLServer2005),itgetsworse:

http://www.linqpad.net/WhyLINQBeatsSQL.aspx

1/7

1/28/2017

WhyLINQbeatsSQL

SELECTTOP10UPPER(c1.Name)
FROMCustomerc1
WHERE
c1.NameLIKE'A%'
ANDc1.IDNOTIN
(
SELECTTOP20c2.ID
FROMCustomerc2
WHEREc2.NameLIKE'A%'
ORDERBYc2.Name
)
ORDERBYc1.Name

Notonlyisthiscomplicatedandmessy,butitviolatestheDRYprinciple(Don'tRepeatYourself).Here's
samequeryinLINQ.Thegaininsimplicityisclear:
varquery=
fromcindb.Customers
wherec.Name.StartsWith("A")
orderbyc.Name
selectc.Name.ToUpper();

varthirdPage=query.Skip(20).Take(10);

OnlywhenweenumeratethirdPagewillthequeryactuallyexecute.InthecaseofLINQtoSQLorEnty
Framework,thetranslaonenginewillconvertthequery(thatwecomposedintwosteps)intoasingle
SQLstatementopmizedforthedatabaseservertowhichit'sconnected.

Composability
Youmighthavenocedanothermoresubtle(butimportant)benetoftheLINQapproach.Wechoseto
composethequeryintwostepsandthisallowsustogeneralizethesecondstepintoareusable
methodasfollows:
IQueryable<T>Paginate<T>(thisIQueryable<T>query,intskip,inttake)
{
returnquery.Skip(skip).Take(take);
}

Wecanthendothis:
varquery=...
varthirdPage=query.Paginate(20,10);

Theimportantthing,here,isthatwecanapplyourPaginatemethodtoanyquery.Inotherwords,with
LINQyoucanbreakdownaqueryintoparts,andthenreusesomeofthosepartsacrossyour
applicaon.

Associaons
http://www.linqpad.net/WhyLINQBeatsSQL.aspx

2/7

1/28/2017

WhyLINQbeatsSQL

AnotherbenetofLINQisthatyoucanqueryacrossrelaonshipswithouthavingtojoin.Forinstance,
supposewewanttolistallpurchasesof$1000orgreatermadebycustomerswholiveinWashington.
Tomakeitinteresng,we'llassumepurchasesareitemized(theclassicPurchase/PurchaseItem
scenario)andthatwealsowanttoincludecashsales(withnocustomer).Thisrequiresqueryingacross
fourtables(Purchase,Customer,AddressandPurchaseItem).InLINQ,thequeryiseortless:
frompindb.Purchases
wherep.Customer.Address.State=="WA"||p.Customer==null
wherep.PurchaseItems.Sum(pi=>pi.SaleAmount)>1000
selectp

ComparethistotheSQLequivalent:
SELECTp.*
FROMPurchasep
LEFTOUTERJOIN
CustomercINNERJOINAddressaONc.AddressID=a.ID
ONp.CustomerID=c.ID

WHERE
(a.State='WA'||p.CustomerIDISNULL)
ANDp.IDin
(
SELECTPurchaseIDFROMPurchaseItem
GROUPBYPurchaseIDHAVINGSUM(SaleAmount)>1000
)

Extendingthisexample,supposewewanttosorttheresultsinreverseorderofprice,andalsowantthe
salesperson'snameandnumberofpurchaseditemsinthenalprojecon.Nocehownaturallywecan
expresstheseaddionalcriteriawithoutrepeon:
frompindb.Purchases
wherep.Customer.Address.State=="WA"||p.Customer==null
letpurchaseValue=p.PurchaseItems.Sum(pi=>pi.SaleAmount)
wherepurchaseValue>1000
orderbypurchaseValuedescending
selectnew
{
p.Description,
p.Customer.SalesPerson.Name,
PurchaseItemCount=p.PurchaseItems.Count()
}

Here'sthesamequeryinSQL:
SELECT
p.Description,
s.Name,
(SELECTCOUNT(*)FROMPurchaseItempiWHEREp.ID=pi.PurchaseID)
PurchaseItemCount

FROMPurchasep
LEFTOUTERJOIN
Customerc
INNERJOINAddressaONc.AddressID=a.ID
http://www.linqpad.net/WhyLINQBeatsSQL.aspx

3/7

1/28/2017

WhyLINQbeatsSQL

INNERJOINAddressaONc.AddressID=a.ID
LEFTOUTERJOINSalesPersonsONc.SalesPersonID=s.ID
ONp.CustomerID=c.ID

WHERE
(a.State='WA'ORp.CustomerIDISNULL)
ANDp.IDin
(
SELECTPurchaseIDFROMPurchaseItem
GROUPBYPurchaseIDHAVINGSUM(SaleAmount)>1000
)
ORDERBY
(SELECTSUM(SaleAmount)FROMPurchaseItempiWHEREp.ID=pi.PurchaseID)DESC

Aninteresngpointisthatit'spossibletotransliteratetheaboveSQLquerybackintoLINQ,yieldinga
querythat'severybitasrepeveandatleastasclumsy.You'lloensee(typicallynonworking
versionsof)suchqueriespostedonforumsthishappensasaresultofthinkinginSQL,ratherthan
thinkinginLINQ.It'sratherliketransliterangaFortranprogramintoC#6,andthencomplainingabout
theclumsysyntaxforGOTO.

ShapingData
SelecngfrommorethanonetableinSQLrequiresjoiningtheendresultbeingrowsofattuples.If
you'veusedSQLformanyyears,youmayhavebecomesoaccepngofthisthatitmaynotoccurtoyou
thatthisforceddenormalizaonisoenundesirable:itleadstodataduplicaonandmakesresultsets
awkwardtoworkwithontheclient.Incontrast,LINQletsyouretrieveshapedorhierarchicaldata.This
avoidsduplicaon,makesresultseasiertoworkwith,andinmostcasesitevenobviatestheneedfor
joining.Forexample,supposewewanttoretrieveaseleconofcustomers,eachwiththeirhighvalue
purchases.InLINQ,youcandothis:
fromcindb.Customers
wherec.Address.State=="WA"
selectnew
{
c.Name,
c.CustomerNumber,
HighValuePurchases=c.Purchases.Where(p=>p.Price>1000)
}

HighValuePurchases,here,isacollecon.Andbecausewewerequeryinganassociaonproperty,we
didn'tneedtojoin.Whichmeansthedetailofwhetherthiswasainnerorouterjoinisnicelyabstracted
away.Inthiscase,thequery,whentranslatedtoSQL,wouldbeanouterjoin:LINQdoesn'texcluderows
justbecauseasubcolleconreturnszeroelements.Ifwewantedsomethingthattranslatedtoaninner
join,wecoulddothis:
fromcindb.Customers
wherec.Address.State=="WA"
letHighValuePurchases=c.Purchases.Where(p=>p.Price>1000)
whereHighValuePurchases.Any()
selectnew
{
c.Name,
c.CustomerNumber,
http://www.linqpad.net/WhyLINQBeatsSQL.aspx

4/7

1/28/2017

WhyLINQbeatsSQL

c.CustomerNumber,
HighValuePurchases
}

LINQalsosupportsatouterjoins,adhocjoins,subqueries,andnumerousotherkindsofqueries
througharichsetofoperators.

Parameterizaon
Whatifwewantedtoparameterizeourpreviousexample,sothatthestate"WA"camefromavariable?
Thisisallwedo:
stringstate="WA";

varquery=
fromcindb.Customers
wherec.Address.State==state
...

NomessingwithparametersonDbCommandobjectsorworryingaboutSQLinjeconaacks.LINQ's
parameterizaonisinline,typesafe,andhighlyreadable.Itdoesn'tjustsolvetheproblemitsolvesit
reallywell.
AsbecauseLINQqueriesarecomposable,wecanaddpredicatescondionally.Forexample,wecould
writeamethodasfollows:
IQueryable<Customer>GetCustomers(stringstate,decimal?minPurchase)
{
varquery=Customers.AsQueryable();

if(state!=null)
query=query.Where(c=>c.Address.State==state);

if(minPurchase!=null)
query=query.Where(c=>c.Purchases.Any(p=>p.Price>
minPurchase.Value));

returnquery;
}

IfwecallthismethodwithnullstateandminPurchasevalues,thefollowingSQLisgeneratedwhenwe
enumeratetheresult:
SELECT[t0].[ID],[t0].[Name],[t0].[AddressID]
FROM[Customer]AS[t0]

However,ifwespecifyvaluesforstateandminPurchase,LINQtoSQLwillnotonlyaddthepredicatesto
thequery,butthenecessaryjoinsaswell:
SELECT[t0].[ID],[t0].[Name],[t0].[AddressID]
FROM[Customer]AS[t0]
LEFTOUTERJOIN[Address]AS[t1]ON[t1].[ID]=[t0].[AddressID]
http://www.linqpad.net/WhyLINQBeatsSQL.aspx

5/7

1/28/2017

WhyLINQbeatsSQL

LEFTOUTERJOIN[Address]AS[t1]ON[t1].[ID]=[t0].[AddressID]
WHERE(EXISTS(
SELECTNULLAS[EMPTY]
FROM[Purchase]AS[t2]
WHERE([t2].[Price]>@p0)AND([t2].[CustomerID]=[t0].[ID])
))AND([t1].[State]=@p1)

BecauseourmethodreturnsanIQueryable,thequeryisnotactuallytranslatedtoSQLandrununl
enumerated.Thisgivethecallerachancetoaddfurtherpredicates,paginaon,customprojecons,and
soon.

StacTypeSafety
Intheprecedingqueries,ifwehaddeclaredthestatevariableasanintegerratherthanastring,the
querywouldfailatcompilemeratherthanrunme.Thesameappliesifyougetanyofthetableor
columnnameswrong.Thisisofrealbenetwhenrefactoring:thecompilerwilltellyouifyouhaven't
completelydoneyourjob.

ClientProcessing
LINQletsyoueortlesslyshipartofthequeryontotheclientforprocessing.Whywouldyouwantto
dothis?Withaheavilyburdeneddatabaseserver,itcanactuallyimproveperformance.Aslongasyou
don'ttakemoredatathanyouneed(inotherwords,youslldoallthelteringontheserver)youcan
oenhelpperformancebyshiingsomeoftheburdenofreordering,transformingandregroupingthe
resultsontoalessloadedapplicaonserver.WithLINQ,allyouneedtodoistoslipAsEnumerable()into
thequery,andeverythingfromthatpointonexecuteslocally.

WhennottouseLINQforqueryingdatabases
Despiteitspower,LINQdoesn'tdeprecateSQL.Ittakesmorethan95%ofthequeryingbrunt,butyou
sllsomemesneedSQLfor:
Handtweakedqueries(especiallywithopmizaonorlockinghints)
Queriesthatinvolveselecngintotemporarytables,thenqueryingthosetables
Predicatedupdatesandbulkinserts
AndofcourseyousllneedSQLfortriggers.(SQL'salsoneededforstoredproceduresandfuncons,
althoughtheneedforthesecropsuplessoenwhenyou'reusingLINQ).YoucancombineLINQwith
SQLbywringtablevaluedfunconsinSQL,andthencallingthosefunconswithinmoreelaborate
LINQqueries.
Havingtoknowtwoqueryinglanguagesisnotreallyanissuebecauseyou'llwanttolearnLINQanyway
LINQissousefulforqueryinglocalcolleconsandXMLDOMs.Ifyou'resllusingtheold
XmlDocumentbasedDOM,you'llndLINQtoXML'sDOMadramacstepup.
AndbecauseLINQiseasiertomasterthanSQL,thegoalofwringreallygoodqueriesismore
achievablewithLINQthanwithSQL.

LINQintheField
http://www.linqpad.net/WhyLINQBeatsSQL.aspx

6/7

1/28/2017

WhyLINQbeatsSQL

IuseLINQalmostexclusivelyforqueryingdatabasesbecauseit'smoreproducve.
Forwringapplicaons,mypersonalexperienceisaLINQenableddataaccesslayer(usinganAPIsuch
asLINQtoSQLorEntyFramework)cutsthedataaccessdevelopmentmebymorethanahalf,aswell
asmakingmaintenancefareasier.

follow@linqpad

ViewonTwier

LucasTeixeira
@Flash3001

FollowLINQPadonFacebook

@Nick_Craver@linqpadwaybetterthanopening
Ildasm

Like

Share 2.7Kpeoplelikethis.Bethefirstof
yourfriends.

MakeaFeatureRequest
VisittheLINQPadForum
ContactCustomerSupport

JosephAlbahari20072017

http://www.linqpad.net/WhyLINQBeatsSQL.aspx

7/7