private async Task <CSEntryChangeResult> PutCSEntryChangeAdd(CSEntryChange csentry)
        {
            string teamid = csentry.GetValueAdd <string>("team");

            if (teamid == null)
            {
                logger.Error($"Export of item {csentry.DN} failed as no 'team' attribute was provided\r\n{string.Join(",", csentry.ChangedAttributeNames)}");
                return(CSEntryChangeResult.Create(csentry.Identifier, null, MAExportError.ExportActionRetryReferenceAttribute));
            }

            Beta.Channel c = new Beta.Channel();
            c.DisplayName = csentry.GetValueAdd <string>("displayName");
            c.Description = csentry.GetValueAdd <string>("description");

            // 2020-05-15 This currently doesn't come in with a GET request, so for now, it needs to be initial-flow only
            // https://github.com/microsoftgraph/microsoft-graph-docs/issues/6792
            c.IsFavoriteByDefault = csentry.HasAttributeChange("isFavoriteByDefault") && csentry.GetValueAdd <bool>("isFavoriteByDefault");

            if (csentry.ObjectType == "privateChannel")
            {
                if (!csentry.HasAttributeChangeAdd("owner"))
                {
                    throw new InvalidProvisioningStateException("At least one owner must be specified when creating a channel");
                }

                string ownerID = csentry.GetValueAdds <string>("owner").First();
                c.MembershipType = Beta.ChannelMembershipType.Private;
                c.AdditionalData = new Dictionary <string, object>();
                c.AdditionalData.Add("members", new[] { GraphHelperTeams.CreateAadUserConversationMember(ownerID, "owner") });
            }

            logger.Trace($"{csentry.DN}: Channel data: {JsonConvert.SerializeObject(c)}");

            var channel = await GraphHelperTeams.CreateChannel(this.betaClient, teamid, c, this.token);

            logger.Trace($"Created channel {channel.Id} for team {teamid}");

            if (csentry.ObjectType == "privateChannel")
            {
                await this.PutMemberChanges(csentry, teamid, channel.Id);
            }

            List <AttributeChange> anchorChanges = new List <AttributeChange>();

            anchorChanges.Add(AttributeChange.CreateAttributeAdd("id", channel.Id));
            anchorChanges.Add(AttributeChange.CreateAttributeAdd("teamid", teamid));

            return(CSEntryChangeResult.Create(csentry.Identifier, anchorChanges, MAExportError.Success));
        }
        private async Task PutMemberChanges(CSEntryChange csentry, string teamid, string channelid)
        {
            if (!csentry.HasAttributeChange("member") && !csentry.HasAttributeChange("owner"))
            {
                return;
            }

            IList <string> memberAdds    = csentry.GetValueAdds <string>("member");
            IList <string> memberDeletes = csentry.GetValueDeletes <string>("member");
            IList <string> ownerAdds     = csentry.GetValueAdds <string>("owner");
            IList <string> ownerDeletes  = csentry.GetValueDeletes <string>("owner");

            if (csentry.ObjectModificationType == ObjectModificationType.Add)
            {
                if (ownerAdds.Count > 0)
                {
                    ownerAdds.RemoveAt(0);
                }
            }

            if (csentry.HasAttributeChangeDelete("member") || csentry.HasAttributeChangeDelete("owner"))
            {
                var existingMembership = await GraphHelperTeams.GetChannelMembers(this.betaClient, teamid, channelid, this.token);

                if (csentry.HasAttributeChangeDelete("member"))
                {
                    memberDeletes = existingMembership.Where(t => !this.userFilter.ShouldExclude(t.Id, this.token) && (t.Roles == null || !t.Roles.Any(u => string.Equals(u, "owner", StringComparison.OrdinalIgnoreCase)))).Select(t => t.Id).ToList();
                }

                if (csentry.HasAttributeChangeDelete("owner"))
                {
                    ownerDeletes = existingMembership.Where(t => !this.userFilter.ShouldExclude(t.Id, this.token) && t.Roles != null && t.Roles.Any(u => string.Equals(u, "owner", StringComparison.OrdinalIgnoreCase))).Select(t => t.Id).ToList();
                }
            }

            var memberUpgradesToOwners = memberDeletes.Intersect(ownerAdds).ToList();

            foreach (var m in memberUpgradesToOwners)
            {
                memberDeletes.Remove(m);
                ownerAdds.Remove(m);
            }

            var ownerDowngradeToMembers = ownerDeletes.Intersect(memberAdds).ToList();

            foreach (var m in ownerDowngradeToMembers)
            {
                memberAdds.Remove(m);
                ownerDeletes.Remove(m);
            }

            List <Beta.AadUserConversationMember> cmToAdd    = new List <Beta.AadUserConversationMember>();
            List <Beta.AadUserConversationMember> cmToDelete = new List <Beta.AadUserConversationMember>();
            List <Beta.AadUserConversationMember> cmToUpdate = new List <Beta.AadUserConversationMember>();

            foreach (var m in memberDeletes)
            {
                cmToDelete.Add(GraphHelperTeams.CreateAadUserConversationMember(m));
            }

            // If we try to delete the last owner on a channel, the operation will fail. If we are swapping out the full set of owners (eg an add/delete of 100 owners), this will never succeed if we do a 'delete' operation first.
            // If we do an 'add' operation first, and the channel already has the maximum number of owners, the call will fail.
            // So the order of events should be to
            //    1) Process all membership removals except for one owner (100-99 = 1 owner)
            //    2) Process all membership adds except for one owner (1 + 99 = 100 owners)
            //    3) Remove the final owner (100 - 1 = 99 owners)
            //    4) Add the final owner (99 + 1 = 100 owners)

            string lastOwnerToRemove = null;

            if (ownerDeletes.Count > 0)
            {
                lastOwnerToRemove = ownerDeletes[0];
                ownerDeletes.RemoveAt(0);
            }

            string lastOwnerToAdd = null;

            if (ownerAdds.Count > 0)
            {
                lastOwnerToAdd = ownerAdds[0];
                ownerAdds.RemoveAt(0);
            }

            foreach (var m in ownerDeletes)
            {
                cmToDelete.Add(GraphHelperTeams.CreateAadUserConversationMember(m));
            }

            foreach (var m in memberAdds)
            {
                cmToAdd.Add(GraphHelperTeams.CreateAadUserConversationMember(m));
            }

            foreach (var m in ownerAdds)
            {
                cmToAdd.Add(GraphHelperTeams.CreateAadUserConversationMember(m, "owner"));
            }

            foreach (var m in memberUpgradesToOwners)
            {
                cmToUpdate.Add(GraphHelperTeams.CreateAadUserConversationMember(m, "owner"));
            }

            foreach (var m in ownerDowngradeToMembers)
            {
                cmToUpdate.Add(GraphHelperTeams.CreateAadUserConversationMember(m, (string[])null));
            }

            await GraphHelperTeams.RemoveChannelMembers(this.betaClient, teamid, channelid, cmToDelete, true, this.token);

            await GraphHelperTeams.UpdateChannelMembers(this.betaClient, teamid, channelid, cmToUpdate, this.token);

            await GraphHelperTeams.AddChannelMembers(this.betaClient, teamid, channelid, cmToAdd, true, this.token);

            if (lastOwnerToRemove != null)
            {
                cmToDelete.Clear();
                cmToDelete.Add(GraphHelperTeams.CreateAadUserConversationMember(lastOwnerToRemove));
                await GraphHelperTeams.RemoveChannelMembers(this.betaClient, teamid, channelid, cmToDelete, true, this.token);
            }

            if (lastOwnerToAdd != null)
            {
                cmToAdd.Clear();
                cmToAdd.Add(GraphHelperTeams.CreateAadUserConversationMember(lastOwnerToAdd, "owner"));
                await GraphHelperTeams.AddChannelMembers(this.betaClient, teamid, channelid, cmToAdd, true, this.token);
            }

            logger.Info($"Membership modification for channel {teamid}:{channelid} completed. Members added: {memberAdds.Count}, members removed: {memberDeletes.Count}, owners added: {ownerAdds.Count}, owners removed: {ownerDeletes.Count}, owners downgraded to members: {ownerDowngradeToMembers.Count}, members upgraded to owners: {memberUpgradesToOwners.Count}");
        }