none
Problème relié à l'utilisation d'une base de données Access pour une application web RRS feed

  • Question

  • Je ne sais pas si c'est un problème strictement relié à Access, mais voilà. Je travail sur la programmation d'une application en ligne, programmée en ASP.NET 3.5 et C# avec Visual Studio 2010 et Access 2010 comme base de données. Comme mon hébergeur web ne prend pas en charge Access 2010, j'ai sauvegardé sous le format « .mdb » des version antérieurs d'Access et non le nouveau « .accdb ». En principe, l'application fonctionne bien, mais j'ai un bogue de connexion avec la base de données. De temps à autre, la connexion entre l'application et la base de données devient impossible et il n'y a plus rien à faire.

     

    Dans Visual Studio lorsque je débogue, je doit fermer et rouvrir Visual Studio lui-même pour que les connexions à la base de données reprennent (juste arrêté le débogage et relancé l'application ne fonctionne pas). Sur le serveur, il me faut attendre entre 5 et 15 minutes avant que les connexions reprennent. Pendant ce temps, Access laisse ouvert un fichier « .ldb » qui bloque la base de données de tout intervention à distance, comme si elle était toujours accédée par un utilisateur. J'ai débogué mon application au point où Visual Studio ne trouve plus rien à me dire. Mon code (je suis certain qu'il est imparfait) ne génère aucune erreur. Bref, ça plante « silencieusement », même ma page d'erreur par défaut n’est pas appelé. Les connexions deviennent juste coupées. Quand je fais un débogage avec le débogueur de Visual Studio, tout fonctionne, c'est uniquement la connexion qui refuse systématiquement l'accès à Access.

     

    Cette erreur survient sur Windows 7, Windows XP, MAC OS X Snow Leopard, avec Firefox et Internet Explorer, donc ça ne me semble pas relié au système d'exploitation, ni au navigateur Internet. Le principal problème, c'est que le bogue ne survient jamais vraiment au même moment, ni au même endroit, donc difficile à cibler. J'ai également vérifié et mon application ferme toute les connexions qu'elle ouvre (j'ai même un try/catch qui ferme la connexion en cas d'erreur), parce que oui, Visual Studio plante s'il y a plus de 64 connexions ouvertes en même temps. J'en conclu donc que c'est une procédure prise par Access pour gérer un problème quelconque.

     

    J'aimerais donc savoir si quelqu'un saurait ce qui pourrait causé les connexions avec Access de se rompre et/ou s'il y a un moyen d'empêcher le blocage complet des connexions ultérieurs pendant ces 5-15 minutes.

    mercredi 18 mai 2011 18:24

Réponses

  • J'avais déjà prévu faire une modification de mon code similaire aux exemples de la page donnée (ça m'a aidé à finaliser mon plan, merci), pour réduire la quantité de code et rendre les modifications plus faciles. Par contre, cette modification, bien qu'elle m'ait aidé à régler quelques erreurs de programmation ne change rien au problème.

     

    Sinon, je crois avoir trouvé la solution:

    connexion.Close(); ne ferme pas la connexion, ça ne fait que mettre le statut de la connexion à: « fermé ». Dans les faits, la connexion est toujours ouverte. En ajoutant connexion.Dispose(), la connexion est définitivement et complétement effacé et n'existe donc plus. À ma première tentative, j'ai réussi à réduire le temps où la base de données Access tombait inaccessible. Toutefois, même si le temps était réduit (à environ 1 minute dans Visual Studio), sur le serveur, au rétablissement de la connexion avec Access, la moindre modification à la base de données entraînait à nouveau l'erreur et Access retombait innaccessible, pour un bon 10 minutes.

     

    J'ai réalisé après avoir ajouter des connexion.Dispose() à la suite de mes connexions.Close() qu'il restait un type de connexion qui restait ouvert, les dataReaders (puisqu'ils nécessite que la base de données soit ouverte). J'ai donc ajouté une méthode pour fermer toutes les connexions encore ouverte à la base de données que j'appelle à la fin de l'affichage de mes données (donc après la dernière connexion). J'ai remarqué que mes GridView prenaient plus de temps à se charger après une modification (update). Avant, c'était comme s'ils étaient garder en mémoire et maintenant, les GridViews sont reconstruits à chaque rafraîchissement de page, ce qui me confirme qu'il ne reste plus aucune connexion existante.

     

    Depuis cette dernière modification, je n'arrive plus à faire « planter » mon application web (ce qui est normal considérant que je gérais toutes les erreurs possibles sauf le cas où je n'avais plus accès à Access). Par conséquent, le problème d'Access devenant inaccessible ne survient plus non plus. J'ai testé tout ce qui précédemment faisaient « planter » l'application, mais sans y arriver maintenant. J'expliquerais ce problème par la limite de connexions avec Access atteinte parce que les connexions n'étaient pas adéquatement « détruite ». Dans certaines circonstances, ces connexions fantômes s'accumulaient sans jamais être détruite convenablement et puis « boom! ». Le délais d'inactivité d'Access aurait donc été causé par le temps requis aux connexions pour se détruire elles-mêmes suite à une trop longue inactivité. Ainsi, ce n'était pas Access qui bloquait l'accès, mais l'incapacité d'ASP.NET à ouvrir une nouvelle connexion.

     

    Je crois donc qu'on peut considérer ce problème résolut. Merci beaucoup de l'aide apporté.

     

    ******************************************************

    //code derrière (associé directement) la page web\\
    
    try
    {
          //code utilisant les données de la base de données Access
    }
    catch (Exception ex)
    {
          //affiche l'erreur technique
          lblErreur.Text = "Une erreur est survenue lors du rafraîchissement de l'interface.<br /><br />" +
            ex.ToString();
    } //end catch
    finally
    {
          //ferme et dispose des connexions à la base de données
          PouvoirDB.ClearConnection();
    } //end finally
    
    //code dans un dossier App_Code\\
    
    //fermeture de toutes les connexions à la base de données
    public static void ClearConnection()
    {
        //vide le ObjectPool de connexion
        OleDbConnection.ReleaseObjectPool();
        //Garbage Collector
        GC.Collect();
    } //end ClearConnection


    mercredi 8 juin 2011 02:48

Toutes les réponses

  • Bonjour,

    Comment est définie/établie la connexion entre l'application et la BDD ?


    Argy
    jeudi 19 mai 2011 13:19
    Modérateur
  • J'utilise un module que j'appelle chaque fois qu'il est nécessaire d'accéder à la base de données.

    //connexion à la base de données Access
      private static string GetConnection()
      {
        OleDbConnection connection = new OleDbConnection();
    
        //Get the physical path to the file.
        string FilePath = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/Aramortis.mdb");
    
        return connection.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0; Data Source = "
          + @FilePath
          + "; User Id=admin;Password=;";
      } //end GetConnection
    
    

    Ce module fonctionne tout le temps... sauf quand Access semble me bloquer toute connexion. Le module est dans un fichier class « .cs » dans le répertoire « App_Code » de mon application. Aucune connexion ne se fait en dehors de mes class « .cs », qui utilisent tous le même module de connexion. Lorsqu'une fonction qui doit obtenir de l'information (ou écrire) sur la base de données dans l'application, elle appelle une requête SQL qui elle appel le module de connexion, la requête étant elle aussi dans mon fichier « .cs ». La base de données étant dans le répertoire « App_Data ».

     

    P.S. désolé pour les commentaires dans mon code. J'ai l'habitude de commenter en français pour ce qui explique une fonction complexe et en anglais pour des commentaires simples.



    jeudi 19 mai 2011 14:12
  • Bonjour,

    Humm, cette fonction retourne la ConnectionString mais comment ouvrez-vous et fermez/libérez-vous la connexion ?

    "Aucune connexion ne se fait en dehors de mes class « .cs », qui utilisent tous le même module de connexion"
    Est-elle fermée ?


    Argy
    vendredi 20 mai 2011 08:19
    Modérateur
  •   //prend la langue primaire de la race
      [DataObjectMethod(DataObjectMethodType.Select)]
      public static bool isLangueEcrite(int langueID)
      {
        //initialisation des variables
        bool isTrue = false;
        OleDbConnection connection = new OleDbConnection(GetConnection());
        string select = "SELECT DISTINCT [Langue_ecrite] "
          + "FROM [tb_langue] "
          + "WHERE ([Langue_ID] = lID)";
    
        OleDbCommand command = new OleDbCommand(select, connection);
    
        //utilisation des paramètres
        command.Parameters.AddWithValue("lID", langueID);
    
        try
        {
          //ouverture de la connexion avec Access
          connection.Open();
          //converti le crie d'Access en un string
          isTrue = (bool)command.ExecuteScalar();
          //fermeture de la connexion avec Access
          connection.Close();
        } //end try
        catch (Exception ex)
        {
          //ne rien faire, la langue n'est pas écrite, retourner false
          //ferme la connexion avec la base de données
          connection.Close();
        } //end catch
    
        return isTrue;
      } //end isLangueEcrite
    
    

     

    Voici un exemple. J'utilise ce modèle d'ouverture/fermeture partout.



    vendredi 20 mai 2011 12:38
  • Humm, là comme ça, rien ne justifie la perte de la connexion mais...
    Quelle est la fréquence d'accès à la BDD - regardant cette fonction, je suppose qu'il y a une procédure identique pour chaque appel nécessitant la collecte d'infomations.

    Ne serait-il pas mieux d'avoir une connexion ouverte par session plutôt que d'ouvrir une connexion à chaque appel...?
    Qu'est-ce qui garanti que vous ne dépassez pas les limites de 64 connexions ?

     


    Argy
    lundi 23 mai 2011 13:23
    Modérateur
  • La base de données est accédée plus de 100 fois pour charger chacune des 5 pages principales de l'application (j'utilise des DataTable pour personnalisé l'affichage des données des requêtes SQL), qui sont mises à jour à chaque rafraîchissement de page. Sinon, j'ai découvert qu'en cas d'erreur lorsque j'utilisais un DataReader, je ne fermais pas ce DataReader. J'ai corrigé ce problème, mais ce n'est toujours pas la cause du problème avec Access. Le code donné à mon poste précédent ne contient pas de DataReader, mais la seule différence, c'est que j’initialise le DataReader à null, puis j'entre dans le try/catch et dans le catch, j'ajoute le code pour fermer le DataReader

    OleDbDataReader dataReader = null;
    
        try
        {
          //ouverture de la connection avec Access
          connection.Open();
          //utilisation et fermeture de la connection avec Access
          dataReader = command.ExecuteReader();
    
          //lecture du dataReader
          while (dataReader.Read())
          {
            langue[index] = dataReader.GetInt32(0);
            index++;
          } //end while
          //fermeture du dataReader
          dataReader.Close();
        } //end try
        catch (Exception ex)
        {
          //ignore l'exception et retourne 0
          //ferme la connexion avec la base de données
          connection.Close();
          //ferme le dataReader
          dataReader.Close();
        } //end catch
    
    

    Le problème avec une connexion par session, c'est qu'il faut déterminer quand fermer la connexion. L'utilisateur peut fermer Internet, faire précédent ou suivant dans son navigateur Internet ou juste fermer l'onglet de l'application. Si pour une quelconque raison, je n'arrive pas à fermer cette connexion, Access restera verrouillé sur le serveur avec peu ou aucun intervention possible de ma part. Je trouve que c'est risqué, en plus d'une perte de la sécurité de la base de données.

     

    De ce que j'ai pu tester, l'erreur survient surtout avec les changements de pages.


    lundi 23 mai 2011 17:00
  • Il ne m'est pas possible de tester un contexte identique.

    Toutefois, j'aurais défini ma chaîne de connexion avec "Provider=Microsoft.Jet.OLEDB.4.0" et un jeu de Dataset pour ne pas avoir à me soucier des pertes de connexion éventuelles.


    Argy
    mardi 24 mai 2011 10:09
    Modérateur
  • J'ai changé ma ConnectionString pour utiliser le Jet provider et j'ai réglé un bogue avec un GridView et l'erreur du double clique pour obtenir une action. La GridView était DataBind() deux fois, ce qui lui faisait perdre son ViewState (bref, sa capacité à réagir à une action de bouton par exemple). Le bogue du GridView réglé, Access plante moins souvent, mais il plante encore à l'occasion sans raison apparente. Pour je ne sais quelle raison, l'erreur du double clique sur le GridView causait le bogue d'Access après un changement de page et une tentative modification de la base de données par la suite. L'erreur corrigé, c'est une cause de moins pour le bogue d'Access.

     

    Je ne peux pas utiliser un jeu de Dataset, parce que presque toutes les données prises dans la base de données peuvent être changé par l'utilisateur (c'est l'un des buts premiers de l'application). Je gagnerais probablement un peu en vitesse de chargement de page, mais les Datasets devraient trop souvent être changé pour pouvoir profiter de l'avantage de ces derniers (à savoir copier le contenu de la base de données et le garder en mémoire). Et puis, si l'utilisateur se contente de naviguer d'une page à l'autre de l'application sans faire de changement, je n'arrive pas à reproduire le bogue, ce qui me porte à croire que la lecture de données est OK.

     

    C'est donc un bogue qui se produit avec les ajouts et les modifications de données dans la base de données (il y a très peu de suppression et le bogue ne survient pas à ce moment). Je sais qu'il reste des erreurs de programmation dans mon code, mais ce que j'aimerais savoir, c'est pourquoi je n'ai plus accès à Access après l'erreur. Jusqu'à maintenant, je n'ai réussi qu'à enlever quelques erreurs qui causait le bogue. J'imagine que quelque chose fait en sorte que l'insertion ou la modification de données devient corrompus par un facteur X et qu'Access tombe en mode protection des données ou en mode réparation.

     

    C'est possible? Qu'est-ce qui arrive à Access si jamais quelque chose corrompt une donnée en cour d'écriture? Je n'ai pas de données corrompu dans ma base de données, alors j'imagine qu'Access y est pour quelque chose. Je ne m'attend plus à ce que cette erreur soit connu, mais j'aimerais avoir quelques nouvelles pistes, autrement, je débogue à l'aveuglette.


    mercredi 25 mai 2011 16:27
  • Hummm, non Access ne passe pas en mode protection des données ou en mode réparation en cas d'INSERT.

    Je ne sais pas si vous connaissez DAO mais dans Access lui même, on procède ainsi :

    DB.Execute SQLStatement, dbFailOnError
    

     où DB est un objet Database et SQLStatement est une chaîne SQL "INSERT" ou "UPDATE" ou "DELETE"

    Le dbFailOnError permet de récupérer l'erreur éventuelle.

    Puis-je voir un bout de code d'exemple de MAJ/INS que vous employez ?


    Argy
    jeudi 26 mai 2011 10:53
    Modérateur
  • Sur le code qui suit, quand la connexion avec Access est coupé, le code plante à « connection.open(); », il est attraper par le try/catch et le code suit son cour, mais comme rien n'est accessible, le code fini par envoyer l'utilisateur sur une page qui n'a aucune connexion avec la base de données. À toute fin pratique, le code ne plante plus. S'il y a une erreur dans la requête SQL, « command.ExecuteNonQuery(); » plante et ne va pas plus loin. Le tout est fonctionnel, mais quelque chose finit par survenir. Possible que j'aie fait quelque chose qu'il ne fallait pas.

    [DataObject(true)]
    public static class CompetenceDB
    {
    
    [DataObjectMethod(DataObjectMethodType.Insert)]
      public static int InsertPersoLanguesEcrites(int persoID, int langueID)
      {
        //initialisation des variables
        OleDbConnection connection = new OleDbConnection(GetConnection());
        string insert = "INSERT INTO tb_perso_langue "
                + "(Perso_Langue_Personnage_ID, Perso_Langue_langue_ID, "
                + "Perso_Langue_ecrite_uniquement, Perso_Langue_parle_uniquement) "
                + "VALUES (@persoID,@ID,true,false)";
    
        OleDbCommand command = new OleDbCommand(insert, connection);
    
        //utilisation des paramètres
        command.Parameters.AddWithValue("@persoID", persoID);
        command.Parameters.AddWithValue("@ID", langueID);
    
        int i = 0;
    
        try
        {
          //ouverture de la connection avec Access
          connection.Open();
          //utilisation et fermeture de la connection avec Access
          i = command.ExecuteNonQuery();
          connection.Close();
        } //end try
        catch (Exception ex)
        {
          //ferme la connexion à la base de données
          connection.Close();
        } //end catch
    
        return i;
      } //end InsertPersoLanguesEcrites
    
    
    
    [DataObjectMethod(DataObjectMethodType.Update)]
      public static int UpdatePersoLangues(int original_Perso_ID, int original_Langue_ID)
      {
        //initialisation des variables
        OleDbConnection connection = new OleDbConnection(GetConnection());
        string update = "UPDATE tb_perso_langue "
          + "SET Perso_Langue_ecrite_uniquement = false, "
          + "Perso_Langue_parle_uniquement = false "
          + "WHERE (Perso_Langue_Personnage_ID = @persoID) "
          + "AND (Perso_Langue_langue_ID = @langueID)";
    
        OleDbCommand command = new OleDbCommand(update, connection);
    
        //utilisation des paramètres
        command.Parameters.AddWithValue("persoID", original_Perso_ID);
        command.Parameters.AddWithValue("langueID", original_Langue_ID);
    
        int i = 0;
    
        try
        {
          //ouverture de la connection avec Access
          connection.Open();
          //utilisation et fermeture de la connection avec Access
          i = command.ExecuteNonQuery();
          //fermeture de la connection avec Access
          connection.Close();
        } //end try
        catch (Exception ex)
        {
          //ferme la connexion à la base de données
          connection.Close();
        } //end catch
    
        return i;
      } //end UpdatePersoArme
    
    //connection à la base de données Access
      private static string GetConnection()
      {
        OleDbConnection connection = new OleDbConnection();
    
        //Get the physical path to the file.
        string FilePath = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/Aramortis.mdb");
    
        return connection.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source = "
          + @FilePath
          + "; User Id=admin;Password=;";
      } //end GetConnection
    } //end CompetenceDB


    jeudi 26 mai 2011 12:58
  • Humm, j'ai trouvé un peu de temps pour mettre en application l'exemple de cette page.

    Pouvez-vous faire de même pour voir si vous rencontrez le même comportement ?

     


    Argy
    lundi 6 juin 2011 13:44
    Modérateur
  • J'avais déjà prévu faire une modification de mon code similaire aux exemples de la page donnée (ça m'a aidé à finaliser mon plan, merci), pour réduire la quantité de code et rendre les modifications plus faciles. Par contre, cette modification, bien qu'elle m'ait aidé à régler quelques erreurs de programmation ne change rien au problème.

     

    Sinon, je crois avoir trouvé la solution:

    connexion.Close(); ne ferme pas la connexion, ça ne fait que mettre le statut de la connexion à: « fermé ». Dans les faits, la connexion est toujours ouverte. En ajoutant connexion.Dispose(), la connexion est définitivement et complétement effacé et n'existe donc plus. À ma première tentative, j'ai réussi à réduire le temps où la base de données Access tombait inaccessible. Toutefois, même si le temps était réduit (à environ 1 minute dans Visual Studio), sur le serveur, au rétablissement de la connexion avec Access, la moindre modification à la base de données entraînait à nouveau l'erreur et Access retombait innaccessible, pour un bon 10 minutes.

     

    J'ai réalisé après avoir ajouter des connexion.Dispose() à la suite de mes connexions.Close() qu'il restait un type de connexion qui restait ouvert, les dataReaders (puisqu'ils nécessite que la base de données soit ouverte). J'ai donc ajouté une méthode pour fermer toutes les connexions encore ouverte à la base de données que j'appelle à la fin de l'affichage de mes données (donc après la dernière connexion). J'ai remarqué que mes GridView prenaient plus de temps à se charger après une modification (update). Avant, c'était comme s'ils étaient garder en mémoire et maintenant, les GridViews sont reconstruits à chaque rafraîchissement de page, ce qui me confirme qu'il ne reste plus aucune connexion existante.

     

    Depuis cette dernière modification, je n'arrive plus à faire « planter » mon application web (ce qui est normal considérant que je gérais toutes les erreurs possibles sauf le cas où je n'avais plus accès à Access). Par conséquent, le problème d'Access devenant inaccessible ne survient plus non plus. J'ai testé tout ce qui précédemment faisaient « planter » l'application, mais sans y arriver maintenant. J'expliquerais ce problème par la limite de connexions avec Access atteinte parce que les connexions n'étaient pas adéquatement « détruite ». Dans certaines circonstances, ces connexions fantômes s'accumulaient sans jamais être détruite convenablement et puis « boom! ». Le délais d'inactivité d'Access aurait donc été causé par le temps requis aux connexions pour se détruire elles-mêmes suite à une trop longue inactivité. Ainsi, ce n'était pas Access qui bloquait l'accès, mais l'incapacité d'ASP.NET à ouvrir une nouvelle connexion.

     

    Je crois donc qu'on peut considérer ce problème résolut. Merci beaucoup de l'aide apporté.

     

    ******************************************************

    //code derrière (associé directement) la page web\\
    
    try
    {
          //code utilisant les données de la base de données Access
    }
    catch (Exception ex)
    {
          //affiche l'erreur technique
          lblErreur.Text = "Une erreur est survenue lors du rafraîchissement de l'interface.<br /><br />" +
            ex.ToString();
    } //end catch
    finally
    {
          //ferme et dispose des connexions à la base de données
          PouvoirDB.ClearConnection();
    } //end finally
    
    //code dans un dossier App_Code\\
    
    //fermeture de toutes les connexions à la base de données
    public static void ClearConnection()
    {
        //vide le ObjectPool de connexion
        OleDbConnection.ReleaseObjectPool();
        //Garbage Collector
        GC.Collect();
    } //end ClearConnection


    mercredi 8 juin 2011 02:48
  • Oui, effectivement, j'avais omis le .Dispose() inscrit dans le code.

    En tout cas, je suis ravi que vous ayez solutionné ce point.


    Argy
    mercredi 8 juin 2011 10:37
    Modérateur