{"record":{"id":"f786934a540c8d41","repo":"stride3d/stride","slug":"the-asset-directory-cannot-be-null-before-deleting-an-asset","errorCode":null,"errorMessage":"The asset directory cannot be null before deleting an asset.","messagePattern":"The asset directory cannot be null before deleting an asset\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/editor/Stride.Core.Assets.Editor/ViewModel/AssetCollectionViewModel.cs","lineNumber":443,"sourceCode":"            UpdateAssetsCollection(newAssets, true);\n        }\n\n        /// <summary>\n        /// Deletes the given assets in a single transaction without asking for confirmation nor fixing broken references.\n        /// Assets whose <see cref=\"AssetViewModel.CanDelete()\"/> method returns <c>false</c> won't be deleted, unless <paramref name=\"forceDelete\"/> is <c>true</c>.\n        /// </summary>\n        /// <param name=\"assetsToDelete\">The list of assets to delete.</param>\n        /// <param name=\"forceDelete\">If <c>true</c> the asset whose <see cref=\"AssetViewModel.CanDelete()\"/> method returns <c>false</c> will still be deleted</param>\n        /// <returns>The number of assets that have been successfully deleted.</returns>\n        internal int DeleteAssets(IEnumerable<AssetViewModel> assetsToDelete, bool forceDelete = false)\n        {\n            using (var transaction = Session.UndoRedoService.CreateTransaction())\n            {\n                var deletedAssets = new List<AssetViewModel>();\n                foreach (var asset in assetsToDelete.Where(x => forceDelete || x.CanDelete()))\n                {\n                    if (asset.Directory == null)\n                        throw new InvalidOperationException(\"The asset directory cannot be null before deleting an asset.\");\n\n                    if (!forceDelete && !asset.CanDelete())\n                        continue;\n\n                    // This must be done before we clear the Directory property of the asset\n                    AssetDependenciesViewModel.NotifyAssetChanged(asset.Session, asset);\n\n                    var oldDirectory = asset.Directory;\n\n                    // It is important to set IsDeleted before clearing the directory, so the parent project can be marked as dirty\n                    asset.IsDeleted = true;\n                    asset.Directory.RemoveAsset(asset);\n                    asset.Directory = null;\n\n                    // Update RootAssets, for both current package and packages referencing this one\n                    // Note: Package to Asset references should be handled in a more generic way (same as Asset to Asset references)\n                    // We check only local\n                    foreach (var localPackage in Session.LocalPackages)","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/editor/Stride.Core.Assets.Editor/ViewModel/AssetCollectionViewModel.cs#L425-L461","documentation":"During batch asset deletion inside an undo/redo transaction, each asset must still belong to a directory. If an AssetViewModel's Directory is null when the delete loop reaches it, the editor's internal invariant is broken (the asset should always know its directory before deletion), so an InvalidOperationException is thrown to abort the operation rather than corrupt the session.","triggerScenarios":"Calling the asset-collection delete operation (force or filtered by CanDelete) when one or more queued assets have had their Directory cleared or were created without being attached to a directory view model.","commonSituations":"Deleting assets that were just moved or unparented in a prior (possibly failed) transaction; assets whose parent directory was removed concurrently; session state corrupted by custom tooling that manipulates view models directly.","solutions":["Ensure every asset has a valid Directory before invoking delete; re-attach or reload assets with a null Directory.","Filter out (or fix) assets with null Directory before starting the delete loop: assets.Where(a => a.Directory != null).","Refresh/rebuild the asset collection view model from the session to repair stale directory references."],"exampleFix":"// before\nvar toDelete = assets.ToList();\ncollectionViewModel.DeleteAssets(toDelete);\n// after\nvar toDelete = assets.Where(a => a.Directory != null).ToList();\ncollectionViewModel.DeleteAssets(toDelete);","handlingStrategy":"validation","validationCode":"if (assets.Any(a => a.Directory == null)) throw new InvalidOperationException(\"All assets must be attached to a directory before deletion\");","typeGuard":"bool IsDeletable(AssetViewModel a) => a.Directory != null;","tryCatchPattern":"try { collection.DeleteAssets(assets); }\ncatch (InvalidOperationException ex) { logger.Error(ex, \"Asset with null directory encountered during delete; refresh session\"); }","preventionTips":["Never manipulate AssetViewModel.Directory directly outside undo transactions","Refresh the collection view model after failed move/copy operations","Pre-filter assets by Directory != null before batch operations"],"tags":["assets","null-reference","delete-operation","internal-state"],"backgroundTag":"null-argument","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}