IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C# Discussion :

Export AD vers CSV très lent


Sujet :

C#

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Homme Profil pro
    Administrateur systèmes et réseaux
    Inscrit en
    Février 2014
    Messages
    19
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Administrateur systèmes et réseaux

    Informations forums :
    Inscription : Février 2014
    Messages : 19
    Par défaut Export AD vers CSV très lent
    Bonjour,

    J'ai codé un programme qui exporte la liste des utilisateurs d'une OU spécifique vers un fichier .csv.

    la connexion à l'AD est fonctionnelle, tout comme l'export des usagers.

    Le problème, c'est que c'est très lent (en moyenne, une minute par utilisateur et j'en ai parfois plus de 200).

    J'ai testé la même chose en PowerShell, sur le même poste, et ça n'a demandé que quelques secondes.

    Alors je me demande si le problème ne vient pas de mon code, le voici :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    // ** Export des utilisateurs **
            private void button2_Click(object sender, EventArgs e)
            {
                // Début du sablier
                Cursor.Current = Cursors.WaitCursor;
     
                // S'assurer qu'une OU a bien été saisie
                if (saisieOU == "")
                {
                    textBox1.Focus();
                    return;
                }
     
                // S'assurer que l'OU recherchée existe
                sourceBase = "OU=Utilisateurs,OU=" + saisieOU + ",OU=Ma Société,OU=INTERNET,DC=FR";
                if (DirectoryEntry.Exists("LDAP://" + sourceBase))
                {
                    DE = new DirectoryEntry("LDAP://" + sourceBase);
                }
                else
                {
                    // L'OU n'a pas été trouvée
                    MessageBox.Show("Impossible de trouver '" + saisieOU + "' dans l'AD.");
                    return;
                }
     
                // Recherche des utilisateurs          
                SearchResultCollection results;
                DirectorySearcher DS = null;
                DS = new DirectorySearcher(DE);
                //DS.PropertiesToLoad.Add("Initials");
                DS.PropertiesToLoad.Add("Surname");
                DS.PropertiesToLoad.Add("GivenName");
                DS.PropertiesToLoad.Add("SamAccountName");
                //DS.PropertiesToLoad.Add("Department");
                //DS.PropertiesToLoad.Add("Organization");
                DS.PropertiesToLoad.Add("DisplayName");
     
                // Filtres
                DS.Filter = "(&(objectCategory=person)(objectClass=user))";
                results = DS.FindAll();
     
                // En-tête des colonnes
                DataTable DT = new DataTable();
                //DT.Columns.Add("Initials", typeof(string));
                DT.Columns.Add("Surname", typeof(string));
                DT.Columns.Add("GivenName", typeof(string));
                DT.Columns.Add("SamAccountName", typeof(string));
                //DT.Columns.Add("Department", typeof(string));
                //DT.Columns.Add("Organization", typeof(string));
                DT.Columns.Add("DisplayName", typeof(string));
     
                // Récupération des données
                foreach (SearchResult SR in results)
                {
                    DataRow DR = DT.NewRow();
                    DirectoryEntry entry = SR.GetDirectoryEntry();
     
                        // Iniatials
                        //if (entry.Properties["Initials"].Count > 0)
                        //{
                        //    DR["Initials"] = entry.Properties["Initials"].Value.ToString();
                        //}
     
                        // Surname
                        if (entry.Properties["sn"].Count > 0)
                        {
                            DR["Surname"] = entry.Properties["sn"].Value.ToString();
                        }
     
                        // GivenName
                        if (entry.Properties["GivenName"].Count > 0)
                        {
                            DR["GivenName"] = Suppression_Caractere_accentue(entry.Properties["GivenName"].Value.ToString());
                        }
     
                        // SamAccountName
                        if (entry.Properties["SamAccountName"].Count > 0)
                        {
                            DR["SamAccountName"] = entry.Properties["SamAccountName"].Value.ToString();
                        }
     
                        // Department
                        //if (entry.Properties["Department"].Count > 0)
                        //{
                        //    DR["Department"] = entry.Properties["Department"].Value.ToString();
                        //}
     
                        // Organization
                        //if (entry.Properties["Organization"].Count > 0)
                        //{
                        //    DR["Organization"] = Suppression_Caractere_accentue(entry.Properties["Organization"].Value.ToString());
                        //}
     
                        // DisplayName
                        if (entry.Properties["DisplayName"].Count > 0)
                        {
                            DR["DisplayName"] = Suppression_Caractere_accentue(entry.Properties["DisplayName"].Value.ToString());
                        }
     
                        // Ajout
                        DT.Rows.Add(DR);
                }
     
                // Export vers le fichier .csv
                filePath = Directory.GetCurrentDirectory() + @"\" + saisieOU + ".csv";
                CreateCSVFile(DT, filePath);
     
                // Fin du sablier
                Cursor.Current = Cursors.Default;
                MessageBox.Show("Export effectué vers " + filePath);
            }
    La fonction "Suppression_Caractere_accentue" :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    //Le but de cette fonction est de remplacer un ensemble de caractères prédéfinis par d'autres dans un string.
            private string Suppression_Caractere_accentue(string Texte_a_corriger)
            {
                //Conversion du string en tableau de char
                char[] ligne_char = Texte_a_corriger.ToCharArray();
                int mchar;
                for (int i = 0; i < Texte_a_corriger.Length; i++)
                {
                    //Conversion du caractere en int
                    mchar = (int)ligne_char[i];
                    // Remplacement des accents par a
                    if (mchar >= 224 && mchar <= 230)
                    {
                        ligne_char[i] = 'a'; //(char)97;
                    }
                    else
                    // Remplacement des accents par e
                    if (mchar >= 232 && mchar <= 235)
                    {
                        ligne_char[i] = 'e'; //(char)101;
                    }
                    else
                    // Remplacement des accents par i
                    if (mchar >= 236 && mchar <= 239)
                    {
                        ligne_char[i] = 'i'; //(char)105;
                    }
                    else
                    // Remplacement des accents par o
                    if (mchar >= 242 && mchar <= 248)
                    {
                        ligne_char[i] = 'o'; //(char)111;
                    }
                    else
                    // Remplacement des accents par u
                    if (mchar >= 249 && mchar <= 252)
                    {
                        ligne_char[i] = 'u'; //(char)117;
                    }
                    else
                    // Remplacement des ç par c
                    if (mchar == 231)
                    {
                        ligne_char[i] = 'c'; //(char)99;
                    }
                    else
                    // Remplacement des Ð par d
                    if (mchar == 208)
                    {
                        ligne_char[i] = 'd'; //(char)100;
                    }
                    else
                    // Remplacement des Ñ par n
                    if (mchar == 241)
                    {
                        ligne_char[i] = 'n'; //(char)110;
                    }
                    else
                    // Remplacement des Ý par y
                    if (mchar == 253 || mchar == 255)
                    {
                        ligne_char[i] = 'y'; //(char)121;
                    }
                    else
                    // Remplacement des ° par ' '
                    if (mchar == 176)
                    {
                        ligne_char[i] = ' ';
                    }
                }
                // Conversion du tableau de char[] en string
                return new string(ligne_char);
            }
    La fonction "CreateCSVFile" :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    // Fonction de création et d'écriture dans le fichier .csv
            public void CreateCSVFile(DataTable DT, string strPath)
            {
                try
                {
                    StreamWriter SW = new StreamWriter(strPath, false);
                    int columnCount = DT.Columns.Count;
                    for (int i = 0; i < columnCount; i++)
                    {
                        SW.Write(DT.Columns[i]);
                        if (i < columnCount - 1)
                        {
                            SW.Write(";");
                        }
                    }
                    SW.Write(SW.NewLine);
     
                    foreach (DataRow dr in DT.Rows)
                    {
                        for (int i = 0; i < columnCount; i++)
                        {
                            if (!Convert.IsDBNull(dr[i]))
                            {
                                SW.Write(dr[i].ToString());
                            }
                            if (i < columnCount - 1)
                            {
                                SW.Write(";");
                            }
                        }
                        SW.Write(SW.NewLine);
                    }
                    SW.Close();
                }
                catch
                {
                    throw;
                }
            }
    Merci par avance

  2. #2
    Max
    Max est déconnecté
    Expert confirmé

    Avatar de Max
    Homme Profil pro
    Artisan développeur
    Inscrit en
    Mai 2007
    Messages
    2 954
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Pyrénées Atlantiques (Aquitaine)

    Informations professionnelles :
    Activité : Artisan développeur
    Secteur : Industrie

    Informations forums :
    Inscription : Mai 2007
    Messages : 2 954
    Par défaut
    Salut.

    Plusieurs éléments à faire évoluer.

    Le premier concerne le test du type suivant :
    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    if (entry.Properties["XXX"].Count > 0)
    Un Count > 0 est par définition un test coûteux. Prenons un exemple générique simple avec une liste de 50000 éléments :
    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    if (myList.Any()) // En interne, dès qu'il voit un élément, il s'arrête puisqu'y il a bien quelque chose
    if (myList.Count > 0) // En interne, le Count va d'abord être fait, soit une énumération de 50000 éléments, puis le test > 0
    Je te laisse deviner lequel des deux tests est le plus rapide .

    Dans ton cas, cela ne s'applique pas de cette façon mais ça valait le coup d'être dit. Tu peux donc remplacer ton test par le test suivant :
    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    if (entry.Properties["XXX"].Value != null)
    Si on était dans le cadre d'une API plus générique, on devrait même faire un test plus rigoureux du type :
    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    if (entry.Properties["XXX"] != null && entry.Properties["XXX"].Value != null)
    Dans ton cas, comme tu demandes explicitement que la propriété "XXX" soit chargée :
    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    DS.PropertiesToLoad.Add("XXX");
    Tu es certain que entry.Properties["XXX"] ne sera pas null et peux donc éviter un premier test inutile.

    Deuxième point, une petite erreur que l'on trouve souvent (que j'ai moi même faite et découverte à cette occasion et compris ici ). Tu fais l'effort de spécifier les propriétés dont tu as besoin via DS.PropertiesToLoad.Add("XXX") afin de faire une requête la plus légère possible, mais en appelant SR.GetDirectoryEntry(), à chaque itération de la boucle tu recharges intégralement ton entrée (cf. mon explication dans les liens précédents) !

    Si on met de côté ton traitement des accents, ta boucle devient minuscule :
    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    foreach (SearchResult SR in results) {
        DataRow DR = DT.NewRow();
        foreach (string currentName in SR.Properties.PropertyNames) {
            DR[currentName] = (string)SR.Properties[currentName][0];
        }
        DT.Rows.Add(DR);
    }

    Du coup, avec tous ces tests et explications (et autres bouts de code à disposition), j'ai presque un exemple basique complet à te proposer . À étudier et pas recopier bêtement :

    Classe Person :
    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    public class Person {
        public static readonly string PropertySurname = "sn";
        public static readonly string PropertyGivenName = "givenname";
        public static readonly string PropertySamAccountName = "samaccountname";
        public static readonly string PropertyDisplayName = "displayname";
        public static readonly string DegreeSymbol = "°";
        public static readonly string Whitespace = " ";
        private const string Semicolon = ";";
        // En gérant ça ici, tout le reste est "dynamique" : tu peux ajouter/supprimer des colonnes ici sans modifier autre chose ailleurs (utilisé également pour l'ordre des colonnes dans le CSV)
        public static readonly IList<string> NeededOrderedColumns = new List<string>() {
            Person.PropertySurname,
            Person.PropertyGivenName,
            Person.PropertySamAccountName,
            Person.PropertyDisplayName
        };
        public Person() {
            this.Properties = new Dictionary<string, string>();
            foreach (var currentProperty in Person.NeededOrderedColumns) {
                // Utile pour plus tard, on est certain que le dictionnaire contiendra l'index donné
                this.Properties[currentProperty] = string.Empty;
            }
        }
        public string ToCsvLine() {
            StringBuilder currentLine = new StringBuilder();
            foreach (var currentProperty in Person.NeededOrderedColumns) {
                if (this.Properties[currentProperty] != null) {
                    currentLine.Append(this.Properties[currentProperty]);
                }
                currentLine.Append(Person.Semicolon);
            }
            return currentLine.ToString();
        }
        public static string GetCsvHeader() {
            StringBuilder currentLine = new StringBuilder();
            foreach (var currentProperty in Person.NeededOrderedColumns) {
                currentLine.Append(currentProperty);
                currentLine.Append(Person.Semicolon);
            }
            return currentLine.ToString();
        }
        // Devrait être du <string, object> puisqu'on peut récupérer des types différents depuis l'AD, mais <string, string> suffit dans ton cas
        public IDictionary<string, string> Properties { get; set; }
    }
    Classe de test :
    Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    public class TestClass {
     
        public static void Test() {
            string path = @"C:\...\greenangel.csv";
            TestClass.WriteCsv(TestClass.GetPersons(), path, true);
        }
     
        // Truc bourrin qui enlève tous les accents du marché :)
        public static string RemoveDiatrics(string input) {
            string inputFormD = input.Normalize(NormalizationForm.FormD);
            StringBuilder builder = new StringBuilder();
            UnicodeCategory tempUnicodeCategory;
            inputFormD.ToList().ForEach(currentChar => {
                tempUnicodeCategory = CharUnicodeInfo.GetUnicodeCategory(currentChar);
                if (tempUnicodeCategory != UnicodeCategory.NonSpacingMark) {
                    builder.Append(currentChar);
                }
            }
            );
            return builder.ToString();
        }
     
        public static void WriteCsv(IList<Person> persons, string filePath, bool includeHeader) {
            try {
                using (StreamWriter writer = new StreamWriter(filePath)) {
                    if (includeHeader) {
                        writer.WriteLine(Person.GetCsvHeader());
                    }
                    foreach (var currentPerson in persons) {
                        writer.WriteLine(currentPerson.ToCsvLine());
                    }
                }
            }
            catch (Exception) {
                throw;
            }
        }
     
        // Ne mettra pas le header CSV
        public static void WriteCsv(IList<Person> persons, string filePath) {
            TestClass.WriteCsv(persons, filePath, false);
        }
     
        public static IList<Person> GetPersons() {
            string sourceBase = "LDAP://...";
            DirectoryEntry ouEntry = ...;   
            SearchResultCollection results;
            IList<Person> persons = new List<Person>();
            using (DirectorySearcher searcher = new DirectorySearcher(ouEntry)) {
                foreach (var currentProperty in Person.NeededOrderedColumns) {
                    // On charge dynamiquement les propriétés nécessaires
                    searcher.PropertiesToLoad.Add(currentProperty);
                }
                searcher.Filter = "(&(objectCategory=person)(objectClass=user))";
                results = searcher.FindAll();
                // On définit quelles sont celles dont on doit virer les accents et le °
                IList<string> propertiesToRemoveDiatrics = new List<string>() {
                    Person.PropertyGivenName,
                    Person.PropertyDisplayName
                };
                Person tempPerson;
                foreach (SearchResult currentResult in results) {
                    tempPerson = new Person();
                    foreach (string currentName in currentResult.Properties.PropertyNames) {
                        tempPerson.Properties[currentName] = propertiesToRemoveDiatrics.Contains(currentName) ? TestClass.RemoveDiatrics((string)currentResult.Properties[currentName][0]).Replace(Person.DegreeSymbol, Person.Whitespace) : (string)currentResult.Properties[currentName][0];
                    }
                    persons.Add(tempPerson);
                }
            }
            return persons;
        }
    }

    Pour l'appeler : TestClass.Test();

    Voilà, pavé César . J'espère que ce te sera utile .

    D'après ton profil, le développement n'est pas ton cœur de métier, du coup j'en profite pour te glisser les quelques conseils et bonnes pratiques suivants :

    • dès que tu utilises plus d'une fois une chaîne de caractère (par exemple "GivenName" dans ton cas), utilise toujours une constante : si du dois modifier cette chaîne ultérieurement, tu n'auras qu'une seule modification à faire, alors que tu n'es jamais à l'abri d'en oublier une si une chaîne est présente à plusieurs endroits ;
    • attention aux conventions de nommage C# : les respecter c'est rendre ton code plus lisible (et augmenter tes chances de te faire aider parce que quand c'est moche on n'a pas envie de lire ) ;
    • entry.Properties["XXX"].Value.ToString() : ToString() c'est le mal ! En tout cas ici c'est un parfait exemple de mauvaise utilisation . Il faut caster dans ton cas : (string)entry.Properties["XXX"].Value. Le ToString() ça donne une représentation texte d'un objet de ce qu'a voulu le développeur, ce n'est pas fait pour transformer un objet en string() (dans ton cas ça fonctionne vu que cela en est déjà un). Exemple farfelu mais valable :
      Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      public class Car {
          public string BrandName { get; set; }
          public string ColorName { get; set; }
          public override string ToString() {
              return "I'm a " + this.ColorName + "car from the " + this.BrandName + " brand";
          }
      }
    • point purement subjectif : la combinaison DataTable DataRow est un poil lourde ici : la création d'un objet de type Person serait mieux selon moi

Discussions similaires

  1. Exporter ListView vers CSV
    Par Zhamy dans le forum C#
    Réponses: 4
    Dernier message: 22/10/2011, 23h19
  2. [MySQL] Exporter table vers csv via PhpMyAdmin
    Par mikael2235 dans le forum PHP & Base de données
    Réponses: 3
    Dernier message: 09/06/2010, 20h47
  3. Export Delphi vers Excel très lent
    Par Sandara dans le forum Langage
    Réponses: 14
    Dernier message: 22/02/2008, 11h27
  4. Export oracle vers csv
    Par MikeM dans le forum SQL
    Réponses: 5
    Dernier message: 11/02/2008, 14h11
  5. [SQL] exportation php vers csv
    Par yveslens dans le forum PHP & Base de données
    Réponses: 8
    Dernier message: 10/02/2007, 07h06

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo