Description
Is there an existing issue for this?
- I have searched the existing issues
Description of problem
Searching Global Assets (or any folder tree) on large portals causes thousands of database round-trips and saturates CPU.
Repro (customer scale)
- Portal with ≈ 67 000 files in ≈ 2 700 folders.
- Open Global Assets → search for a single letter (e.g. “d”).
- Runtime ≈ 60 s, server at 100 % CPU. Logs show ~2 700 calls to
dbo.GetFiles
with@Recursive = 0
.
Why it happens
FolderManager.SearchFiles
always calls GetFiles(folderId, false, false)
and then iterates every sub-folder in C#, producing one DB hit per folder. Each individual query is cheap; the volume is the bottleneck.
Description of solution
Refactor FolderManager.SearchFiles
to pass the recursive flag straight through to the stored procedure, then filter the single result set in memory while respecting permissions.
private IEnumerable<IFileInfo> SearchFiles(IFolderInfo folder, Regex regex, bool recursive)
{
var files = CBO.Instance.FillCollection<FileInfo>(
DataProvider.Instance().GetFiles(folder.FolderID, false, recursive));
if (!recursive)
return files.Where(f => regex.IsMatch(f.FileName)).Cast<IFileInfo>();
var allowed = GetFolders(folder.PortalID)
.Where(f => f.FolderPath.StartsWith(folder.FolderPath) &&
FolderPermissionController.Instance.CanViewFolder(f))
.Select(f => f.FolderPath)
.ToHashSet();
return files
.Where(f => regex.IsMatch(f.FileName) && allowed.Contains(f.Folder))
.Cast<IFileInfo>();
}
No schema or stored-procedure changes required.
Description of alternatives considered
Alternative | Outcome |
---|---|
Additional indexes (IX_Folders_PortalPath , IX_Files_PortalID_FolderID_INC ) |
≈ 7 % fewer reads but still ~60 s runtime. |
Modify the GetFiles SP | Larger impact radius. |
Anything else?
Observation
It’s likely that the SearchFiles
function was written this way to enforce per-folder permission checks.
You can achieve the same result more efficiently by first retrieving all files and then filtering by permissions in C#.
Current implementation
foreach (var subFolder in this.GetFolders(folder))
{
if (FolderPermissionController.Instance.CanViewFolder(subFolder))
{
files.AddRange(this.SearchFiles(subFolder, regex, true));
}
}
Suggested implementation
return fileCollection
.Where(f => regex.IsMatch(f.FileName) &&
allowedFolderPaths.Contains(f.Folder))
.Cast<IFileInfo>();
Key differences
Aspect | Current approach | Suggested approach |
---|---|---|
When permissions are checked | Before each recursive search | After collecting all files |
Performance impact | Multiple recursive calls; may be slower on deep trees | Single pass through file list; simpler and faster |
Readability | Nested logic inside SearchFiles |
Clear LINQ pipeline with explicit filters |
Both methods honor folder-level permissions, but the suggested version decouples file retrieval from permission evaluation, leading to improved performance.
Current Implementation Takes Up To 1 Minute To Search "d" on an installation with ~2k folders and ~60k files
DNN_Search_For_d_57_secs_original.mp4
Do you be plan to contribute code for this enhancement?
- Yes
Would you be interested in sponsoring this enhancement?
- Yes
Code of Conduct
- I agree to follow this project's Code of Conduct