public async Task <CreateInvitation> CreateInvitationAsync([FromBody] CreateInvitationRequest request) { var(auth, tenantId, adminId, adminEmail) = Authorize(); if (!auth) { return(new CreateInvitation()); } var invite = ValidatorLinkEntity.Generate(tenantId, Guid.NewGuid().ToString("N")); invite.InvitationToken = Guid.NewGuid().ToString("N") + Guid.NewGuid().ToString("N") + Guid.NewGuid().ToString("N"); invite.Name = request.Name; invite.Position = request.Position; invite.Description = request.Description; invite.CreatedAt = DateTime.UtcNow; invite.CreatedByAdminId = adminId; invite.CreatedByAdminEmail = adminEmail; await _validationWriter.InsertOrReplaceAsync(invite); _logger.LogInformation("Invitation Created. TenantId: {TenantId}; AdminId: {AdminId}", tenantId, adminId); var resp = new CreateInvitation() { TenantId = invite.TenantId, InvitationToken = invite.InvitationToken, Name = invite.Name, Position = invite.Position, Description = invite.Description }; return(resp); }
public async Task InsertOrReplaceCountry(CountryModel country) { await using var context = new DatabaseContext(_dbContextOptionsBuilder.Options); await context.UpsertAsync(new[] { country }); await _cache.InsertOrReplaceAsync(KycCountryNoSqlEntity.Create(country)); }
public async Task SetBaseAssetToClientProfile(string tenantId, long clientId, string baseAssetId) { var profile = await GetClientProfile(tenantId, clientId); profile.BaseAssetId = baseAssetId; await _clientProfileDataWriter.InsertOrReplaceAsync(profile); }
public async Task <(SessionEntity, string)> CreateVerifiedSessionAsync(string tenantId, long clientId, string publicKey = null) { var(session, token) = SessionEntity.Generate(_sessionConfig.ExpirationTimeInMins); session.Verified = true; session.Sms = true; session.Pin = true; session.PublicKey = publicKey; session.ClientId = clientId; session.TenantId = tenantId; await _sessionsWriter.InsertOrReplaceAsync(session); return(session, token); }
private async Task ProcessQuoteAsync(QuoteMessage quote) { var entity = await _priceWriter.GetAsync(Price.GetPk(), quote.AssetPair); var writerTask = Task.CompletedTask; if (entity != null) { if (quote.IsBuy) { entity.Bid = (decimal)quote.Price; } else { entity.Ask = (decimal)quote.Price; } entity.UpdatedDt = quote.Timestamp; writerTask = _priceWriter.InsertOrReplaceAsync(entity); } await Task.WhenAll( _database.HashSetAsync(RedisService.GetMarketDataKey(quote.AssetPair), quote.IsBuy ? nameof(MarketSlice.Bid) : nameof(MarketSlice.Ask), quote.Price.ToString(CultureInfo.InvariantCulture)), writerTask); }
private async Task DoProcess() { List <BidAsk> buffer; lock (_gate) { if (!_buffer.Any()) { return; } buffer = _buffer; _buffer = new List <BidAsk>(128); } var taskList = new List <Task>(); foreach (var group in buffer.GroupBy(e => e.Id)) { var last = @group.OrderByDescending(e => e.DateTime).First(); var task = _writer.InsertOrReplaceAsync(BidAskNoSql.Create(last)); taskList.Add(task.AsTask()); _logger.LogInformation("Quote: {brokerId}:{symbol} {bid} | {ask} | {timestampText}", last.LiquidityProvider, last.Id, last.Bid, last.Ask, last.DateTime.ToString("O")); } if (taskList.Any()) { await Task.WhenAll(taskList); } }
public override async Task <CreateApprovalRequestResponse> CreateApprovalRequest(CreateApprovalRequestRequest request, ServerCallContext context) { var tenantId = request.TenantId; var vaultId = context.GetVaultId(); foreach (var validatorRequest in request.ValidatorRequests) { var entity = ApprovalRequestMyNoSqlEntity.Generate(validatorRequest.ValidatorId, request.TransferSigningRequestId); entity.TenantId = tenantId; entity.MessageEnc = validatorRequest.TransactionDetailsEncBase64; entity.SecretEnc = validatorRequest.SecretEncBase64; entity.IvNonce = validatorRequest.IvNonce; entity.IsOpen = true; entity.VaultId = vaultId; await _dataWriter.InsertOrReplaceAsync(entity); await _pushNotificator.SendPushNotifications(entity); _logger.LogInformation("CreateApprovalRequest processed. TransferSigningRequestId={TransferSigningRequestId}; TenantId={TenantId}; VaultId={VaultId}; ValidatorId={ValidatorId}", request.TransferSigningRequestId, tenantId, vaultId, validatorRequest.ValidatorId); } //await _dataWriter.BulkInsertOrReplaceAsync(list, DataSynchronizationPeriod.Immediately); var resp = new CreateApprovalRequestResponse(); return(resp); }
public async Task UpdateBitGoWallet(BitGoWallet wallet) { using var action = MyTelemetry.StartActivity("Update BitGo wallet"); wallet.ApiKey = _encryptionService.Encrypt(wallet.ApiKey); try { _logger.LogInformation("Update BitGoWallet: {jsonText}", JsonConvert.SerializeObject(wallet, new ApiKeyHiddenJsonConverter(typeof(BitGoWallet)))); wallet.UpdatedDate = DateTime.Now; ValidateWallet(wallet); var entity = BitGoWalletNoSqlEntity.Create(wallet); var existingItem = await _writer.GetAsync(entity.PartitionKey, entity.RowKey); if (existingItem == null) { throw new Exception("Cannot update BitGo wallet. Do not exist"); } await _writer.InsertOrReplaceAsync(entity); _logger.LogInformation("Updated BitGo wallet: {jsonText}", JsonConvert.SerializeObject(wallet, new ApiKeyHiddenJsonConverter(typeof(BitGoWallet)))); } catch (Exception ex) { _logger.LogError(ex, "Cannot update BitGo wallet: {requestJson}", JsonConvert.SerializeObject(wallet, new ApiKeyHiddenJsonConverter(typeof(BitGoWallet)))); ex.FailActivity(); throw; } }
public async Task UpdateSpotInstrumentFeesSettings(SpotInstrumentFees settings) { using var action = MyTelemetry.StartActivity("Update Spot Instrument Fees Settings"); settings.AddToActivityAsJsonTag("settings"); try { _logger.LogInformation("Update Spot Instrument Fees Setting: {jsonText}", JsonConvert.SerializeObject(settings)); ValidateSettings(settings); var entity = SpotInstrumentFeesNoSqlEntity.Create(settings); var existingItem = await _writer.GetAsync(entity.PartitionKey, entity.RowKey); if (existingItem == null) { throw new Exception("Cannot update Spot Instrument Fees Settings. Do not exist"); } await _writer.InsertOrReplaceAsync(entity); _logger.LogInformation("Updated Spot Instrument Fees Setting: {jsonText}", JsonConvert.SerializeObject(settings)); } catch (Exception ex) { _logger.LogError(ex, "Cannot update ExternalMarketSettings: {requestJson}", JsonConvert.SerializeObject(settings)); ex.FailActivity(); throw; } }
private async Task ProcessMessageAsync(OrderbookMessage orderbookMessage) { OrderbookEntity orderbook = null; try { orderbook = await _orderbookWriter.GetAsync(OrderbookEntity.GetPk(), orderbookMessage.AssetPair); } catch (Exception ex) { _log.Warning($"Can't get orderbook from mynosql for assetPair = {orderbookMessage.AssetPair}", ex); } var entity = orderbook ?? new OrderbookEntity(orderbookMessage.AssetPair) { CreatedAt = orderbookMessage.Timestamp }; entity.CreatedAt = orderbookMessage.Timestamp; var prices = orderbookMessage.IsBuy ? entity.Bids : entity.Asks; prices.Clear(); foreach (var price in orderbookMessage.Prices) { prices.Add(new VolumePriceEntity((decimal)price.Volume, (decimal)price.Price)); } await _orderbookWriter.InsertOrReplaceAsync(entity); }
public async Task <ClientWalletEntity> RegisterOrGetDefaultWallets(ClientIdentity client) { var existWallet = await _walletsWriter.TryGetAsync(ClientWalletEntity.GetPartitionKey(client.TenantId), ClientWalletEntity.GetRowKey(client.ClientId)); if (existWallet != null) { return(existWallet); } bool result; do { var walletId = GenerateWalletId(); var entity = ClientWalletEntity.Generate(client.TenantId, client.ClientId); entity.WalletId = walletId; entity.Type = TradingWalletType.Trading; entity.Client = client; var indexById = ClientWalletIndexByIdEntity.Generate(entity.Client.TenantId, walletId, entity.Client.ClientId); result = await _walletsByIdWriter.TryInsertAsync(indexById); if (result) { await _walletsWriter.InsertOrReplaceAsync(entity); return(entity); } _logger.LogInformation("Cannot insert new wallet with id={WalletId}. ClientId={clientId}, TenantId={TenamtId}", entity.WalletId, entity.Client.TenantId, entity.Client.ClientId); } while (true); }
public async ValueTask <bool> UpdateBitgoCoinEntityAsync(string coin, int accuracy, int requiredConfirmations, bool isMainNet) { if (string.IsNullOrEmpty(coin)) { throw new Exception("Cannot update coin. Coin cannot be empty"); } if (accuracy < 0) { throw new Exception("Cannot update coin. Accuracy can't be less then 0"); } if (requiredConfirmations < 0) { throw new Exception("Cannot update coin. RequiredConfirmations can't be less then 0"); } var entity = BitgoCoinEntity.Create(coin, accuracy, requiredConfirmations, isMainNet); var existingItem = await _bitgoCoins.GetAsync(entity.PartitionKey, entity.RowKey); if (existingItem == null) { throw new Exception("Cannot update coin. Coin not found"); } await _bitgoCoins.InsertOrReplaceAsync(entity); return(true); }
public async Task AddOrUpdateAsync(string ruleId, DateTime expires) { var nosqlModel = NotificationNoSql.Create(ruleId, expires); await _myNoSqlServerDataWriter.InsertOrReplaceAsync(nosqlModel); await _myNoSqlServerDataWriter.CleanAndKeepMaxRecords(NotificationNoSql.GeneratePartitionKey(), 3500); }
public async Task RegisterNewSessionAsync(AuthorizationToken token, RefreshToken refreshToken, string userAgent, string ip, string description, PlatformTypes platformType, LoginApplication application) { if (!token.RootSessionId.HasValue) { throw new Exception("Cannot RegisterNewSession without RootSessionId"); } var pkBase64 = token.PublicKey != null?Convert.ToBase64String(token.PublicKey) : ""; var session = RootSessionNoSqlEntity.Generate(token.RootSessionId.Value, token.TraderId(), token.BrandId, userAgent, ip, pkBase64, token.Passed2FA, token.EmailVerified, description, platformType, application); session.Expires = refreshToken.Expires; var task1 = _sessionWriter.InsertOrReplaceAsync(session).AsTask(); var shortSession = ShortRootSessionNoSqlEntity.Create(session); shortSession.Expires = refreshToken.Expires; var task2 = _rootSessionWriter.InsertOrReplaceAsync(shortSession).AsTask(); var task3 = _rootSessionWriter.GetCountAsync( ShortRootSessionNoSqlEntity.GeneratePartitionKey(token.TraderId())).AsTask(); await Task.WhenAll(task1, task2, task3); if (task3.Result > _maxSessionsCount) { var sessions = await _sessionWriter.GetAsync( ShortRootSessionNoSqlEntity.GeneratePartitionKey(token.TraderId())); var oldestSession = sessions.OrderBy(s => s.CreateTime).First(); await Logout(token.TraderId(), oldestSession.RootSessionId); } }
private async Task AddVolumeToApiKey(string brokerId, string apiKey, string asset, double amount) { try { var existingEntity = await _myNoSqlServerApiKeyDataWriter.GetAsync( ApiKeyVolumeNoSqlEntity.GeneratePartitionKey(brokerId), ApiKeyVolumeNoSqlEntity.GenerateRowKey(apiKey, asset)); if (existingEntity == null) { existingEntity = ApiKeyVolumeNoSqlEntity.Create(new ApiKeyVolume { BrokerId = brokerId, ApiKeyHash = apiKey, Asset = asset, Volume = amount, LastUpdateTime = DateTime.Now }); } else { existingEntity.Volume.Volume += amount; existingEntity.Volume.LastUpdateTime = DateTime.Now; } await _myNoSqlServerApiKeyDataWriter.InsertOrReplaceAsync(existingEntity); } catch (Exception ex) { _logger.LogError(ex, "Unable to update accumulated volume for api key {apiKey}, asset {asset}, amount {amount}", apiKey, asset, amount); } }
private async Task SaveMarketDataAsync(List <MarketSlice> marketData, Dictionary <string, IList <Candle> > prices) { var tasks = new List <Task>(); List <string> assetPairIds = prices.Keys.ToList(); var pricesValue = new Dictionary <string, SortedSetEntry[]>(); var baseVolumesValue = new Dictionary <string, SortedSetEntry[]>(); var quoteVolumesValue = new Dictionary <string, SortedSetEntry[]>(); tasks.Add(_database.SortedSetAddAsync(RedisService.GetAssetPairsKey(), marketData.Select(x => new SortedSetEntry(x.AssetPairId, 0)).ToArray())); foreach (var assetPairId in assetPairIds) { pricesValue.Add(assetPairId, prices[assetPairId].Select(x => new SortedSetEntry(RedisExtensions.SerializeWithTimestamp((decimal)x.Open, x.DateTime), x.DateTime.ToUnixTime())).ToArray()); baseVolumesValue.Add(assetPairId, prices[assetPairId].Select(x => new SortedSetEntry(RedisExtensions.SerializeWithTimestamp((decimal)x.TradingVolume, x.DateTime), x.DateTime.ToUnixTime())).ToArray()); quoteVolumesValue.Add(assetPairId, prices[assetPairId].Select(x => new SortedSetEntry(RedisExtensions.SerializeWithTimestamp((decimal)x.TradingOppositeVolume, x.DateTime), x.DateTime.ToUnixTime())).ToArray()); } foreach (MarketSlice marketSlice in marketData) { tasks.Add(_database.HashSetAsync(RedisService.GetMarketDataKey(marketSlice.AssetPairId), marketSlice.ToMarketSliceHash())); } await Task.WhenAll(tasks); tasks = new List <Task>(); foreach (MarketSlice marketSlice in marketData) { tasks.Add(_priceWriter.InsertOrReplaceAsync(marketSlice.ToPrice())); tasks.Add(_tickerWriter.InsertOrReplaceAsync(marketSlice.ToTicker())); } await Task.WhenAll(tasks); tasks = new List <Task>(); foreach (var assetPairId in assetPairIds) { tasks.Add(_database.SortedSetAddAsync(RedisService.GetMarketDataPriceKey(assetPairId), pricesValue[assetPairId])); tasks.Add(_database.SortedSetAddAsync(RedisService.GetMarketDataBaseVolumeKey(assetPairId), baseVolumesValue[assetPairId])); tasks.Add(_database.SortedSetAddAsync(RedisService.GetMarketDataQuoteVolumeKey(assetPairId), quoteVolumesValue[assetPairId])); } await Task.WhenAll(tasks); }
public async Task UpdateLiquidityConverterSettingsAsync(LiquidityConverterSettings settings) { await _settingsDataWriter.InsertOrReplaceAsync(SettingsLiquidityConverterNoSql.Create(settings)); await ReloadSettings(); _logger.LogInformation("Updated LiquidityConverterSettings Settings: {jsonText}", JsonConvert.SerializeObject(settings)); }
public async Task <ClientIdentity> Login(string tenantId, string username, string password) { var data = await _dataWriter.TryGetAsync(AuthDataEntity.GeneratePartitionKey(), AuthDataEntity.GenerateRowKey(tenantId, username)); if (data.PasswordHash == password.ToSha256().ToBase64()) { var indexEntity = AuthDataIndexByIdEntity.Generate(data.TenantId, data.ClientId, data.Email); await _indexDataWriter.InsertOrReplaceAsync(indexEntity); return(new ClientIdentity() { ClientId = data.ClientId, TenantId = data.TenantId }); } return(null); }
public async Task <string> CreateVerifiedSessionAsync(string token, string publicKey = null) { string sessionId = SessionEntity.GenerateSessionId(); var session = SessionEntity.Generate(_expirationTimeInMins, sessionId); session.Token = token; session.Verified = true; session.Sms = true; session.Pin = true; session.PublicKey = publicKey; var lykkeSession = await _clientSessionsClient.GetAsync(token); session.ClientId = lykkeSession.ClientId; session.PartnerId = lykkeSession.PartnerId; session.LykkeSessionId = lykkeSession.AuthId.ToString(); await _sessionsWriter.InsertOrReplaceAsync(session); return(sessionId); }
public void Update(string brokerId, OrderBook orderBook) { var symbol = orderBook.Symbol; _orderBooks.Update(brokerId, symbol, orderBook); var entity = OrderBookEntity.GenerateEntity(brokerId, orderBook.Symbol); entity.OrderBook = orderBook; _dataWriter.InsertOrReplaceAsync(entity).GetAwaiter().GetResult(); }
public async Task UpdateProfile(KycProfile profile, string operation, string agent, string comment) { await using var context = new DatabaseContext(_dbContextOptionsBuilder.Options); await context.UpsertAsync(new[] { profile }); await _cache.InsertOrReplaceAsync(KycProfileNoSqlEntity.Create(profile)); await _cache.CleanAndKeepMaxRecords(KycProfileNoSqlEntity.GeneratePartitionKey(), 10000); context.AuditLogs.Add(KycAuditLog.Create(profile, operation, agent, comment)); await context.SaveChangesAsync(); }
public async Task AddWalletAsync(LpWallet wallet) { var entity = LpWalletNoSql.Create(wallet); await _noSqlDataWriter.InsertOrReplaceAsync(entity); lock (_sync) { _data[wallet.Name] = wallet; } _logger.LogInformation("Added Wallet {name}: {jsonText}", wallet.Name, JsonConvert.SerializeObject(wallet)); }
public async Task <ProfilesResponse> GetAllProfiles() { var groups = await _profileWriter.GetAsync(FeeProfilesNoSqlEntity.GeneratePartitionKey(), FeeProfilesNoSqlEntity.GenerateRowKey()); if (groups.DepositProfiles == null || !groups.DepositProfiles.Any()) { var list = new List <string>() { FeeProfileConsts.DefaultProfile }; await _profileWriter.InsertOrReplaceAsync(FeeProfilesNoSqlEntity.Create(groups.Profiles, list)); } return(new ProfilesResponse() { WithdrawalProfiles = groups?.Profiles ?? new List <string>(), DepositProfiles = groups?.DepositProfiles ?? new List <string>() { FeeProfileConsts.DefaultProfile } }); }
public async Task AddOrUpdateRouteAsync(ProviderRoute newRoute) { if (string.IsNullOrEmpty(newRoute.Id)) { newRoute.Id = Guid.NewGuid().ToString("N"); } var entity = ProviderRouteMyNoSqlEntity.Create(newRoute); await _routeWriter.InsertOrReplaceAsync(entity); _logger.LogInformation("Added/updated provider route {routeJson}", JsonConvert.SerializeObject(newRoute)); }
public async Task <bool> TryInsertOrReplaceAsync(TEntity entity) { try { await _writer.InsertOrReplaceAsync(entity); return(true); } catch (Exception e) { _log.Error(e, $"Cannot execute InsertOrReplaceAsync to MyNoSql with entity {typeof(TEntity).Name}", $"data: {entity.ToJson()}"); return(false); } }
public async Task PingValidatorAsync([FromBody] PingValidatorRequest request) { var(auth, tenantId, adminId, adminEmail) = Authorize(); if (!auth) { return; } var entity = _validationReader.Get(ValidatorLinkEntity.GeneratePartitionKey(tenantId), ValidatorLinkEntity.GenerateRowKey(request.ApiKeyId)); if (entity == null) { _logger.LogInformation("Cannot send ping. ValidatorLinkEntity not found by API key: {ApiKeyId}", request.ApiKeyId); HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; return; } if (!entity.IsAccepted) { _logger.LogInformation("Cannot send ping. ValidatorLinkEntity is not accepted: {ApiKeyId}", request.ApiKeyId); HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; return; } if (string.IsNullOrEmpty(request.Message)) { _logger.LogInformation("Cannot send ping. Message cannot be empty: {ApiKeyId}", request.ApiKeyId); HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; return; } if (Encoding.UTF8.GetBytes(request.Message).Length > 100) { _logger.LogInformation("Cannot send ping. Message length in bytes more that 100: {ApiKeyId}; Message: {Message}", request.ApiKeyId, request.Message); HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; return; } var message = PingMessageMyNoSqlEntity.Generate(entity.ValidatorId, request.Message); await _pingMessageWriter.InsertOrReplaceAsync(message); _logger.LogInformation("Ping message are created. API key: {ApiKeyId}; ValidatorId: {ValidatorId}; TenantId: {TenantId}; Name: {Name}; AdminId: {AdminId}", request.ApiKeyId, entity.ValidatorId, entity.TenantId, entity.Name, adminId); }
public async ValueTask <bool> UpdateBitgoAssetMapEntityAsync(string brokerId, string assetSymbol, string bitgoWalletId, string enabledBitgoWalletIds, string bitgoCoin, double minBalance, string tagSeparator) { if (string.IsNullOrEmpty(brokerId)) { throw new Exception("Cannot update asset map. BrokerId cannot be empty"); } if (string.IsNullOrEmpty(assetSymbol)) { throw new Exception("Cannot update asset map. AssetSymbol cannot be empty"); } if (string.IsNullOrEmpty(bitgoWalletId)) { throw new Exception("Cannot update asset map. BitgoWalletId cannot be empty"); } if (string.IsNullOrEmpty(bitgoCoin)) { throw new Exception("Cannot update asset map. BitgoCoin cannot be empty"); } var coin = await _bitgoCoins.GetAsync(BitgoCoinEntity.GeneratePartitionKey(), BitgoCoinEntity.GenerateRowKey(bitgoCoin)); if (coin == null) { throw new Exception("Cannot update asset map. Unknown BitgoCoin."); } var entity = BitgoAssetMapEntity.Create(brokerId, assetSymbol, bitgoWalletId, enabledBitgoWalletIds, bitgoCoin, minBalance, tagSeparator); var existingEntity = await _assetMap.GetAsync(entity.PartitionKey, entity.RowKey); if (existingEntity == null) { throw new Exception("Cannot update asset map. Asset map not found"); } await _assetMap.InsertOrReplaceAsync(entity); return(true); }
private async Task ProcessMessageAsync(OrderbookMessage orderbookMessage) { var entity = await _orderbookWriter.GetAsync(OrderbookEntity.GetPk(), orderbookMessage.AssetPair) ?? new OrderbookEntity(orderbookMessage.AssetPair) { CreatedAt = orderbookMessage.Timestamp, }; entity.CreatedAt = orderbookMessage.Timestamp; var prices = orderbookMessage.IsBuy ? entity.Bids : entity.Asks; prices.Clear(); foreach (var price in orderbookMessage.Prices) { prices.Add(new VolumePriceEntity((decimal)price.Volume, (decimal)price.Price)); } await _orderbookWriter.InsertOrReplaceAsync(entity); }
public async Task <SnapshotEntity> GetSnapshot(string walletId, string assetId) { var snapshot = await _writer.GetAsync(SnapshotNoSqlEntity.GeneratePartitionKey(walletId), SnapshotNoSqlEntity.GenerateRowKey(assetId)); if (snapshot != null) { return(snapshot.Entity); } await using var ctx = DatabaseContext.Create(_dbContextOptionsBuilder); var snapshotEntity = await ctx.AvgSnapshots.FirstOrDefaultAsync(t => t.AssetId == assetId && t.WalletId == walletId); if (snapshotEntity != null) { await _writer.InsertOrReplaceAsync(SnapshotNoSqlEntity.Create(snapshotEntity)); await _writer.CleanAndKeepMaxPartitions(Program.Settings.MaxCachedSnapshots); } return(snapshotEntity); }
private async Task UpdateCache(string clientId, string brokerId, List <ClientWalletEntity> list) { if (string.IsNullOrWhiteSpace(brokerId) || string.IsNullOrWhiteSpace(clientId)) { throw new Exception($"Client and broker cannot be null. Client: {JsonConvert.SerializeObject(clientId)}"); } var noSqlEntity = new ClientWalletNoSqlEntity() { BrokerId = brokerId, ClientId = clientId, PartitionKey = ClientWalletNoSqlEntity.GeneratePartitionKey(brokerId), RowKey = ClientWalletNoSqlEntity.GenerateRowKey(clientId), Wallets = list.Select(e => new ClientWallet() { Name = e.Name, IsDefault = e.IsDefault, WalletId = e.WalletId, CreatedAt = e.CreatedAt, BaseAsset = e.BaseAsset, EnableUseTestNet = e.EnableUseTestNet, IsInternal = e.IsInternal, EnableEarnProgram = e.EnableEarnProgram }).ToList() }; try { await _writer.InsertOrReplaceAsync(noSqlEntity); } catch (Exception ex) { _logger.LogError(ex, "Cannot update cache. Entity: {json}", JsonConvert.SerializeObject(noSqlEntity)); } }