{"record":{"id":"7b73e921c710487e","repo":"fullstackhero/dotnet-starter-kit","slug":"file-not-found-restorefilecommandhandler","errorCode":null,"errorMessage":"file not found","messagePattern":"file not found","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Files/Modules.Files/Features/v1/RestoreFile/RestoreFileCommandHandler.cs","lineNumber":21,"sourceCode":"using FSH.Modules.Files.Data;\nusing Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Files.Features.v1.RestoreFile;\n\npublic sealed class RestoreFileCommandHandler(FilesDbContext db)\n    : ICommandHandler<RestoreFileCommand, Unit>\n{\n    public async ValueTask<Unit> Handle(RestoreFileCommand cmd, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(cmd);\n\n        // IgnoreQueryFilters because the SoftDelete filter would otherwise hide the row.\n        var f = await db.FileAssets\n            .IgnoreQueryFilters()\n            .FirstOrDefaultAsync(x => x.Id == cmd.FileAssetId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"file not found\");\n\n        if (!f.IsDeleted)\n        {\n            return Unit.Value; // idempotent — already live\n        }\n\n        f.Restore();\n        await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n        return Unit.Value;\n    }\n}\n","sourceCodeStart":3,"sourceCodeEnd":33,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Files/Modules.Files/Features/v1/RestoreFile/RestoreFileCommandHandler.cs#L3-L33","documentation":"NotFoundException('file not found') thrown by RestoreFileCommandHandler when no FileAsset row with cmd.FileAssetId exists — the query uses IgnoreQueryFilters() so even soft-deleted rows would be found; only a truly absent (or wrong-tenant-filtered) id yields null. Restore is a soft-delete reversal, and a missing target is surfaced as a 404.","triggerScenarios":"Calling restore with a file id that was never created, an id from another tenant (tenant query filter still applies), a truncated/mistyped GUID, or referencing a file permanently deleted (hard delete) so the row no longer exists at all.","commonSituations":"Client restoring from a stale list after the record was purged; copying ids across environments; cross-tenant id confusion in multi-tenant testing; users hitting restore twice after a hard-delete cleanup job removed the row.","solutions":["Verify the FileAssetId exists (list/get the file first) and belongs to the current tenant before restoring.","Fix the client to use the id returned by the files API rather than one captured from another context/environment.","If the file was hard-deleted, it cannot be restored — re-upload the file instead.","Ensure restore requests are always made within the tenant that owns the file."],"exampleFix":"// before\nawait restoreFile({ fileAssetId: staleIdFromOtherEnv }); // 404 file not found\n// after\nconst file = await getFileAsset(fileAssetId); // confirms existence in this tenant\nif (file?.isDeleted) await restoreFile({ fileAssetId: file.id });","handlingStrategy":"try-catch","validationCode":"const file = await getFileAsset(fileAssetId); // 404 here means nothing to restore\nif (!file?.isDeleted) return; // idempotent — nothing to do","typeGuard":"function isRestorable(f) { return f != null && f.isDeleted === true; }","tryCatchPattern":"try { await restoreFile({ fileAssetId }); } catch (e) { if (e.status === 404) { refreshList(); showToast(\"File no longer exists\"); } else throw e; }","preventionTips":["Refresh file lists after mutations so ids are never stale.","Scope all file ids to the current tenant; never reuse ids across environments.","Treat restore as idempotent on the client: only call it when the file is known to be soft-deleted."],"tags":["files","not-found","soft-delete","multitenancy"],"backgroundTag":"record-not-found","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}