stride3d/stride · error · InvalidOperationException

The asset directory cannot be null before deleting an asset.

Error message

The asset directory cannot be null before deleting an asset.

What it means

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.

Solutions

  1. Ensure every asset has a valid Directory before invoking delete; re-attach or reload assets with a null Directory.
  2. Filter out (or fix) assets with null Directory before starting the delete loop: assets.Where(a => a.Directory != null).
  3. Refresh/rebuild the asset collection view model from the session to repair stale directory references.

Example fix

// before
var toDelete = assets.ToList();
collectionViewModel.DeleteAssets(toDelete);
// after
var toDelete = assets.Where(a => a.Directory != null).ToList();
collectionViewModel.DeleteAssets(toDelete);
Defensive patterns

Strategy: validation

Validate before calling

if (assets.Any(a => a.Directory == null)) throw new InvalidOperationException("All assets must be attached to a directory before deletion");

Type guard

bool IsDeletable(AssetViewModel a) => a.Directory != null;

Try / catch

try { collection.DeleteAssets(assets); }
catch (InvalidOperationException ex) { logger.Error(ex, "Asset with null directory encountered during delete; refresh session"); }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/f786934a540c8d41. Report an issue: GitHub.

Appendix: source

Thrown at sources/editor/Stride.Core.Assets.Editor/ViewModel/AssetCollectionViewModel.cs:443

            UpdateAssetsCollection(newAssets, true);
        }

        /// <summary>
        /// Deletes the given assets in a single transaction without asking for confirmation nor fixing broken references.
        /// Assets whose <see cref="AssetViewModel.CanDelete()"/> method returns <c>false</c> won't be deleted, unless <paramref name="forceDelete"/> is <c>true</c>.
        /// </summary>
        /// <param name="assetsToDelete">The list of assets to delete.</param>
        /// <param name="forceDelete">If <c>true</c> the asset whose <see cref="AssetViewModel.CanDelete()"/> method returns <c>false</c> will still be deleted</param>
        /// <returns>The number of assets that have been successfully deleted.</returns>
        internal int DeleteAssets(IEnumerable<AssetViewModel> assetsToDelete, bool forceDelete = false)
        {
            using (var transaction = Session.UndoRedoService.CreateTransaction())
            {
                var deletedAssets = new List<AssetViewModel>();
                foreach (var asset in assetsToDelete.Where(x => forceDelete || x.CanDelete()))
                {
                    if (asset.Directory == null)
                        throw new InvalidOperationException("The asset directory cannot be null before deleting an asset.");

                    if (!forceDelete && !asset.CanDelete())
                        continue;

                    // This must be done before we clear the Directory property of the asset
                    AssetDependenciesViewModel.NotifyAssetChanged(asset.Session, asset);

                    var oldDirectory = asset.Directory;

                    // It is important to set IsDeleted before clearing the directory, so the parent project can be marked as dirty
                    asset.IsDeleted = true;
                    asset.Directory.RemoveAsset(asset);
                    asset.Directory = null;

                    // Update RootAssets, for both current package and packages referencing this one
                    // Note: Package to Asset references should be handled in a more generic way (same as Asset to Asset references)
                    // We check only local
                    foreach (var localPackage in Session.LocalPackages)

View on GitHub (pinned to 96fad776d2)