如何用Microsoft Graph和类Linq语法查询SharePoint未导入文件?
针对你遇到的全量遍历文件导致性能下降的问题,以下是三种可行的实现方案,根据已导入文件的规模选择合适的方式:
方案1:小批量已导入文件时,服务端直接过滤
当已导入文件数量较少(几百个以内),可以通过Graph的$filter拼接多个ne条件,直接在服务端过滤掉已导入的文件,减少本地处理的数据量。
// 从数据库获取已导入文件名列表 List<string> importedFileNames = await GetImportedFileNamesFromDbAsync(); // 构建Graph过滤条件,注意转义文件名中的单引号 string filter = string.Join(" and ", importedFileNames.Select(name => $"fileName ne '{EscapeSingleQuotes(name)}'")); // 查询指定SharePoint文件夹下未导入的文件 var result = await graphClient.Sites["{site-id}"].Drives["{drive-id}"].Items["{folder-id}"].Children .Request() .Filter(filter) .Select("id, name, fileName, lastModifiedDateTime") // 仅获取必要字段,降低传输开销 .GetAsync(); List<DriveItem> unimportedFiles = result.CurrentPage.ToList(); // 处理分页结果 while (result.NextPageRequest != null) { result = await result.NextPageRequest.GetAsync(); unimportedFiles.AddRange(result.CurrentPage); } // 辅助方法:转义单引号避免Graph查询语法错误 string EscapeSingleQuotes(string input) => input.Replace("'", "''");
方案2:大批量已导入文件时,内存高效过滤
当已导入文件数量较多,拼接$filter会触发Graph的请求长度限制,此时先拉取所有文件(仅必要字段),再用HashSet做O(1)时间复杂度的存在性判断,比List.Contains的O(n)效率提升显著。
// 从数据库获取已导入文件名并转为HashSet,提升查询性能 HashSet<string> importedFileNamesSet = new HashSet<string>(await GetImportedFileNamesFromDbAsync(), StringComparer.OrdinalIgnoreCase); // 拉取文件夹下所有文件 var allFiles = new List<DriveItem>(); var result = await graphClient.Sites["{site-id}"].Drives["{drive-id}"].Items["{folder-id}"].Children .Request() .Select("id, name, fileName, lastModifiedDateTime") .GetAsync(); allFiles.AddRange(result.CurrentPage); while (result.NextPageRequest != null) { result = await result.NextPageRequest.GetAsync(); allFiles.AddRange(result.CurrentPage); } // 过滤未导入的文件 List<DriveItem> unimportedFiles = allFiles.Where(file => !importedFileNamesSet.Contains(file.FileName)).ToList();
方案3:最优方案——用Delta Query跟踪文件变更
如果你的需求是定期爬取新增/修改的文件,推荐使用Graph的Delta Query,它会返回自上次查询以来的所有变更(新增、修改、删除),无需全量遍历对比,性能最优。
// 从数据库获取上次保存的delta链接(首次查询时为null) string deltaLink = await GetLastDeltaLinkFromDbAsync(); IDriveItemDeltaCollectionPage deltaResult; if (string.IsNullOrEmpty(deltaLink)) { // 首次查询:初始化delta请求 deltaResult = await graphClient.Sites["{site-id}"].Drives["{drive-id}"].Items["{folder-id}"].Children .Delta() .Request() .Select("id, name, fileName, lastModifiedDateTime") .GetAsync(); } else { // 后续查询:使用上次的delta链接获取变更 deltaResult = await graphClient.Sites["{site-id}"].Drives["{drive-id}"].Items["{folder-id}"].Children .Delta() .Request() .WithUrl(deltaLink) .GetAsync(); } // 收集所有变更的文件 List<DriveItem> changedFiles = deltaResult.CurrentPage.ToList(); while (deltaResult.NextPageRequest != null) { deltaResult = await deltaResult.NextPageRequest.GetAsync(); changedFiles.AddRange(deltaResult.CurrentPage); } // 保存新的delta链接到数据库,供下次查询使用 if (deltaResult.AdditionalData.TryGetValue("@odata.deltaLink", out object newDeltaLink)) { await SaveDeltaLinkToDbAsync(newDeltaLink.ToString()); } // changedFiles即为自上次爬取以来新增/修改的文件,可直接导入数据库 // 若需排除已导入的修改文件,可结合方案2的HashSet进行二次过滤
注意事项
- 确保应用已配置
Files.Read.All或Sites.Read.All权限(根据场景选择) - 始终使用
Select指定必要字段,减少网络传输数据量 HashSet的字符串比较规则可根据需求调整大小写敏感性- Delta Query会返回删除的文件,若无需处理可过滤掉
deleted状态的项
内容的提问来源于stack exchange,提问作者Andrew Casey
相关产品推荐
相关产品推荐

