/// <summary>
        /// 可疊數量如果歸0->不刪除紀錄,直接更新成0(背包計算上已處理這塊)
        /// 不可疊->直接砍掉該筆紀錄
        /// </summary>
        /// <param name="uid"></param>
        /// <param name="items"></param>
        /// <param name="fromType"></param>
        /// <param name="from"></param>
        /// <returns></returns>
        public static async ETTask <EquipmentResult> DeleteEquipment(long uid, List <EquipmentInfo> items, EquipmentFrom fromType, int from, bool deleteAnyhow = false)
        {
            // 回傳結構
            var equipmentResult = new EquipmentResult();

            // 抓取使用者背包限制資料
            User user = await UserDataHelper.FindOneUser(uid);

            // 無使用者?
            if (user == null)
            {
                equipmentResult.error = ErrorCode.ERR_AccountDoesntExist;
                return(equipmentResult);
            }

            // 先把相關資料搜尋出來
            var eids    = items.Select(e => e.ConfigId).ToList();
            var results = await dbProxy.Query <Equipment>(entity => entity.uid == uid && eids.Contains(entity.configId));

            var equipments = results.OfType <Equipment>().ToList();

            // 用企劃表id進行分組
            var dictConfigId = OtherHelper.Group(equipments, entity => entity.configId);
            //// 用企劃表type進行分組(TODO:記得擋背包類型下最大存放數)
            //var dictConfigType = OtherHelper.Group(equipments, entity => entity.configType);

            // 批處理
            var saveBatch      = new List <ComponentWithId>();
            var logBatch       = new List <ComponentWithId>();
            var deleteBatch    = new List <long>();
            var refreshBagType = new List <int>();

            for (int i = 0; i < items.Count; i++)
            {
                var item = items[i];
                if (item.Count <= 0)
                {
                    equipmentResult.error = ErrorCode.ERR_EquipmentInvalidCount;
                    return(equipmentResult);
                }

                if (TryGetEquipmentConfig(item.ConfigId, out CharacterConfig equipmentConfig))
                {
                    // 道具是否可用?
                    if (!equipmentConfig.IsAvailable())
                    {
                        equipmentResult.error = ErrorCode.ERR_EquipmentUnavailable;
                        return(equipmentResult);
                    }

                    if (!dictConfigId.TryGetValue(equipmentConfig.Id, out var userEqs))
                    {
                        userEqs = new List <Equipment>(0);
                    }

                    // 道具是否可疊加?
                    if (equipmentConfig.IsStackEquipment())
                    {
                        var maxCount = equipmentConfig.MaxCountOnSlot;

                        // 可疊不該有兩筆以上的記錄
                        if (userEqs.Count > 1)
                        {
                            // 記在ErrorLog裡
                            Log.Error(new Exception($"ERR_EquipmentRecordError> Uid:{uid}, Type:{equipmentConfig.Type}, ConfigId:{equipmentConfig.Id}"));
                            await _MergeEquipment(uid, userEqs.ToArray());

                            // 合併資料後用遞迴再跑一次
                            return(await DeleteEquipment(uid, items.Skip(i).ToList(), fromType, from));
                        }
                        // 判斷有無記錄?
                        else if (userEqs.Count == 0)
                        {
                            equipmentResult.error = ErrorCode.ERR_EquipmentBagIsEmpty;
                            return(equipmentResult);
                        }
                        // 存在一筆紀錄了
                        else
                        {
                            var userEq = userEqs[0];
                            if (userEq.count < item.Count)
                            {
                                equipmentResult.error = ErrorCode.ERR_EquipmentNotEnough;
                                return(equipmentResult);
                            }
                            userEq.count -= item.Count;
                            saveBatch.Add(userEq);
                            var log = ComponentFactory.Create <EquipmentLog>();
                            log.from     = from;
                            log.fromType = fromType;
                            log.count    = item.Count;
                            var dbLog = _GetSaveEquipmentDBLog(uid, userEq, log, DBLog.LogType.SubtractEquipment);
                            logBatch.Add(dbLog);
                        }
                    }
                    else
                    {
                        if (deleteAnyhow)
                        {
                            var configs = userEqs;
                            if (configs.Count < item.Count)
                            {
                                equipmentResult.error = ErrorCode.ERR_EquipmentNotEnough;
                                return(equipmentResult);
                            }
                            for (int k = 0; k < item.Count; k++)
                            {
                                var config = configs[k];
                                if (!deleteBatch.Contains(config.Id))
                                {
                                    deleteBatch.Add(config.Id);
                                }
                            }
                        }
                        else
                        {
                            if (!deleteBatch.Contains(item.Id))
                            {
                                deleteBatch.Add(item.Id);
                            }
                        }
                    }

                    if (!refreshBagType.Contains(equipmentConfig.Type))
                    {
                        refreshBagType.Add(equipmentConfig.Type);
                    }
                }
                else
                {
                    equipmentResult.error = ErrorCode.ERR_EquipmentNotDefined;
                    return(equipmentResult);
                }
            }

            var result = new RepeatedField <EquipmentInfo>();

            // 批刪除
            // 不可疊加就根據Id直接砍,Log額外再紀錄
            if (deleteBatch.Count != 0)
            {
                foreach (var v in dictConfigId)
                {
                    var dels   = OtherHelper.SearchAll(v.Value, entity => deleteBatch.Contains(entity.Id));
                    var delLog = ComponentFactory.Create <EquipmentLog>();
                    delLog.from     = from;
                    delLog.fromType = fromType;
                    var dbLog = _GetDeleteBatchNoStackEquipmentDBLog(uid, dels, delLog);
                    logBatch.Add(dbLog);
                }
                await dbProxy.DeleteJson <Equipment>(entity => deleteBatch.Contains(entity.Id));

                foreach (var id in deleteBatch)
                {
                    result.Add(new EquipmentInfo
                    {
                        Id    = id,
                        Count = 0,
                    });
                }
            }

            // 批儲存資料
            if (saveBatch.Count != 0)
            {
                await dbProxy.SaveBatch(saveBatch);

                foreach (var item in saveBatch)
                {
                    result.Add(((Equipment)item).ToEquipmentInfo());
                }
            }

            // 批儲存Log
            if (logBatch.Count != 0)
            {
                await dbProxy.SaveBatch(logBatch);
            }

            if (refreshBagType.Count != 0)
            {
                await user.RecalculateBagCount(refreshBagType);
            }

            equipmentResult.error          = ErrorCode.ERR_Success;
            equipmentResult.equipmentInfos = result;
            equipmentResult.userBagInfo    = ToUserBagCapacity(user.userBagCapacity);
            return(equipmentResult);
        }
        public static async ETTask <EquipmentResult> CreateEquipment(long uid, List <EquipmentInfo> items, EquipmentFrom fromType, int from)
        {
            // 回傳結構
            var equipmentResult = new EquipmentResult();

            // 抓取使用者背包限制資料
            User user = await UserDataHelper.FindOneUser(uid);

            // 無使用者?
            if (user == null)
            {
                equipmentResult.error = ErrorCode.ERR_AccountDoesntExist;
                return(equipmentResult);
            }

            // 先把相關資料搜尋出來
            var eids = items.Select(e => e.ConfigId).ToList();
            var ret  = await dbProxy.Query <Equipment>(entity => entity.uid == uid && eids.Contains(entity.configId));

            var results = ret.OfType <Equipment>().ToList();

            // 用企劃表id進行分組
            var dictConfigId = OtherHelper.Group(results, entity => entity.configId);
            // 用企劃表type進行分組(TODO:記得擋背包類型下最大存放數)
            //var dictConfigType = OtherHelper.Group(results, entity => entity.configType);

            // 批處理
            var saveBatch      = new List <ComponentWithId>();
            var logBatch       = new List <ComponentWithId>();
            var refreshBagType = new List <int>();

            for (int i = 0; i < items.Count; i++)
            {
                var item = items[i];
                if (item.Count <= 0)
                {
                    equipmentResult.error = ErrorCode.ERR_EquipmentInvalidCount;
                    return(equipmentResult);
                }

                if (TryGetEquipmentConfig(item.ConfigId, out CharacterConfig equipmentConfig))
                {
                    // 道具是否可用?
                    if (!equipmentConfig.IsAvailable())
                    {
                        equipmentResult.error = ErrorCode.ERR_EquipmentUnavailable;
                        return(equipmentResult);
                    }

                    if (!dictConfigId.TryGetValue(equipmentConfig.Id, out var userEqs))
                    {
                        userEqs = new List <Equipment>(0);
                    }

                    // 道具是否可疊加?
                    if (equipmentConfig.IsStackEquipment())
                    {
                        var maxCount = equipmentConfig.MaxCountOnSlot;

                        // 可疊不該有兩筆以上的記錄
                        if (userEqs.Count > 1)
                        {
                            // 記在ErrorLog裡
                            Log.Error(new Exception($"ERR_EquipmentRecordError> Uid:{uid}, Type:{equipmentConfig.Type}, ConfigId:{equipmentConfig.Id}"));
                            await _MergeEquipment(uid, userEqs.ToArray());

                            // 合併資料後用遞迴再跑一次
                            return(await CreateEquipment(uid, items.Skip(i).ToList(), fromType, from));
                        }
                        // 判斷有無記錄?
                        else if (userEqs.Count == 0)
                        {
                            // 判斷背包容量
                            if (user.IsOverOrEqualBagMaxSlotCount((Equipment.EquipmentType)equipmentConfig.Type))
                            {
                                equipmentResult.error = ErrorCode.ERR_EquipmentBagOverLimit;
                                return(equipmentResult);
                            }
                            else
                            {
                                if (equipmentConfig.IsOverOwnedEquipmentLimit(item.Count))
                                {
                                    equipmentResult.error = ErrorCode.ERR_EquipmentOverOwnedLimit;
                                    return(equipmentResult);
                                }
                                var userEq = ComponentFactory.CreateWithId <Equipment>(IdGenerater.GenerateId());
                                userEq.uid        = uid;
                                userEq.configType = equipmentConfig.Type;
                                userEq.configId   = item.ConfigId;
                                userEq.count      = item.Count;
                                saveBatch.Add(userEq);
                                var log = ComponentFactory.Create <EquipmentLog>();
                                log.from     = from;
                                log.fromType = fromType;
                                log.count    = item.Count;
                                var dbLog = _GetSaveEquipmentDBLog(uid, userEq, log, DBLog.LogType.AddEquipment);
                                logBatch.Add(dbLog);
                            }
                        }
                        // 存在一筆紀錄了
                        else
                        {
                            var userEq = userEqs[0];
                            userEq.count += item.Count;
                            if (equipmentConfig.IsOverOwnedEquipmentLimit(userEq.count))
                            {
                                equipmentResult.error = ErrorCode.ERR_EquipmentOverOwnedLimit;
                                return(equipmentResult);
                            }
                            saveBatch.Add(userEq);
                            var log = ComponentFactory.Create <EquipmentLog>();
                            log.from     = from;
                            log.fromType = fromType;
                            log.count    = item.Count;
                            var dbLog = _GetSaveEquipmentDBLog(uid, userEq, log, DBLog.LogType.AddEquipment);
                            logBatch.Add(dbLog);
                        }
                    }
                    else
                    {
                        // 不可疊加就直接創,Log額外再紀錄
                        if (equipmentConfig.IsOverOwnedEquipmentLimit(userEqs.Count + item.Count))
                        {
                            equipmentResult.error = ErrorCode.ERR_EquipmentOverOwnedLimit;
                            return(equipmentResult);
                        }
                        var equipments = new List <Equipment>();
                        for (int j = 0; j < item.Count; j++)
                        {
                            var userEq = ComponentFactory.CreateWithId <Equipment>(IdGenerater.GenerateId());
                            userEq.uid        = uid;
                            userEq.configType = equipmentConfig.Type;
                            userEq.configId   = equipmentConfig.Id;
                            userEq.count      = 1;
                            equipments.Add(userEq);
                        }
                        saveBatch.AddRange(equipments);
                        var log = ComponentFactory.Create <EquipmentLog>();
                        log.from     = from;
                        log.fromType = fromType;
                        var dbLog = _GetSaveBatchEquipmentDBLog(uid, equipments, log);
                        logBatch.Add(dbLog);
                    }

                    if (!refreshBagType.Contains(equipmentConfig.Type))
                    {
                        refreshBagType.Add(equipmentConfig.Type);
                    }
                }
                else
                {
                    equipmentResult.error = ErrorCode.ERR_EquipmentNotDefined;
                    return(equipmentResult);
                }
            }

            // 批儲存
            if (saveBatch.Count != 0)
            {
                await dbProxy.SaveBatch(saveBatch);

                await dbProxy.SaveBatch(logBatch);
            }

            if (refreshBagType.Count != 0)
            {
                await user.RecalculateBagCount(refreshBagType);
            }

            equipmentResult.error          = ErrorCode.ERR_Success;
            equipmentResult.userBagInfo    = ToUserBagCapacity(user.userBagCapacity);
            equipmentResult.equipmentInfos = saveBatch.Aggregate(new RepeatedField <EquipmentInfo>(), (list, item) =>
            {
                list.Add(((Equipment)item).ToEquipmentInfo());
                return(list);
            });
            return(equipmentResult);
        }