AAD GraphClient API add membership very slow

606 views Asked by At

I' am testing a console application realizing a synchronization between a sql server database and an Azure Active Directory (group, user, group-to-group relationship, person-to-group relationship)

I've found that adding users to a group is very slow. For information I have a volumetry of about 200000 users to attach to about 5000 groups.

Here is an sample of my code:

public static Dictionary<string, string> AddUsersToGroup(string groupName, string groupId, Dictionary<string, string> personToAddNames)
{
    try
    {
        Dictionary<string, string> result = new Dictionary<string, string>();
        bool l_contextChanged = false;

        // Get the group from its name
        Group group = SearchGroup(groupName, groupId);

        if (group != null)
        {
            foreach (KeyValuePair<string, string> personName in personToAddNames)
            {
                // Get the user from its name
                User person = SearchUser(personName.Value, personName.Key);
                if (person != null)
                {
                    try
                    {
                        // check if the user already belongs to the group
                        if (_fullSync || !ExistUserInGroup(group, person))
                        {
                            group.Members.Add(person);
                            l_contextChanged = true;
                        }

                        // Add the result to the dictionary (Argos Id + AAD Id)
                        result.Add(personName.Key, person.ObjectId);
                    }
                    catch (Exception e)
                    {
                    ...
                    }
                }
            }

            // Save all the modifications on the group
            if (l_contextChanged)
            {
                group.UpdateAsync().Wait();
            }
        }

        return result;
    }
    catch (Exception e)
    {
        ...
    }
}

Is there another method for adding users to a group that would be faster?

UPDATE I realized a test by applying the asynchronous spots to the operations of the GraphClient API but without gain of performance: 1 hour for 6000 users.

It is considered that the number of persons to be added in a group is variable from 1 to X

Here is an sample of my code :

 public static Dictionary<string, string> AddUsersToGroup(string nomGroupe, string idGroupe, Dictionary<string, string> listeNomPersonneAAjouter)
    {
        try
        {
            Dictionary<string, string> resultat = new Dictionary<string, string>();
            bool l_contextChanged = true;

            // Obtient le groupe depuis son nom pour avoir son id
            Group groupePourAjoutUtilisateur = SearchGroup(nomGroupe, idGroupe);

            if (groupePourAjoutUtilisateur != null)
            {
                Dictionary<Task<string>, string> l_taskNomPersonne = new Dictionary<Task<string>, string>();
                foreach (KeyValuePair<string, string> nomMailPersonneAAjouter in listeNomPersonneAAjouter)
                {
                    l_taskNomPersonne.Add(AddUserToGroupAsync(groupePourAjoutUtilisateur, nomMailPersonneAAjouter.Key, nomMailPersonneAAjouter.Value), nomMailPersonneAAjouter.Key);
                }

                Task.WaitAll(l_taskNomPersonne.Keys.ToArray());

                foreach(KeyValuePair<Task<string>, string> l_task in l_taskNomPersonne)
                {
                    resultat.Add(l_task.Value, l_task.Key.Result);
                }

                groupePourAjoutUtilisateur.UpdateAsync().Wait();

            }

            return resultat;
        }
        catch (Exception e)
        {
            throw new ApplicationException(String.Format("Impossible d'ajouter la liste d'utilisateur au groupe {0} - {1} {2}",
                nomGroupe, e.Message, e.InnerException != null ? e.InnerException.Message : String.Empty));
        }
    }


public static async Task<string> AddUserToGroupAsync(Group groupePourAjoutUtilisateur, string nomPersonneAAjouter, string mailPersonneAAjouter)
    {
        string l_return = string.Empty;

        // Obtient l'utilisateur depuis son nom pour avoir son id
        User personneAAjouter = SearchUser(mailPersonneAAjouter, nomPersonneAAjouter);
        if (personneAAjouter != null)
        {
            try
            {
                // On vérifie que la personne n'existe pas déjà dans le groupe 
                // si on est en exécution différentielle
                if (_fullSync || !ExistUserInGroup(groupePourAjoutUtilisateur, personneAAjouter))
                {
                    groupePourAjoutUtilisateur.Members.Add(personneAAjouter);
                }

                // retourne l'id AAD de la personne
                l_return = personneAAjouter.ObjectId;
            }
            catch (Exception e)
            {
                Logs.Logger.Error(String.Format("Une erreur s'est produite lors du rattachement de l'utilisateur {0} au groupe {1}",
                    personneAAjouter.DisplayName, groupePourAjoutUtilisateur.DisplayName),
                    new Exception(String.Format("{0} - {1}", e.Message, e.InnerException.Message)));
            }
        }

        return l_return;
    }
1

There are 1 answers

1
Fei Xue On

How did you call this method? Since the Azure Graph client is using the HTTP request to perform the REST API, you may consider using the asynchronous coding. And we also can using the multi-threads to improve the performance.

I also made a test to insert 200 users and add them into one group. It spend about 10 seconds.

Here is the code for your reference:

public async Task<string> AddUser(string accessToken, string displayName)
{            
        try
        {
            HttpClient client = new HttpClient();
            var user = new User
            {
                accountEnabled = true,
                displayName = displayName,
                mailNickname = displayName,
                passwordProfile = new PasswordProfile
                {
                    password = "asdf123(",
                    forceChangePasswordNextLogin = false
                },

                userPrincipalName = $"{displayName}@adfei3.onmicrosoft.com"
            };


            var bodyContent = new StringContent(new JavaScriptSerializer().Serialize(user), Encoding.UTF8, "application/json");

            client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", accessToken);

            HttpResponseMessage responseMessage = await client.PostAsync(addUserRequestUri, bodyContent);
            var responseString = await responseMessage.Content.ReadAsStringAsync();

            var objectId = JObject.Parse(responseString)["objectId"].Value<string>();
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine($"OK:{displayName}|Task{Thread.CurrentThread.ManagedThreadId}");
            return objectId;
        }
        catch (Exception ex)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"Failed:{displayName}|Task{Thread.CurrentThread.ManagedThreadId}");
            return "";
        }
}

public async Task AddMember(string accessToken, string groupId, string objectId)
{
        if (string.IsNullOrEmpty(objectId))
            return;
        try
        {
            HttpClient client = new HttpClient();


            var bodyContent = new StringContent(String.Format("{{\"url\": \"https://graph.windows.net/adfei3.onmicrosoft.com/directoryObjects/{0}\"}}", objectId), Encoding.UTF8, "application/json");

            client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", accessToken);

            Uri addMemberRequestUri = new Uri(String.Format(addMemberRequestString, groupId));

            HttpResponseMessage responseMessage = await client.PostAsync(addMemberRequestUri, bodyContent);

            if (responseMessage.StatusCode == System.Net.HttpStatusCode.NoContent)
            //return new MemberInfo { GroupId = groupId, UserInfo = userInfo, OK = true };
            {
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine($"OK:{objectId}|Task{Thread.CurrentThread.ManagedThreadId}");
            }
            else
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"Failed:{objectId}|Task{Thread.CurrentThread.ManagedThreadId}");
            }
        }
        catch (Exception ex)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"Failed:{objectId}|Task{Thread.CurrentThread.ManagedThreadId}");
        }
}

public void Test()
{
        DateTime beginTime = DateTime.Now;
        string accessToken = "";
        string groupId = "92374923-09a2-4e74-a3f2-57bbbafacc8b";

        Parallel.ForEach(Enumerable.Range(1, 200),  i =>
        {
             AddUser(accessToken, "users" + i).ContinueWith(userId =>
            {
                AddMember(accessToken, groupId, userId.Result).ContinueWith(a=> {                     
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine($"Total time:{ DateTime.Now.Subtract(beginTime)}");
                });
            });
        });
}