[C# .net] Faire un simple "select" à SQL Server

Faire un simple "select" à SQL Server [C# .net] - C#/.NET managed - Programmation

Marsh Posté le 14-07-2006 à 16:23:44    

Bonjour à tous :)
 
J'ai un site Web où des utilisateurs peuvent créer des comptes perso en donnant leur pseudo.
Avant d'insérer leur pseudo, je dois vérifier qu'il n'existe pas déjà.  

Code :
  1. string command "select id_member from member where pseudo='robert'";
  2. SqlDataSource dataSource = new SqlDataSource(connectionString, command);
  3. this.mygridView.DataSource = dataSource;
  4. this.mygridView.DataBind();
  5. if (mygridView.Rows.Count > 0)
  6. {
  7.   // pseudo already exists
  8. }


C'est ce que j'ai l'habitude de faire, et je sais que c'est bien pour remplir un gridView mais nul pour tester juste l'existence d'un pseudo en base.  
Savez-vous comment je peux simplifier mon programme ?  
Mici bien :)
 

Reply

Marsh Posté le 14-07-2006 à 16:23:44   

Reply

Marsh Posté le 14-07-2006 à 21:54:08    

tu mets une contrainte et si tu essaies de créer avec le même pseudo, ça explose, alors tu affiches l'erreur.

Reply

Marsh Posté le 14-07-2006 à 22:38:16    

Taz a écrit :

tu mets une contrainte et si tu essaies de créer avec le même pseudo, ça explose, alors tu affiches l'erreur.


 
 
a la rigueur en testant le code de retour SQL, parce que faire un try/catch pour ca, ca me semble un tantinet couteux...


---------------
Hobby eien /人◕ ‿‿ ◕人\
Reply

Marsh Posté le 14-07-2006 à 22:40:38    

c'est certain que 10 millions d'utilisateurs s'inscrivent chaque jour.

Reply

Marsh Posté le 14-07-2006 à 22:42:33    

super l'argument : "tu peux coder comme un porc, c'est pas grave, ce code sera pas appelé souvent" :heink:


---------------
Hobby eien /人◕ ‿‿ ◕人\
Reply

Marsh Posté le 14-07-2006 à 22:55:54    

en quoi c'est porc ? le fait de vérifier par un select avant ne te garanties pas que l'insert suivant fonctionnera.

Reply

Marsh Posté le 14-07-2006 à 23:05:38    

c'est porc de chercher a faire "exploser" la requete d'insertion pour voir si l'utilisateur existe. Alors qu'il suffit de faire un  
 
select count(*) from ta_table (nolock) where ton_user = 'toto'
 
et de regarder si ca vaut 0 ou 1 pour faire ce qu'il veut.
 
Il veut "tester juste l'existence d'un pseudo en base." pas tester si l'insertion suivante marchera ou pas...


---------------
Hobby eien /人◕ ‿‿ ◕人\
Reply

Marsh Posté le 14-07-2006 à 23:08:14    

je ne vois absolument pas pourquoi c'est porc. C'est EAFP de base, ça marche très bien et ça fait moins de code.

Reply

Marsh Posté le 14-07-2006 à 23:11:10    

Taz a écrit :

je ne vois absolument pas pourquoi c'est porc. C'est EAFP de base, ça marche très bien et ça fait moins de code.


 
je ne sais pas, par exemple parce que ca crée un log dans la base de données, que ca perds du temps à faire une gestion d'erreur etc... maintenant si ca ne te choque pas, tant mieux pour toi.  :hello:


---------------
Hobby eien /人◕ ‿‿ ◕人\
Reply

Marsh Posté le 14-07-2006 à 23:15:57    

mais tu te rends compte que le select avant pour vérifier ne sert pas à grand chose et que quoi qu'il arrive, t'es obligé de faire un try sur l'insert ? quel log ? quel perte de temps ? quel cout en performance ? là je vois pas.

Reply

Marsh Posté le 14-07-2006 à 23:15:57   

Reply

Marsh Posté le 14-07-2006 à 23:40:30    

Effectivement, savoir que l'utilisateur n'existe pas ne va pas garantir que l'insertion marchera. Mais je n'ai jamais dit le contraire hein. Seulement si l'utilisateur existe deja, il ne fera pas d'insert et ca lui épargnera un try/catch couteux (et dans le code, et dans le serveur SQL).
 
Et pour les logs, j'ai vu le cas sur DB2 (et j'imagine que ce n'est pas le seul SGBD concerné) et où ca plombait carrément les perfs et le disque.
 
Maintenant tu fais comme tu veux hein,  ca va pas m'empêcher de dormir.


Message édité par Tamahome le 14-07-2006 à 23:42:39

---------------
Hobby eien /人◕ ‿‿ ◕人\
Reply

Marsh Posté le 17-07-2006 à 10:12:02    

Tamahome a écrit :

c'est porc de chercher a faire "exploser" la requete d'insertion pour voir si l'utilisateur existe. Alors qu'il suffit de faire un  
 
select count(*) from ta_table (nolock) where ton_user = 'toto'
 
et de regarder si ca vaut 0 ou 1 pour faire ce qu'il veut.
 
Il veut "tester juste l'existence d'un pseudo en base." pas tester si l'insertion suivante marchera ou pas...


Ca, y'a rien de plus crade.
 
Il se passe quoi si une personne s'incrit entre le moment où tu fait ton select et le moment où tu fait le insert ?
Ben ça plantera. Et vu que t'as codé comme un goret sans gestion d'erreur sur le insert, tu vas avoir droit un à beau plantage. Super clean.
 
C'est déprimant de voir des trucs pareil dès le lundi main :/

Message cité 1 fois
Message édité par Arjuna le 17-07-2006 à 10:13:26
Reply

Marsh Posté le 17-07-2006 à 10:20:24    

Tamahome > Dans tous les cas, quelque soit la requête que tu fais dans la base, le "try catch" est obligatoire. A moins que tu te retranches derrière la gestion du code d'erreur HTTP 500 pour afficher un message propre en cas d'erreur (sans savoir d'où il vient, donc impossible à tracer). Le moteur de base de données peut être indisponible pour un nombre infini de raison : requête pendant une oppération de backup, hold lock sur une table qui expire, etc. Sans parler simplement des problèmes courants de fichier de données plein, ou bêtement une coupure du service suite à un plantage.
 
Le moindre accès à la base doit toujours impérativement être dans un bloc TRY. Y'a pas d'alternative à ça si tu veux faire proprement. C'est d'autant plus vrai pour un site genre eCommerce ou autre, où la disponibilité et la transparence auprès de l'utilisateur final est capitale si on ne veut pas le faire fuir. Perso, une erreur 500 sur un site eCommerce, je ne retourne jamais dessus, trop peur que ma transaction soit aussi mal gérée.
 
Ensuite, pour ce qui est de la gestion des utilisateurs, le mieux, c'est une fonction T-SQL qui :
 
-> insertion dans la table
-> surcharge de l'erreur afin de la personnaliser. ou retour du nouvau numéro de ligne (scope_identity()) si tout se passe bien
 
le tout, géré dans un try depuis la couche web, au cas où une erreur survienne lors de l'accès à la base. Y'a pas à tortiller :spamafote:


Message édité par Arjuna le 17-07-2006 à 10:23:06
Reply

Marsh Posté le 17-07-2006 à 11:17:31    

je suis d'accord avec Arjuna. Ne pas faire une gestion dans un try catch est complètement abominable et le site sera une source d'erreur comme l'a déjà dit Arjuna.  
 
Faut pas oublié que si une constrainte est renvoyée (par exemple que le user existe déjà), cela est nettement plus performant que de faire un select avant de vérifier le code de retour puis d'insérer.  
Pourquoi, tout simplement parce que les conditions sont très gourmantes et qu'un SGBD est optimisé pour faire ce genre d'opération.  
 
S'il veut vraiment faire un select avant, ce que je déconseille, il doit au minimum vérouiller la table avant pour être sure de ne pas avoir de problème si un autre user en même temps lui chope son nouveau pseudo. Mais bon c'est couteau en performance si chaque user doit locker la table avant l'insert.  
 
La solution c'eszt bien sure de faire qu'un INSERT et de récupérer le message d'erreur s'il en contient en en sortie. Ou fair eune procédure d'insertion qui se chargera de personnaliser l'erreur. Mais c'est un peu plus compliqué que faire un simple insert du coté web. Dur dur d'entendre des anneries le lundi matin. Ceux qui ne savent pas de quoi il parle ferait parfois mieux de s'abstenir.

Reply

Marsh Posté le 17-07-2006 à 11:41:19    

Arjuna a écrit :

Ca, y'a rien de plus crade.
 
Il se passe quoi si une personne s'incrit entre le moment où tu fait ton select et le moment où tu fait le insert ?
Ben ça plantera. Et vu que t'as codé comme un goret sans gestion d'erreur sur le insert, tu vas avoir droit un à beau plantage. Super clean.
 
C'est déprimant de voir des trucs pareil dès le lundi main :/


 
où as tu lu que je ne mettais pas de gestion d'erreur sur l'insert ?
 
Je dis juste qu'il faut éviter de faire un insert si on sait d'avance que ca ne marchera pas. Apprends à lire.


---------------
Hobby eien /人◕ ‿‿ ◕人\
Reply

Marsh Posté le 17-07-2006 à 11:45:44    

t'as dit que les "try" ça prenait du temps et qu'il fallait les éviter. par extension, je traduis que tu voulais remplacer le try par un select dans la table, espérant obtenir le même résultat.
seul hic, il faut un try aussi pour le select
et en plus il ne te dégage pas de la possibilité de planter
 
dans tout les cas, un select suivit d'un insert prend bien plus de temps qu'un simple insert, même si ce dernier plante. pourquoi ? parceque les deux choses qui sont abominablement lentes dans une requête, c'est :
- communication entre les services
- parsing/optimisation de la requête
 
avec un select avant un insert :
- tu fais ces trucs deux fois
- dans tous les cas, en interne, le serveur va checker toutes les contraintes lors de l'insert, car il ne sait pas que tu a fait des tests pour lui avant (donc le insert est aussi lent qu'il réussisse ou qu'il plante, puisque la vérification se fait avant l'insertion effective)
- tu laisses passer des cas d'erreurs, alors que tu pars du principe que tu ne dois pas planter, donc tu risques grandement de ne pas gérer l'erreur correctement

Reply

Marsh Posté le 17-07-2006 à 12:51:00    

Arjuna a écrit :

t'as dit que les "try" ça prenait du temps et qu'il fallait les éviter.


 
ca serait bien de tout lire hein...
 
faut les éviter pour faire du traitement qu'on peut faire proprement par ailleurs : dans ce cas la, c'etait tester l'existence d'un login, pas tester le résultat d'un insert.
 
Je récapitule :
 
1 - on regarde si le login existe ou pas avec le select count(*)
2 - s'il n'existe pas on fait l'insert (dans un try/catch car un insert peut planter pour x raisons)
   -s'il existe, on ne perds pas de temps a essayer de faire un insert en récupérant l'Exception puisqu'on sait d'avance que ca ne marchera pas. (je me base sur l'énoncé du gus la)


Message édité par Tamahome le 17-07-2006 à 12:51:33

---------------
Hobby eien /人◕ ‿‿ ◕人\
Reply

Marsh Posté le 18-07-2006 à 00:14:09    

sauf que pour le second point du point deux, ça sert à rien étant donné que le select va "virtuellement" prendre autant de temps que le insert, même si ce dernier plante.
 
car je le rappelle, surtout pour ce genre de requêtes, la plupart du temps, c'est le fait d'éxécuter la requête (on se moque de ce qu'elle fait) qui prends le plus de temps.
 
deplus, privilégier les erreurs de ce type au niveau SGBD et non pas dans la couche application, c'est s'assurer que le SGBD connait toutes les contraintes du modèle de données.
 
Pas plus tard que tout à l'heure, j'ai pondu ce genre de trigger : (puisque la contrainte n'est pas suffisament simple pour être gérer en tant que contrainte)


-- Insert uniquement : le update est interdit sur cette table car trop le bordel et pas vraiment d'utilité
alter trigger travailpour_ins on travailpour instead of insert as
begin
 declare @newEditeur_id numeric(18,0)
 declare @newAuteur_id numeric(18,0)
 declare @newDebut datetime
 declare @newFin datetime
 
 begin transaction
 
 -- Ca, c'est juste pour que personne ne vienne m'emmerder pendant l'update. Logiquement, c'est inutile vu qu'un trigger est gérer comme une micro-instruction
 -- mais vu la présence de la transaction, j'ai eu peur
 select null from travailpour with (holdlock tablockx) where null is not null
 
 -- Dans la mesure où on peut ajouter plusieurs périodes d'un coup, je gère le truc ligne par ligne
 declare newRows cursor local FAST_FORWARD
 for select editeur_id, auteur_id, debut, fin from inserted
 
 open newRows
 
 fetch next from newRows into @newEditeur_id, @newAuteur_id, @newDebut, @newFin
 
 while @@fetch_status = 0
    begin
  declare @workingEditeur_id numeric(18,0)
  set @workingEditeur_id = null
 
  -- Est-ce qu'on a violé la contrainte ?
  select @workingEditeur_id = min(editeur_id) from travailpour where editeur_id != @newEditeur_id and auteur_id = @newAuteur_id and (debut between @newDebut and @newFin or fin between @newDebut and @newFin or @newDebut between debut and fin or @newFin between debut and fin)
 
  if @workingEditeur_id is not null
  begin
   -- Ben oui : l'auteur travaille déjà pour un autre éditeur à cette période
   declare @auteurNom nvarchar(101)
   declare @editeurNom nvarchar(50)
 
   select @auteurNom = auteur.prenom + ' ' + auteur.nom, @editeurNom = editeur.nom
   from editeur, auteur
   where auteur.id = @newAuteur_id and editeur.id = @workingEditeur_id
 
   -- Crève charogne. Puisque c'est comme ça, on annule tout (et toc)
   RAISERROR ('L''auteur %s travaille déjà pour l''éditeur %s durant cette période', 18, 1, @auteurNom, @editeurNom)
   rollback transaction
   return
  end
  else
  begin
   -- Non, ça va, c'est passé
   declare @tmpDebut datetime
   declare @tmpFin datetime
 
   set @tmpDebut = null
   set @tmpFin = null
 
   -- On cherche si on chevauche d'autres périodes. Si oui, on allonge avec les bornes des périodes chevauchées
   select @tmpDebut = isnull(min(debut), @newDebut)
   from travailpour
   where editeur_id = @newEditeur_id
   and auteur_id = @newAuteur_id
   and fin between @newDebut and @newFin
 
   select @tmpFin = isnull(max(fin), @newFin)
   from travailpour
   where editeur_id = @newEditeur_id
   and auteur_id = @newAuteur_id
   and debut between @newDebut and @newFin
 
   -- Plutôt que s'emmerder à décider si on doit mettre à jour X lignes, boucher des trous et tout ça, on consolide les périodes chevuahcées en une seule : du coup on vire tout
   delete travailpour
   where auteur_id = @newAuteur_id
   and editeur_id = @newEditeur_id
   and debut between @tmpDebut and @tmpFin
   and fin between @tmpDebut and @tmpFin
 
   -- Et on ajoute la nouvelle période avec les bornes modifiées en fonction des périodes chevauchées
   insert into travailpour
   (editeur_id, auteur_id, debut, fin)
   values
   (@newEditeur_id, @newAuteur_id, @tmpDebut, @tmpFin)
   
   fetch next from newRows into @newEditeur_id, @newAuteur_id, @newDebut, @newFin
  end
    end
 
 close newRows  
 deallocate newRows
 
 -- Tadam ! On a fini sans planter (un miracle moi je vous dis !)
 commit transaction
end


=> Dans tous les cas, même si ce dernier fait des traîtements relativement lourds, je garantie qu'à tout instant j'aurai une base avec des données cohérentes.
 
Ceci dit, ok pour le fait que tu répondais à la question. Mais il vaut mieux mettre roodie (qu'on à fait fuir :sol:) sur la bonne piste dès le départ ;)

Reply

Marsh Posté le 18-07-2006 à 08:37:28    

Arjuna a écrit :

sauf que pour le second point du point deux, ça sert à rien étant donné que le select va "virtuellement" prendre autant de temps que le insert, même si ce dernier plante.
 
un try/catch est tres couteux et je ne vois pas du tout l'intérêt de lever expres une exception. On fait le select, on voit que le username existe deja, inutile de faire l'insert puisqu'on sait qu'il va planter : ca épargne un try/catch ET un insert. Maintenant si tu me prouves qu'un SELECT est plus long qu'un INSERT qui plante ET d'un try/catch qui lève une exception, je suis tout ouïe !
 
car je le rappelle, surtout pour ce genre de requêtes, la plupart du temps, c'est le fait d'éxécuter la requête (on se moque de ce qu'elle fait) qui prends le plus de temps.
 
deplus, privilégier les erreurs de ce type au niveau SGBD et non pas dans la couche application, c'est s'assurer que le SGBD connait toutes les contraintes du modèle de données.
 
euh oui, je pars du principe que la base a été bien construite, avec toutes les contraintes kivontbien...
 



---------------
Hobby eien /人◕ ‿‿ ◕人\
Reply

Marsh Posté le 18-07-2006 à 09:03:18    

tu n'épargnes rien du tout : le select aussi peut planter (lock sur la table qui te met en timeout, problème de liaison avec la base -maintenance, etc.-). du coup tu dois TOUJOURS faire un try/catch à chaque accès à la base (tu vois que toi aussi tu ne lis pas ce que je dis :o)

Reply

Marsh Posté le 18-07-2006 à 09:28:29    

ouais ouais blablabla et un météorite peut s'écraser sur ton application cliente aussi [:ocube]
 
edit : tu n'arrivera pas a me convaincre et je n'arriverai manifestement pas à te convaincre non plus. Fin de la discution pour ma part :o


Message édité par Tamahome le 18-07-2006 à 09:32:00

---------------
Hobby eien /人◕ ‿‿ ◕人\
Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed