You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用MS Graph API C#无法获取OneDrive指定文件夹全部文件和文件夹

问题:OneDrive指定文件夹下文件/文件夹统计数量与实际不符

实际(根目录/SGIM文件夹下):

  • 文件:62,683
  • 文件夹:13,598

程序统计结果:

  • 文件:25,887
  • 文件夹:13,576

程序无异常抛出,但统计数量少于实际,以下是代码片段:

private HttpClient oneDriveHttpClient;
public string AccessToken = "eyJ0eXAiOiJKV1QiLCJub25j........................";
public string RefreshToken = "...............................";
List<Item> allitems = new List<Item>();

public MainProgram()
{
    string requestUri = "/v1.0/users/d7ddb19e-xxxx-xxxx-xxxx-xxxxxxx/drive/root:/SGIM:/children";
    this.oneDriveHttpClient = new HttpClient()
    {
        BaseAddress = new Uri("https://graph.microsoft.com")
    };

    await GetOneDriveFolderView(requestUri);
}

public async Task GetOneDriveFolderView(string requestUri)
{
    string errorlog = Global.ErrorLog;
    ConcurrentQueue<string> pendingitems = new ConcurrentQueue<string>();
    List<Item> items = new List<Item>();

    try
    {
        pendingitems.Enqueue(requestUri);
    
        do
        {
            items = new List<Item>();

            try
            {
                pendingitems.TryDequeue(out requestUri);
                
                items = await GetOneDriveChildItems(requestUri);
                if (items != null && items.Count > 0)
                {
                    var folders = items.Where(s => s != null && s.Folder != null).ToList();

                    if (folders != null && folders.Count > 0)
                    {
                        foreach (var folder in folders)
                        {
                            string requestUri2 = "";
                            if (requestUri.EndsWith("/root/children"))
                            {
                                requestUri2 = requestUri.Replace(":/children", "").Replace("/children", "") + ":/" + Uri.EscapeUriString(folder.Name) + ":/children";
                            }
                            else
                            {
                                requestUri2 = requestUri.Replace(":/children", "").Replace("/children", "") + "/" + Uri.EscapeUriString(folder.Name) + ":/children";
                            }
                            
                            pendingitems.Enqueue(requestUri2);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                System.IO.File.AppendAllText(errorlog, "\r\nDateTime:" + DateTime.Now.ToString() +
                    "\r\nRequestUri" + requestUri  + "\r\n" + ex.Message + "\r\n" + ex.StackTrace);                            
            }
        }
        while (pendingitems.Count > 0);

        long totalcount = allitems.Count();
        long folderscount = allitems.Where(s => s.Folder != null).Count();
        long filescount = allitems.Where(s => s.File != null).Count();

        System.IO.File.AppendAllText(errorlog, "\r\nDateTime:" + DateTime.Now.ToString() +
            "\r\n\r\nFolders Count: " + folderscount +
            "\r\nFiles Count: " + filescount +
            "\r\nTotal Count: " + totalcount);
    }
    catch (Exception ex)
    {                
        System.IO.File.AppendAllText(errorlog, "\r\nDateTime:" + DateTime.Now.ToString() +
        "\r\n" + ex.Message + "\r\n" + ex.StackTrace);             
    }            
}


public async Task<List<Item>> GetOneDriveChildItems(string requestUri)
{
    List<Item> files = null;
    List<Item> tmpitems = new List<Item>();
    
    try
    {
        OneDriveResponse<Item[]> oneDriveResponse = null;

        do
        {
            oneDriveResponse = null;
            int retry = 3;
            for (int i = 1; i <= retry; i++)
            {
                try
                {
                    oneDriveResponse = await this.GetOneDriveItemSet<Item[]>(requestUri).ConfigureAwait(false);
                    break;
                }
                catch (Exception ex)
                {                         
                    System.IO.File.AppendAllText(errorlog, "\r\nDateTime:" + DateTime.Now.ToString() +
                    "\r\nRetry Attempt " + i.ToString() + " - FilesResource.ListRequest Execute For RequestURI " + requestUri + "\r\n" + ex.Message + "\r\n" + ex.StackTrace);
                 
                    Thread.Sleep(3000 * i);
                    oneDriveResponse = null;
                }

                if (i > retry)
                {
                    break;
                }
            }

            if (oneDriveResponse != null)
            {
                requestUri = oneDriveResponse.NextLink;
            }
            else
            {
                requestUri = null;
            }

            if (oneDriveResponse != null && oneDriveResponse.Value != null)
            {
                files = oneDriveResponse.Value.ToList();

                if (files != null && files.Count > 0)
                {                     
                    allitems.AddRange(files);
                    tmpitems.AddRange(files);                            
                }                        
            }
        } while (requestUri != null);
    }
    catch (Exception ex)
    {
        System.IO.File.AppendAllText(errorlog, "\r\nDateTime:" + DateTime.Now.ToString() +
        "\r\n" + ex.Message + "\r\n" + ex.StackTrace);                
    }

    return tmpitems;
}


private async Task<OneDriveResponse<T>> GetOneDriveItemSet<T>(string requestUri)
{   
    try
    {           
        // Send the request to OneDrive and get the response.
        var response = await this.SendOneDriveRequest(new HttpRequestMessage(HttpMethod.Get, requestUri)).ConfigureAwait(false);

        // Request was successful. Read the content returned.
        string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

        return JsonConvert.DeserializeObject<OneDriveResponse<T>>(content);
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

private async Task<HttpResponseMessage> SendOneDriveRequest(HttpRequestMessage request)
{            
    request.Headers.Authorization = new AuthenticationHeaderValue("bearer", this.AccessToken);
    var response = await this.oneDriveHttpClient.SendAsync(request).ConfigureAwait(false);

    // Check for token refresh
    if (response.StatusCode == HttpStatusCode.Unauthorized)
    {
        var authresult = new AuthenticationResult();
        authresult.Code = this.RefreshToken;
        TokenResponse tokenresponse = await GetAccessToken3(authresult);
        this.AccessToken = tokenresponse.AccessToken;
        this.RefreshToken = tokenresponse.RefreshToken;

        var newRequest = await request.Clone().ConfigureAwait(false);               
        newRequest.Headers.Authorization = new AuthenticationHeaderValue("bearer", this.AccessToken);
        response.Dispose();
        response = await this.oneDriveHttpClient.SendAsync(newRequest).ConfigureAwait(false);               
    }       

    // Any failures (including those from re-issuing after a refresh) will ne handled here
    if (!response.IsSuccessStatusCode)
    {
        throw OneDriveHttpException.FromResponse(response);
    }

    return response;
}


public class OneDriveResponse<T>
{
    [JsonProperty("@odata.context")]
    public string Context { get; set; }

    [JsonProperty("@odata.nextLink")]
    public string NextLink { get; set; }

    [JsonProperty("@odata.deltaLink")]
    public string DeltaLink { get; set; }

    [JsonProperty("@delta.token")]
    public string DeltaToken { get; set; }

    [JsonProperty("value")]
    public T Value { get; set; }

    public OneDriveResponse()
    {
    }

    public OneDriveResponse(T value)
    {
        this.Value = value;
    }
}


public class Item
{       
    [JsonProperty("id")]
    public string Id { get; set; }

    [JsonProperty("createdBy")]
    public IdentitySet CreatedBy { get; set; }

    [JsonProperty("createdDateTime")]
    public DateTime CreatedDateTime { get; set; }

    [JsonProperty("cTag")]
    public string CTag { get; set; }

    [JsonProperty("description")]
    public string Description { get; set; }

    [JsonProperty("eTag")]
    public string ETag { get; set; }

    [JsonProperty("file")]
    public FileFacet File { get; set; }

    [JsonProperty("fileSystemInfo")]
    public FileSystemInfoFacet FileSystemInfo { get; set; }

    [JsonProperty("folder")]
    public FolderFacet Folder { get; set; }

    [JsonProperty("lastModifiedBy")]
    public IdentitySet LastModifiedBy { get; set; }

    [JsonProperty("lastModifiedDateTime")]
    public DateTime? LastModifiedDateTime { get; set; }

    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("parentReference")]
    public ItemReference ParentReference { get; set; }

    [JsonProperty("size")]
    public long Size { get; set; }

    [JsonProperty("deleted")]
    public DeletedFacet Deleted { get; set; }

    public string PathDisplay { get; set; }
}

public class FileFacet
{
    [JsonProperty("mimeType")]
    public string MimeType { get; set; }

    [JsonProperty("hashes")]
    public HashesFacet Hashes { get; set; }
}


public class FolderFacet
{
    [JsonProperty("childCount")]
    public long ChildCount { get; set; }
}

排查出的核心问题

1. 文件夹路径拼接逻辑错误

在GetOneDriveFolderView中拼接子文件夹请求URI时,非根目录场景下使用/拼接会导致URI格式错误,部分文件夹的内容请求会失败(但未触发异常,因为错误被捕获后仅记录日志,程序继续执行),导致这部分文件夹下的文件全部丢失统计。

2. 集合线程安全问题

allitems使用普通List<Item>,在异步并发场景下,多个任务同时调用AddRange会导致集合内部结构损坏,直接造成元素丢失。List<T>本身不是线程安全集合,并发修改无锁保护必然出现数据丢失。

3. 重试逻辑无效且未处理失败场景

GetOneDriveChildItems中的if (i > retry)判断完全冗余(循环条件是i <= retry,永远不会触发);当三次重试都失败时,oneDriveResponse为null,程序直接跳过该文件夹的分页处理,导致该文件夹下所有内容都未被统计。

4. 未验证分页完整性

虽然代码处理了NextLink,但未确认@odata.nextLink是否被正确反序列化(虽然JsonProperty注解正确,但需确保响应中该字段存在时能被正常读取),若分页未被完整遍历,会丢失某一页的文件数据。

修复建议

  1. 修改文件夹请求方式:
    放弃路径拼接,改用文件夹Id构造请求URI,彻底避免路径格式错误:

    string requestUri2 = $"/v1.0/users/d7ddb19e-xxxx-xxxx-xxxx-xxxxxxx/drive/items/{folder.Id}/children";
    
  2. 替换为线程安全集合:
    将List<Item> allitems = new List<Item>()改为:

    ConcurrentBag<Item> allitems = new ConcurrentBag<Item>();
    

    或保留List但添加锁保护:

    lock(allitems)
    {
        allitems.AddRange(files);
        tmpitems.AddRange(files);
    }
    
  3. 修复重试逻辑:
    移除冗余的if (i > retry)判断,在重试结束后添加失败处理:

    if (oneDriveResponse == null)
    {
        System.IO.File.AppendAllText(errorlog, "\r\nDateTime:" + DateTime.Now.ToString() +
            "\r\n所有重试失败,请求URI:" + requestUri);
        break; // 终止该文件夹的分页遍历
    }
    
  4. 增强错误日志:
    在GetOneDriveFolderView的循环中,当items == null || items.Count == 0时记录日志,定位哪些文件夹未返回内容,辅助排查问题。

内容的提问来源于stack exchange,提问作者Gags

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 23:52:12