Avatar billede hrc Mester
28. august 2019 - 09:19 Der er 7 kommentarer og
3 løsninger

Læse data fra tabeller (SqlDataReader) på en sikker måde

Jeg stammer fra Delphi-verdenen og dette indlæg er et undrende hjertesuk.

Nu er det C# og jeg savner et ordentligt programmeringssprog! Specifikt leder jeg efter samme funktionalitet som jeg havde tidligere, for det gav sikker kodning.

create table dbo.postnrtabel
{
  ref int not null
,postnr nvarchar(10) not null
,postby nvarchar(50) not null
}

select ref
      ,postnr
      ,postby
from dbo.postnrtabel

I Delphi kan man lægge scriptet ind i en TQuery (TADOQuery)-komponent, her kaldet qPostnrTabel. Forbinde til en base. Dobbeltklikke på komponenten og vælge de tre felter. Hermed blev der lavet 3 felt-linjer: qPostnrtabelRef: TIntegerField, qPostnrtabelPostnr: TStringField, qPostnrtabelPostBy TStringField; I koden refererede man direkte til disse, eks. Ref := qPostnrTabelRef.AsInteger;
Man kunne naturligvis også bruge qPostnrTabel.FieldByName(<feltnavn>) og qPostnrTabel[<feltnavn>] ... men det prøvede man at undgå.

Det gav en stor sikkerhed i programmeringen, fremfor SqlDataReaders reader.GetInt32(0) og GetString(1) og GetString(2). Noget lort hvis jeg rekreerer tabellen og tilføjer en aktiv-bit efter ref-feltet i tabellen, eller fjerner et felt i den. Så skal jeg HUSKE at rette længere nede. Programmet oversætter fint uanset dette. Jeg kan også skrive (int)reader["ref"] osv, men hvis jeg vælger at ændre postby til by, så skal jeg igen tjekke kode. Igen kan programmet fint bygges uden at afsløre fejlen.

I Delphi ville qPostnrtabelPostBy-feltet blive omdøbt til qPostnrBy og programmet ville ikke kunne kompilere, uden at alle steder var blevet rettet (hvilket var meget let).

Hvorfor kan man ikke gøre samme i C# ?

En anden ting jeg allerede har set en del er: Hvorfor bruger man ikke parameteriserede scripts? Altid noget med at bygge et script i en streng med parametre og alt: var script = $"select ref, postnr, postby  ... where ref={RefNoegle]".
- Sqlserveren vil lave en ny execution plan hver eneste gang scriptet køres? Med parameter-løsningen vil den genbruge og basen vil blive hurtigere.
- En anden og ligeså vigtig ting er faren for SQL-injection!
- Endelig behøver man ikke kere sig om hvorvidt dataformatet skrives "2019-08-28" eller om der er byttet rundt.

Brug dog parametre! Hvorfor gør man sådan i C#?

Der skal noget til at overbevise mig, om at C# er et fremskridt, men prøv. Jeg sidder i suppedasen.
Avatar billede Syska Mester
28. august 2019 - 09:57 #1
Inden du sviner et sprog, så burde du måske lige læse lidt mere om det.

https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlparameter?view=netframework-4.8

At der er mange på nettet der gør det forkert, det kan jo aldrig være sprogets skyld. :-) Der er sikkert også mange i Dephi der gør ting man ikke burde.

Mange bruger https://github.com/StackExchange/Dapper eller andne MIcroORM over IDBConnection ...

Andre bruger en ORM som https://en.wikipedia.org/wiki/Entity_Framework ... hvor du kan få det hele automatisk genereret ud fra din database eller modsat, lavet din database ud fra din C# model.

Der er mange muligheder ...
Avatar billede hrc Mester
28. august 2019 - 10:09 #2
Tak for links. For en der starter på C# så er mængden af information overvældende. Skal jeg starte med at læse om GAC'en? Første program et ASP.NET eller windows forms eller bare et konsolprogram? Pointen er: Uden den store C#-erfaring sidder jeg og kan ikke bruge nogle gode arbejdsgange som jeg har med fra Delphi.

Men du har lidt ret. Jeg synes C# er en syntaktisk rodebutik af hovsaløsninger.
Avatar billede Syska Mester
28. august 2019 - 10:41 #3
Jeg har stadig svært ved at se hvad du ikke kan bruge fra din Delphi tid ... jeg synes egentligt dine 2 punkter blev dækket meget godt, men måske der er noget jeg har overset.

Mht. parameter til SQL, så har du ikke søgt efter det på nettet ellers ville du have fundet massere af svar og hvorfor man skal bruge det netop for at undgå SQL Injection.
Avatar billede hrc Mester
28. august 2019 - 11:52 #4
Vi taler forbi hinanden. Kan godt fornemme at jeg støder en .NET mand på stoltheden. Beklager, det er ikke meningen. Se det fra min side: Jeg føler endnu ikke jeg er på vej mod noget bedre. Jeg er frustreret, føler mig uduelig/ubrugelig og savner en faglig stolthed. Jeg brokker mig skam meget mindre, end jeg søger svar.

Det meste er lavet. Parametrene er på plads; ingen ben i det (mine erfarne kolleger bruger det bare ikke. Skal jeg, som newbee, tage den kamp op? En sikker måde af få venner :-) )

SQL-scripts skriver jeg i en List<string> så jeg kan skrive det som ovenfor. Har så tit oplevet et manglende mellemrum før anførselstegnet. Formaterer med String.Join("\n", lst.ToArray()

... men jeg mangler stadig at kunne referere et felt, uden at bero på et indeks eller et navn i anførselstegn. Jeg vil gerne have en compiler-fejl hvis jeg ændrer navnet.

Jeg skal se hvad ORM har at byde på. Det lyder interessant. Havde håbet på et eller andet med en klasse, med en property for hvert felt, som man kørte gennem en generic-reader, a la reader<postnrklasse>... Sådan lidt over i den magiske verden.

Endelig. Der findes sløset programmering alle steder. Det er bare ikke så let at sjuske i Delphi. Man skal bare følge nogle få enkle regler. De vil jeg gerne have med over i C#

Mange tak for dine kommentarer.
Avatar billede Syska Mester
28. august 2019 - 13:38 #5
Du støder mig ikke, men det er på ingen måder sagligt kritik du kommer med :-) Det er heldigvis kun viden du mangler ..

Hvis i ikke bruge SQL Paramters i .NET, så er det klart en kampen værd.

Det er useriøst ikke at bruge det og dem som ikke allerede gør burde seriøst fyres ... :-) Som du siger ... ny SQL Execution plan for hver unik query ... kan bringe en SQL server i knæ.

Dine kollegaer er ikke erfarne hvis du ikke bruge dem, de er bare gamle.

Kig på Dapper:
https://github.com/StackExchange/Dapper

Super dejlig nemt at bruge i forhold til alt det du har gang i ... næsten intet overhead i forhold til performance.

Ja, du skal have styr på hvad dine felter i DB hedder som skal match din .NET klasse... så er man rimelig langt.

Bruger altid Dapper ...
Avatar billede arne_v Ekspert
28. august 2019 - 14:11 #6
Jeg er ligeså forundret som syska.

Alle C# udviklere med bare minimum af viden/erfaring bruger parametre.

Normalt vil man bruge feltnavn fremfor feltindeks til at udvaelge kolonnen, men det er faktisk ikke et stort problem med feltindeks - tilfoejer man et felt til tabellen og man ikke bruger det saa virker koden stadig (medmindre man bruger *, hvilket man naturligvis ikke goer) og skal man bruge feltet saa skal man alligevel rette koden.

Og jeg forstaar ikke det med navn og omdoeb. Der er ingen sprog paa denne jord hvor du kan omdoebe felter i databasen og en eksisterende EXE vil finde ud af det uden at man goer noget. Det vil kraeve tankelaesning.

Der skal goeres noget. Hvis der autogenereres noget kode udfra tabel, saa vil man faa en compile fejl, naar man rebuilder indtil man har fixet problemet. Hvis det er ren haandkode, saa faar man fejl i ens unit test indtil man har  fixet problemet.

Simpel ADO.NET og ORM code first er ren haandkode. Typed DataSet og ORM database first genererer kode. Du vaelger det som passer dig bedst.
Avatar billede arne_v Ekspert
28. august 2019 - 14:18 #7
Der er rigtigt mange maadet at tilgaa databaser i C#.

Jeg har beskrevet en del her:

http://www.vajhoej.dk/arne/articles/dotnetdb.html

Men der er mange ting som jeg ikke daekker.
Avatar billede hrc Mester
28. august 2019 - 14:46 #8
Tak til jer begge. Det er meget brugbart.

Proceduren i Delphi mht. ændrede feltnavne: Der er ikke tale om at omdøbe felter fra programmeringssproget, blot holde dem 1-1 med scriptet (eks. ved "select ref as ID", eller for at give et kompliceret script nogle fornuftige feltnavne). Dobbeltklik på komponenten, slet felterne og indlæs de nye. qPostnrTabelRef hedder nu qPostnrTabelID. Refererer jeg feltet i min kode, så fejler kompileringen. Det var det jeg mente.

Jeg prøver at ymte den lille detalje med parametre.
Avatar billede hrc Mester
28. august 2019 - 14:51 #9
Min kritik mht. syntaksen ... det må være den du refererer til. For det første:
Hader tuborgklammer! Er miljøskadet efter mange år med Delphi.
Det irriterer mig at skulle sætte () bagefter et funktionskald uden argumenter.
Og hvad med ?$@-tegnene? De dukker pludselig op i koden og betyder hhv. at feltet kan være null, en ny (og bedre) strengformatering og at strengen ikke fortolkes for escape-tegn. Jeg siger ikke funktionaliteten er dum, er endda lidt forelsket i $'eren, men syntaksmæssigt er det kompakte hovsaløsninger. IMHO
Avatar billede arne_v Ekspert
28. august 2019 - 15:19 #10
Sprog er naesten altid lidt forskellige i tilgang til problemer.

Med hensyn til at introducere alias for felter saa vil jeg mene at de to typiske C# maader er:

simpel ADO.NET => man tilfoer AS xxxx i SQL og retter feltnavn indeks i reader

ORM => man renamer property paa klasse og retter mapning til database (SQL genererers automatisk)

funktion1(funktion2())

og

funktion1(funktion2)

er begge valid syntax men goer noget forskelligt. Det foerste kalder med vaerdien fra funktion2. Det andet kalder med en "pointer" til funktion2.

Delphi bestemmer hvilken det er udfra erklaeringen af funktion1, men C# skelner altsaa (overvej hvis funktion1 returnerer en pointer til en funktion!!).
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester