Skip to content

Delete item rows when items leave the game#835

Merged
sven-n merged 1 commit into
MUnique:masterfrom
nolt:fix-item-row-leak
Jul 21, 2026
Merged

Delete item rows when items leave the game#835
sven-n merged 1 commit into
MUnique:masterfrom
nolt:fix-item-row-leak

Conversation

@nolt

@nolt nolt commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Two code paths remove an item from a storage without deleting it from the
database, so the row stays behind forever with a null ItemStorageId.

The problem

Storage.RemoveItemAsync only detaches the item from the storage's collection:

this.ItemStorage.Items.Remove(item);

Since the foreign key is optional, EF nulls ItemStorageId instead of deleting
the row. Most callers follow up with a delete — DestroyInventoryItemAsync
(consumed potions and jewels, mini-game tickets), BaseItemCraftingHandler
(crafting ingredients) and DropItemAction all do. Two do not:

  • SellItemToNpcAction.SellItemAsync — every item sold to a merchant
  • ItemStackAction — packing ten jewels into a stack leaves ten rows behind,
    unpacking leaves one

Impact

Measured on a live server that had been running for a few months with nine
player accounts: 8,511 of 10,179 rows (84%) in data."Item" were orphans.
After adding bots, which sell to merchants constantly, the count grew by
roughly 1,600 rows per day.

The rows are not usable as history — Item has no timestamp and no owner
field, and ItemStorageId was the only link to who held it. Nothing in the
code ever reads items with a null storage.

This is not an urgent performance problem at that scale, but it is unbounded
growth of rows that serve no purpose.

The fix

Delete the item right after removing it, matching the order already used by
DestroyInventoryItemAsync (remove → delete → notify the client):

await player.Inventory!.RemoveItemAsync(item).ConfigureAwait(false);
await player.PersistenceContext.DeleteAsync(item).ConfigureAwait(false);

Three lines in two files. No change to the view plugin calls, so nothing the
client sees is affected.

Notes for review

  • IItemSoldToMerchantPlugIn is invoked after the removal and receives the
    item. It has no implementations in the repository, and the deletion is only
    staged in the persistence context, so the object stays usable for the call.
    If a plugin ever wants to keep the sold item — a buy-back feature, say —
    that plugin point would need rethinking regardless of this change.
  • Paths that move an item between storages (MoveItemAction, player shop
    purchases in BuyRequestAction, returning items from temporary storage)
    are deliberately untouched: there the row must survive, only its owner
    changes.

Testing

  • MUnique.OpenMU.Tests: 570 passed, 18 failed.
  • The same 18 tests fail on unmodified master at the same commit
    (26fc908ba), so they are pre-existing and unrelated to this change. They
    are the Ancient, AncientWithoutBonus, Definition, Durability and
    Level group around item definitions.
  • Verified against a real server database: the orphaned rows are dominated by
    exactly what these two paths produce — jewels (Creation 323, Bless 261,
    Chaos 248, Life 204, Soul 175) and ordinary drops sold to merchants.

Removing an item from a storage only detaches it from the storage's item
collection, which leaves the row behind with a null ItemStorageId instead
of deleting it. Two paths did that without a following delete: selling to
an NPC, and packing or unpacking a stack of jewels.

Both are frequent actions, so the rows pile up: on a server running for a
few months with nine player accounts, 84% of all rows in the item table
were such leftovers, and adding bots raised the rate to roughly 1600 rows
per day. The rows carry no owner and no timestamp, so they are of no use
as a history either.

Delete the item after removing it, the way DestroyInventoryItemAsync
already does for consumed items, crafting ingredients and dropped items.
@nolt
nolt marked this pull request as ready for review July 20, 2026 13:25
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves an issue involving unbounded database growth caused by items that were detached from storage but never deleted. By ensuring that items are properly removed from the persistence context in all relevant code paths, the system will now correctly clean up item records when they are sold or stacked, preventing the accumulation of unnecessary data.

Highlights

  • Orphaned Database Rows: Addressed an issue where items removed from storage were not being deleted from the database, resulting in a significant accumulation of orphaned rows.
  • Persistence Cleanup: Updated ItemStackAction and SellItemToNpcAction to explicitly call DeleteAsync on items after they are removed from the player's inventory.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request ensures that items are properly deleted from the player's persistence context when they are stacked, unstacked, or sold to an NPC. Specifically, calls to player.PersistenceContext.DeleteAsync have been added after removing items from the inventory in ItemStackAction.cs and SellItemToNpcAction.cs. There are no review comments, so no additional feedback is provided.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@sven-n
sven-n merged commit c5eb47f into MUnique:master Jul 21, 2026
3 checks passed
@nolt
nolt deleted the fix-item-row-leak branch July 21, 2026 10:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants