C# .net asp

專案要做一個道具系統 伺服器端
最基本就是必須有ID和Amount兩個欄位去紀錄所有道具的數量

目前想到的兩個方案
方案一 用道具編號當作欄位名稱 Amount記在這個欄位的值


方案二 個欄位叫做itemId記錄道具ID 有一個欄位叫value紀錄道具數量

目前想到及遇到的問題

關於方案一

問題一
因為道具種類會非常多 可能上百種
(例如會有item_78910 ~ item_526284, 也不一定是連續數字)
那欄位有數量限制嗎?
欄位數量過多會影響效能嗎?

問題二
要如何知道這個LCObject有幾個欄位?
目前查看LCObject上的函式沒看到有函式會把estimatedData的keys傳出來
從名稱上看有一個懷疑的函式GetUpdatedKeys
但是這個函式永遠都給我Null
主要會使用在客戶端向伺服器拉取所有道具時會用到
或是客戶端要顯示UI時要列出所有道具
沒有這個功能的話只能for從最小到最大檢查this["item_{id}"]!=null
覺得這樣不太合理

方案一的消耗道具程式寫法

        var user = await LCUser.BecomeWithSessionToken(LCEngineRequestContext.SessionToken);

        // 要消耗的道具及數量 <itemId, amount>
        var consumeItems = new Dictionary<int, int>()
        {
            { 123, 1 },
            { 456, 2 },
        };

        // 拉出objectId
        var query = new LCQuery<LCObject>(ItemDataClassName);
        query.WhereEqualTo("ownerId", user.ObjectId);
        query.Select("objectId");
        var itemData = await query.First();

        // 消耗道具
        var consumeRequest = LCObject.CreateWithoutData(ItemDataClassName, itemData.ObjectId);
        var condition = new LCQuery<LCObject>(ItemDataClassName);

        foreach (var kv in consumeItems)
        {
            var column = $"item_{kv.Key}";
            consumeRequest.Increment(column, -kv.Value);
            condition.WhereGreaterThanOrEqualTo(column, kv.Value);
        }

        try
        {
            var result = await consumeRequest.Save(true, condition);
            // 成功
            return "ok";
        }
        catch (Exception ex)
        {
            //  失敗 不足
            return "insufficient";
        }

關於方案二

問題一
這個方案很明顯在同時需要增減多種道具數量時要發送多個request
而且也不能使用Object.SaveAll去操作
原因是在使用道具有可能失敗 當其中一個道具使用失敗時 要把其他消耗的道具還原
這樣來來回回就會產生很多的request

問題二
承上一個問題
當同時操作多個LCObject時如果不能使用Object.SaveAll
那就有可能出現錯誤(429 信息 - Too many requests)
為了避免發生 必須一個一個道具線性的操作(或是有限度的平行化)
增長了整體API的反應時間,loading大時遇到超時的機會比較高

方案二的消耗道具程式寫法

        var user = await LCUser.BecomeWithSessionToken(LCEngineRequestContext.SessionToken);

        // 要消耗的道具及數量 <itemId, amount>
        var consumeItems = new Dictionary<int, int>()
        {
            { 123, 1 },
            { 456, 2 },
        };

        // 拉出objectId
        var query = new LCQuery<LCObject>(ItemDataClassName2);
        query.WhereEqualTo("ownerId", user.ObjectId);
        query.WhereContainedIn("itemId", consumeItems.Keys);
        var itemDatas = new List<LCObject>(await query.Find());

        // 消耗道具
        var failed = false;
        var succeedItems = new List<ValueTuple<int, string>>(); // itemId , lc objectId
        foreach (var kv in consumeItems)
        {
            try
            {
                var itemId = kv.Key;
                var amount = kv.Value;
                var itemData = itemDatas.Find(obj => itemId == (int)obj["itemId"]);

                var consumeRequest = LCObject.CreateWithoutData(ItemDataClassName2, itemData.ObjectId);
                var condition = new LCQuery<LCObject>(ItemDataClassName2);
                consumeRequest.Increment("value", -amount);
                condition.WhereGreaterThanOrEqualTo("value", amount);

                var result = await consumeRequest.Save(true, condition);
                // success
                succeedItems.Add((itemId, itemData.ObjectId));
            }
            catch (Exception ex)
            {
                //  失敗 不足
                failed = true;
                break;
            }
        }

        if (failed)
        {
            // 還原已扣除
            foreach (var pair in succeedItems)
            {
                var itemId = pair.Item1;
                var objectId = pair.Item2;
                var revertRequest = LCObject.CreateWithoutData(ItemDataClassName2, objectId);
                revertRequest.Increment("value", consumeItems[itemId]);
                await revertRequest.Save();
            }
            //  失敗 不足
            return "insufficient";
        }
        else
        {
            // 成功
            return "ok";
        }

請問使用哪種思路會比較適合
還是有更好的思路 歡迎提出來
謝謝指教

每个 Class 最多可以有 300 个字段。如果道具种类的数量不可控的话,方案一就要被排除掉了。

方案二的话确实会有您说的这些问题。其实如果能确保不会有多个请求同时更新同一个 owner 的数据的话,可以考虑把每个 owner 的所有道具存到一个对象中。这个对象可以只包含两个字段,一个字段用来保存 owner,另一个字段使用一个类似下面的数组来保存这个 owner 的所有道具:

[
  {
    "id": "123",
    "count": 2
  },
  {
    "id": "456",
    "count": 4
  }
]

如果选择这个做法,需要注意两个问题:

  • 如果有多个请求(可能来自不同客户端)同时更新同一个对象,可能会导致数据丢失。
  • 每个对象的大小不能超过 128 KB。您需要根据道具种类的数量上限来评估对象大小是否有可能达到这个限制。

非常感謝
這個方案有考慮過 但是這是伺服器
一定要確保資料的正確性

● 如果有多个请求(可能来自不同客户端)同时更新同一个对象,可能会导致数据丢失。

光是這一點 就不能被接受了
就算是來自同一個客戶端也有可能因為不正常操作導致API被同時多次呼叫
甚至有人惡意修改客戶端來攻擊伺服器
如果最終可以讓使用者該消耗的道具卻沒消耗到而獲得利益
那絕對是不能被接受的