public static void AncientLevelsModel() { var encodedSaveData = File.ReadAllText("TestData\\ValidZlib.txt"); var savedGame = SavedGame.Parse(encodedSaveData); var ancientLevels = new AncientLevelsModel(MockGameData.RealData, savedGame); var expectedAncientLevels = new Dictionary <int, BigInteger> { { 4, 200 }, { 5, 200 }, { 8, 200 }, { 9, 200 }, { 10, 0 }, { 11, 10 }, { 12, 10 }, { 13, 10 }, { 14, 10 }, { 15, 0 }, { 16, 40000 }, { 17, 10 }, { 18, 10 }, { 19, 200 }, { 20, 10 }, { 21, 10 }, { 22, 0 }, { 23, 0 }, { 24, 0 }, { 25, 0 }, { 26, 0 }, { 27, 0 }, { 28, 200 }, { 29, 50 }, { 31, 10 }, { 32, 50 }, }; Assert.NotNull(ancientLevels.AncientLevels); Assert.Equal(expectedAncientLevels.Count, ancientLevels.AncientLevels.Count); foreach (var pair in expectedAncientLevels) { Assert.Equal(pair.Value, ancientLevels.AncientLevels[pair.Key]); } }
private bool ProcessMessage(CloudQueueMessage queueMessage) { var properties = new Dictionary <string, string> { { "CloudQueueMessage-DequeueCount", queueMessage.DequeueCount.ToString() }, { "CloudQueueMessage-InsertionTime", queueMessage.InsertionTime.ToString() }, { "CloudQueueMessage-Id", queueMessage.Id }, }; this.telemetryClient.TrackEvent("UploadProcessor-Recieved", properties); using (var counterProvider = new CounterProvider(this.telemetryClient)) using (var databaseCommandFactory = new DatabaseCommandFactory( this.databaseSettingsOptions, counterProvider)) using (counterProvider.Measure(Counter.ProcessUpload)) { int uploadId = -1; try { var message = JsonConvert.DeserializeObject <UploadProcessingMessage>(queueMessage.AsString); if (message == null) { this.telemetryClient.TrackEvent("UploadProcessor-Abandoned-ParseMessage", properties); return(false); } uploadId = message.UploadId; properties.Add("UploadId", uploadId.ToString()); this.CurrentUploadId = uploadId; GetUploadDetails(databaseCommandFactory, uploadId, out var uploadContent, out var userId, out var playStyle); properties.Add("UserId", userId); if (string.IsNullOrWhiteSpace(uploadContent)) { this.telemetryClient.TrackEvent("UploadProcessor-Abandoned-FetchUpload", properties); return(false); } // Handle legacy uplaods where the upload content was missing. if (uploadContent.Equals("LEGACY", StringComparison.OrdinalIgnoreCase)) { this.telemetryClient.TrackEvent("UploadProcessor-Complete-Legacy", properties); using (var command = databaseCommandFactory.Create()) { command.CommandText = "UPDATE Uploads SET LastComputeTime = getutcdate() WHERE Id = " + uploadId; command.ExecuteNonQuery(); } return(true); } var savedGame = SavedGame.Parse(uploadContent); if (savedGame == null) { this.telemetryClient.TrackEvent("UploadProcessor-Abandoned-ParseUpload", properties); return(false); } this.telemetryClient.TrackEvent("UploadProcessor-Processing", properties); var ancientLevels = new AncientLevelsModel( this.gameData, savedGame, this.telemetryClient); var outsiderLevels = new OutsiderLevelsModel( this.gameData, savedGame, this.telemetryClient); var miscellaneousStatsModel = new MiscellaneousStatsModel(savedGame); /* Build a query that looks like this: * MERGE INTO AncientLevels WITH (HOLDLOCK) * USING * (VALUES (123, 1, 100), (123, 2, 100), ... ) * AS Input(UploadId, AncientId, Level) * ON AncientLevels.UploadId = Input.UploadId * AND AncientLevels.AncientId = Input.AncientId * WHEN MATCHED THEN * UPDATE * SET * Level = Input.Level * WHEN NOT MATCHED THEN * INSERT (UploadId, AncientId, Level) * VALUES (Input.UploadId, Input.AncientId, Input.Level); */ var ancientLevelsCommandText = new StringBuilder(); if (ancientLevels.AncientLevels.Count > 0) { ancientLevelsCommandText.Append(@" MERGE INTO AncientLevels WITH (HOLDLOCK) USING ( VALUES "); var isFirst = true; foreach (var pair in ancientLevels.AncientLevels) { if (!isFirst) { ancientLevelsCommandText.Append(","); } // No need to sanitize, these are all just numbers ancientLevelsCommandText.Append("("); ancientLevelsCommandText.Append(uploadId); ancientLevelsCommandText.Append(","); ancientLevelsCommandText.Append(pair.Key); ancientLevelsCommandText.Append(","); ancientLevelsCommandText.Append(pair.Value.AncientLevel); ancientLevelsCommandText.Append(")"); isFirst = false; } ancientLevelsCommandText.Append(@" ) AS Input(UploadId, AncientId, Level) ON AncientLevels.UploadId = Input.UploadId AND AncientLevels.AncientId = Input.AncientId WHEN MATCHED THEN UPDATE SET Level = Input.Level WHEN NOT MATCHED THEN INSERT (UploadId, AncientId, Level) VALUES (Input.UploadId, Input.AncientId, Input.Level);"); } /* Build a query that looks like this: * MERGE INTO OutsiderLevels WITH (HOLDLOCK) * USING * (VALUES (123, 1, 100), (123, 2, 100), ... ) * AS Input(UploadId, OutsiderId, Level) * ON OutsiderLevels.UploadId = Input.UploadId * AND OutsiderLevels.OutsiderId = Input.OutsiderId * WHEN MATCHED THEN * UPDATE * SET * Level = Input.Level * WHEN NOT MATCHED THEN * INSERT (UploadId, OutsiderId, Level) * VALUES (Input.UploadId, Input.OutsiderId, Input.Level); */ var outsiderLevelsCommandText = new StringBuilder(); if (outsiderLevels.OutsiderLevels.Count > 0) { outsiderLevelsCommandText.Append(@" MERGE INTO OutsiderLevels WITH (HOLDLOCK) USING ( VALUES "); var isFirst = true; foreach (var pair in outsiderLevels.OutsiderLevels) { if (!isFirst) { outsiderLevelsCommandText.Append(","); } // No need to sanitize, these are all just numbers outsiderLevelsCommandText.Append("("); outsiderLevelsCommandText.Append(uploadId); outsiderLevelsCommandText.Append(","); outsiderLevelsCommandText.Append(pair.Key); outsiderLevelsCommandText.Append(","); outsiderLevelsCommandText.Append(pair.Value.Level); outsiderLevelsCommandText.Append(")"); isFirst = false; } outsiderLevelsCommandText.Append(@" ) AS Input(UploadId, OutsiderId, Level) ON OutsiderLevels.UploadId = Input.UploadId AND OutsiderLevels.OutsiderId = Input.OutsiderId WHEN MATCHED THEN UPDATE SET Level = Input.Level WHEN NOT MATCHED THEN INSERT (UploadId, OutsiderId, Level) VALUES (Input.UploadId, Input.OutsiderId, Input.Level);"); } const string ComputedStatsCommandText = @" MERGE INTO ComputedStats WITH (HOLDLOCK) USING (VALUES ( @UploadId, @TitanDamage, @SoulsSpent, @HeroSoulsSacrificed, @TotalAncientSouls, @TranscendentPower, @Rubies, @HighestZoneThisTranscension, @HighestZoneLifetime, @AscensionsThisTranscension, @AscensionsLifetime, @MaxTranscendentPrimalReward, @BossLevelToTranscendentPrimalCap) ) AS Input( UploadId, TitanDamage, SoulsSpent, HeroSoulsSacrificed, TotalAncientSouls, TranscendentPower, Rubies, HighestZoneThisTranscension, HighestZoneLifetime, AscensionsThisTranscension, AscensionsLifetime, MaxTranscendentPrimalReward, BossLevelToTranscendentPrimalCap) ON ComputedStats.UploadId = Input.UploadId WHEN MATCHED THEN UPDATE SET TitanDamage = Input.TitanDamage, SoulsSpent = Input.SoulsSpent, HeroSoulsSacrificed = Input.HeroSoulsSacrificed, TotalAncientSouls = Input.TotalAncientSouls, TranscendentPower = Input.TranscendentPower, Rubies = Input.Rubies, HighestZoneThisTranscension = Input.HighestZoneThisTranscension, HighestZoneLifetime = Input.HighestZoneLifetime, AscensionsThisTranscension = Input.AscensionsThisTranscension, AscensionsLifetime = Input.AscensionsLifetime, MaxTranscendentPrimalReward = Input.MaxTranscendentPrimalReward, BossLevelToTranscendentPrimalCap = Input.BossLevelToTranscendentPrimalCap WHEN NOT MATCHED THEN INSERT ( UploadId, TitanDamage, SoulsSpent, HeroSoulsSacrificed, TotalAncientSouls, TranscendentPower, Rubies, HighestZoneThisTranscension, HighestZoneLifetime, AscensionsThisTranscension, AscensionsLifetime, MaxTranscendentPrimalReward, BossLevelToTranscendentPrimalCap) VALUES ( Input.UploadId, Input.TitanDamage, Input.SoulsSpent, Input.HeroSoulsSacrificed, Input.TotalAncientSouls, Input.TranscendentPower, Input.Rubies, Input.HighestZoneThisTranscension, Input.HighestZoneLifetime, Input.AscensionsThisTranscension, Input.AscensionsLifetime, Input.MaxTranscendentPrimalReward, Input.BossLevelToTranscendentPrimalCap);"; var computedStatsCommandParameters = new Dictionary <string, object> { { "@UploadId", uploadId }, { "@TitanDamage", miscellaneousStatsModel.TitanDamage }, { "@SoulsSpent", miscellaneousStatsModel.HeroSoulsSpent }, { "@HeroSoulsSacrificed", miscellaneousStatsModel.HeroSoulsSacrificed }, { "@TotalAncientSouls", miscellaneousStatsModel.TotalAncientSouls }, { "@TranscendentPower", miscellaneousStatsModel.TranscendentPower }, { "@Rubies", miscellaneousStatsModel.Rubies }, { "@HighestZoneThisTranscension", miscellaneousStatsModel.HighestZoneThisTranscension }, { "@HighestZoneLifetime", miscellaneousStatsModel.HighestZoneLifetime }, { "@AscensionsThisTranscension", miscellaneousStatsModel.AscensionsThisTranscension }, { "@AscensionsLifetime", miscellaneousStatsModel.AscensionsLifetime }, { "@MaxTranscendentPrimalReward", miscellaneousStatsModel.MaxTranscendentPrimalReward }, { "@BossLevelToTranscendentPrimalCap", miscellaneousStatsModel.BossLevelToTranscendentPrimalCap }, }; using (var command = databaseCommandFactory.Create()) { command.BeginTransaction(); if (ancientLevelsCommandText.Length > 0) { command.CommandText = ancientLevelsCommandText.ToString(); command.ExecuteNonQuery(); } if (outsiderLevelsCommandText.Length > 0) { command.CommandText = outsiderLevelsCommandText.ToString(); command.ExecuteNonQuery(); } command.CommandText = ComputedStatsCommandText; command.Parameters = computedStatsCommandParameters; command.ExecuteNonQuery(); command.CommandText = "UPDATE Uploads SET LastComputeTime = getutcdate() WHERE Id = " + uploadId; command.ExecuteNonQuery(); var commited = command.CommitTransaction(); if (!commited) { this.telemetryClient.TrackEvent("UploadProcessor-Abandoned-CommitTransaction", properties); return(false); } } this.telemetryClient.TrackEvent("UploadProcessor-Complete", properties); return(true); } catch (Exception e) { this.telemetryClient.TrackException(e, properties); return(false); } finally { this.CurrentUploadId = null; } } }
public async Task <ActionResult <int> > Add([FromForm] UploadRequest uploadRequest) { // Only associate it with the user if they requested that it be added to their progress. var userId = uploadRequest.AddToProgress && this.User.Identity.IsAuthenticated ? this.userManager.GetUserId(this.User) : null; var savedGame = SavedGame.Parse(uploadRequest.EncodedSaveData); if (savedGame == null) { // Not a valid save return(this.BadRequest()); } // Kick off a clan update in parallel var gameUserId = savedGame.Object.Value <string>("uniqueId"); var passwordHash = savedGame.Object.Value <string>("passwordHash"); var updateClanTask = this.clanManager.UpdateClanAsync(userId, gameUserId, passwordHash); PlayStyle playStyle; if (uploadRequest.PlayStyle.HasValue) { playStyle = uploadRequest.PlayStyle.Value; } else { var userSettings = await this.userSettingsProvider.GetAsync(userId); playStyle = userSettings.PlayStyle.GetValueOrDefault(PlayStyle.Hybrid); } // unixTimestamp is in milliseconds instead of seconds var saveTime = (savedGame.Object.Value <double>("unixTimestamp") / 1000).UnixTimeStampToDateTime(); var ancientLevels = new AncientLevelsModel( this.gameData, savedGame); var outsiderLevels = new OutsiderLevelsModel( this.gameData, savedGame); var computedStats = new ComputedStats(savedGame); int uploadId; using (var command = this.databaseCommandFactory.Create()) { await command.BeginTransactionAsync(); // Insert Upload command.CommandText = @" INSERT INTO Uploads(UserId, UploadContent, PlayStyle, SaveTime) VALUES(@UserId, @UploadContent, @PlayStyle, @SaveTime); SELECT SCOPE_IDENTITY();"; command.Parameters = new Dictionary <string, object> { { "@UserId", userId }, { "@UploadContent", uploadRequest.EncodedSaveData }, { "@PlayStyle", playStyle.ToString() }, { "@SaveTime", saveTime }, }; uploadId = Convert.ToInt32(await command.ExecuteScalarAsync()); // Insert computed stats command.CommandText = @" INSERT INTO ComputedStats( UploadId, TitanDamage, SoulsSpent, HeroSoulsSacrificed, TotalAncientSouls, TranscendentPower, Rubies, HighestZoneThisTranscension, HighestZoneLifetime, AscensionsThisTranscension, AscensionsLifetime) VALUES( @UploadId, @TitanDamage, @SoulsSpent, @HeroSoulsSacrificed, @TotalAncientSouls, @TranscendentPower, @Rubies, @HighestZoneThisTranscension, @HighestZoneLifetime, @AscensionsThisTranscension, @AscensionsLifetime);"; command.Parameters = new Dictionary <string, object> { { "@UploadId", uploadId }, { "@TitanDamage", computedStats.TitanDamage }, { "@SoulsSpent", computedStats.HeroSoulsSpent }, { "@HeroSoulsSacrificed", computedStats.HeroSoulsSacrificed }, { "@TotalAncientSouls", computedStats.TotalAncientSouls }, { "@TranscendentPower", computedStats.TranscendentPower }, { "@Rubies", computedStats.Rubies }, { "@HighestZoneThisTranscension", computedStats.HighestZoneThisTranscension }, { "@HighestZoneLifetime", computedStats.HighestZoneLifetime }, { "@AscensionsThisTranscension", computedStats.AscensionsThisTranscension }, { "@AscensionsLifetime", computedStats.AscensionsLifetime }, }; await command.ExecuteNonQueryAsync(); // Insert ancient levels var ancientLevelsCommandText = new StringBuilder("INSERT INTO AncientLevels(UploadId, AncientId, Level) VALUES"); var ancientLevelsParameters = new Dictionary <string, object> { { "@UploadId", uploadId }, }; var isFirstAncient = true; foreach (var pair in ancientLevels.AncientLevels) { if (!isFirstAncient) { ancientLevelsCommandText.Append(','); } // Looks like: (@UploadId, @AncientId{AncientId}, @AncientLevel{AncientId}) var idParamName = "@AncientId" + pair.Key; var levelParamName = "@AncientLevel" + pair.Key; ancientLevelsCommandText.Append("(@UploadId,"); ancientLevelsCommandText.Append(idParamName); ancientLevelsCommandText.Append(','); ancientLevelsCommandText.Append(levelParamName); ancientLevelsCommandText.Append(')'); ancientLevelsParameters.Add(idParamName, pair.Key); ancientLevelsParameters.Add(levelParamName, pair.Value.ToTransportableString()); isFirstAncient = false; } command.CommandText = ancientLevelsCommandText.ToString(); command.Parameters = ancientLevelsParameters; await command.ExecuteNonQueryAsync(); // Insert outsider levels var outsiderLevelsCommandText = new StringBuilder("INSERT INTO OutsiderLevels(UploadId, OutsiderId, Level) VALUES"); var outsiderLevelsParameters = new Dictionary <string, object> { { "@UploadId", uploadId }, }; var isFirstOutsider = true; foreach (var pair in outsiderLevels.OutsiderLevels) { if (!isFirstOutsider) { outsiderLevelsCommandText.Append(','); } // Looks like: (@UploadId, @OutsiderId{OutsiderId}, @Level{OutsiderId}) var idParamName = "@OutsiderId" + pair.Key; var levelParamName = "@OutsiderLevel" + pair.Key; outsiderLevelsCommandText.Append("(@UploadId,"); outsiderLevelsCommandText.Append(idParamName); outsiderLevelsCommandText.Append(','); outsiderLevelsCommandText.Append(levelParamName); outsiderLevelsCommandText.Append(')'); outsiderLevelsParameters.Add(idParamName, pair.Key); outsiderLevelsParameters.Add(levelParamName, pair.Value); isFirstOutsider = false; } command.CommandText = outsiderLevelsCommandText.ToString(); command.Parameters = outsiderLevelsParameters; await command.ExecuteNonQueryAsync(); command.CommitTransaction(); } // Wait for the task to finish, but don't fail the request if it fails try { await updateClanTask; } catch (Exception e) { var properties = new Dictionary <string, string> { { "UploadId", uploadId.ToString() }, }; this.telemetryClient.TrackException(e, properties); } return(uploadId); }
public IActionResult Details(int uploadId) { if (uploadId < 0) { return(this.BadRequest()); } var userId = this.userManager.GetUserId(this.User); var userSettings = this.userSettingsProvider.Get(userId); var uploadIdParameters = new Dictionary <string, object> { { "@UploadId", uploadId }, }; string uploadContent; PlayStyle playStyle; var upload = new Upload { Id = uploadId }; const string GetUploadDataCommandText = @" SELECT UserId, UserName, UploadTime, UploadContent, PlayStyle FROM Uploads LEFT JOIN AspNetUsers ON Uploads.UserId = AspNetUsers.Id WHERE Uploads.Id = @UploadId"; using (var command = this.databaseCommandFactory.Create( GetUploadDataCommandText, uploadIdParameters)) using (var reader = command.ExecuteReader()) { if (reader.Read()) { var uploadUserId = reader["UserId"].ToString(); var uploadUserName = reader["UserName"].ToString(); if (!string.IsNullOrEmpty(uploadUserId)) { upload.User = new User() { Id = uploadUserId, Name = uploadUserName, }; } // The DateTime is a datetime2 which has no timezone so comes out as DateTimeKind.Unknown. Se need to specify the kind so it gets serialized correctly. upload.TimeSubmitted = DateTime.SpecifyKind(Convert.ToDateTime(reader["UploadTime"]), DateTimeKind.Utc); uploadContent = reader["UploadContent"].ToString(); playStyle = reader["PlayStyle"].ToString().SafeParseEnum <PlayStyle>(); } else { // If we didn't get data, it's an upload that doesn't exist return(this.NotFound()); } } var isAdmin = this.User.IsInRole("Admin"); var isUploadAnonymous = upload.User == null; var isOwn = !isUploadAnonymous && string.Equals(userId, upload.User.Id, StringComparison.OrdinalIgnoreCase); var uploadUserSettings = isOwn ? userSettings : this.userSettingsProvider.Get(upload.User?.Id); var isPublic = isUploadAnonymous || uploadUserSettings.AreUploadsPublic; var isPermitted = isOwn || isPublic || isAdmin; if (!isPermitted) { return(this.Unauthorized()); } // Only return the raw upload content if it's the requesting user's or an admin requested it. if (isOwn || isAdmin) { upload.UploadContent = uploadContent; } // Set the play style. upload.PlayStyle = playStyle; var savedGame = SavedGame.Parse(uploadContent); upload.Stats = new Dictionary <StatType, string>(); // Get ancient level stats var ancientLevelsModel = new AncientLevelsModel( this.gameData, savedGame, this.telemetryClient); foreach (var ancientLevelInfo in ancientLevelsModel.AncientLevels) { var ancientLevel = ancientLevelInfo.Value.AncientLevel; if (ancientLevel > 0) { upload.Stats.Add(AncientIds.GetAncientStatType(ancientLevelInfo.Key), ancientLevel.ToTransportableString()); } var itemLevel = ancientLevelInfo.Value.ItemLevel; if (itemLevel > 0) { upload.Stats.Add(AncientIds.GetItemStatType(ancientLevelInfo.Key), itemLevel.ToString()); } } // Get outsider level stats var outsiderLevelsModel = new OutsiderLevelsModel( this.gameData, savedGame, this.telemetryClient); foreach (var pair in outsiderLevelsModel.OutsiderLevels) { var outsiderLevel = pair.Value.Level; if (outsiderLevel > 0) { upload.Stats.Add(OutsiderIds.GetOusiderStatType(pair.Key), outsiderLevel.ToString()); } } // Get misc stats var miscellaneousStatsModel = new MiscellaneousStatsModel(savedGame); upload.Stats.Add(StatType.AscensionsLifetime, miscellaneousStatsModel.AscensionsLifetime.ToString()); upload.Stats.Add(StatType.AscensionsThisTranscension, miscellaneousStatsModel.AscensionsThisTranscension.ToString()); upload.Stats.Add(StatType.HeroSoulsSacrificed, miscellaneousStatsModel.HeroSoulsSacrificed.ToTransportableString()); upload.Stats.Add(StatType.HeroSoulsSpent, miscellaneousStatsModel.HeroSoulsSpent.ToTransportableString()); upload.Stats.Add(StatType.HighestZoneLifetime, miscellaneousStatsModel.HighestZoneLifetime.ToString()); upload.Stats.Add(StatType.HighestZoneThisTranscension, miscellaneousStatsModel.HighestZoneThisTranscension.ToString()); upload.Stats.Add(StatType.Rubies, miscellaneousStatsModel.Rubies.ToString()); upload.Stats.Add(StatType.TitanDamage, miscellaneousStatsModel.TitanDamage.ToTransportableString()); upload.Stats.Add(StatType.TotalAncientSouls, miscellaneousStatsModel.TotalAncientSouls.ToString()); upload.Stats.Add(StatType.TranscendentPower, miscellaneousStatsModel.TranscendentPower.ToString()); upload.Stats.Add(StatType.MaxTranscendentPrimalReward, miscellaneousStatsModel.MaxTranscendentPrimalReward.ToTransportableString()); upload.Stats.Add(StatType.BossLevelToTranscendentPrimalCap, miscellaneousStatsModel.BossLevelToTranscendentPrimalCap.ToString()); upload.Stats.Add(StatType.HeroSouls, miscellaneousStatsModel.HeroSouls.ToTransportableString()); upload.Stats.Add(StatType.PendingSouls, miscellaneousStatsModel.PendingSouls.ToTransportableString()); return(this.Ok(upload)); }
public IActionResult Add(UploadRequest uploadRequest) { if (uploadRequest.EncodedSaveData == null) { // Not a valid save return(this.BadRequest()); } // Only associate it with the user if they requested that it be added to their progress. var userId = uploadRequest.AddToProgress && this.User.Identity.IsAuthenticated ? this.userManager.GetUserId(this.User) : null; var savedGame = SavedGame.Parse(uploadRequest.EncodedSaveData); if (savedGame == null) { // Not a valid save return(this.BadRequest()); } PlayStyle playStyle; if (uploadRequest.PlayStyle.HasValue) { playStyle = uploadRequest.PlayStyle.Value; } else { var userSettings = this.userSettingsProvider.Get(userId); playStyle = userSettings.PlayStyle; } var ancientLevels = new AncientLevelsModel( this.gameData, savedGame, this.telemetryClient); var outsiderLevels = new OutsiderLevelsModel( this.gameData, savedGame, this.telemetryClient); var miscellaneousStatsModel = new MiscellaneousStatsModel(savedGame); int uploadId; using (var command = this.databaseCommandFactory.Create()) { command.BeginTransaction(); // Insert Upload command.CommandText = @" INSERT INTO Uploads(UserId, UploadContent, PlayStyle) VALUES(@UserId, @UploadContent, @PlayStyle); SELECT SCOPE_IDENTITY();"; command.Parameters = new Dictionary <string, object> { { "@UserId", userId }, { "@UploadContent", uploadRequest.EncodedSaveData }, { "@PlayStyle", playStyle.ToString() }, }; uploadId = Convert.ToInt32(command.ExecuteScalar()); // Insert computed stats command.CommandText = @" INSERT INTO ComputedStats( UploadId, TitanDamage, SoulsSpent, HeroSoulsSacrificed, TotalAncientSouls, TranscendentPower, Rubies, HighestZoneThisTranscension, HighestZoneLifetime, AscensionsThisTranscension, AscensionsLifetime, MaxTranscendentPrimalReward, BossLevelToTranscendentPrimalCap) VALUES( @UploadId, @TitanDamage, @SoulsSpent, @HeroSoulsSacrificed, @TotalAncientSouls, @TranscendentPower, @Rubies, @HighestZoneThisTranscension, @HighestZoneLifetime, @AscensionsThisTranscension, @AscensionsLifetime, @MaxTranscendentPrimalReward, @BossLevelToTranscendentPrimalCap);"; command.Parameters = new Dictionary <string, object> { { "@UploadId", uploadId }, { "@TitanDamage", miscellaneousStatsModel.TitanDamage.ToTransportableString() }, { "@SoulsSpent", miscellaneousStatsModel.HeroSoulsSpent.ToTransportableString() }, { "@HeroSoulsSacrificed", miscellaneousStatsModel.HeroSoulsSacrificed.ToTransportableString() }, { "@TotalAncientSouls", miscellaneousStatsModel.TotalAncientSouls }, { "@TranscendentPower", miscellaneousStatsModel.TranscendentPower }, { "@Rubies", miscellaneousStatsModel.Rubies }, { "@HighestZoneThisTranscension", miscellaneousStatsModel.HighestZoneThisTranscension }, { "@HighestZoneLifetime", miscellaneousStatsModel.HighestZoneLifetime }, { "@AscensionsThisTranscension", miscellaneousStatsModel.AscensionsThisTranscension }, { "@AscensionsLifetime", miscellaneousStatsModel.AscensionsLifetime }, { "@MaxTranscendentPrimalReward", miscellaneousStatsModel.MaxTranscendentPrimalReward.ToTransportableString() }, { "@BossLevelToTranscendentPrimalCap", miscellaneousStatsModel.BossLevelToTranscendentPrimalCap }, }; command.ExecuteNonQuery(); // Insert ancient levels foreach (var pair in ancientLevels.AncientLevels) { command.CommandText = @" INSERT INTO AncientLevels(UploadId, AncientId, Level) VALUES(@UploadId, @AncientId, @Level);"; command.Parameters = new Dictionary <string, object> { { "@UploadId", uploadId }, { "@AncientId", pair.Key }, { "@Level", pair.Value.AncientLevel.ToTransportableString() }, }; command.ExecuteNonQuery(); } // Insert outsider levels foreach (var pair in outsiderLevels.OutsiderLevels) { command.CommandText = @" INSERT INTO OutsiderLevels(UploadId, OutsiderId, Level) VALUES(@UploadId, @OutsiderId, @Level);"; command.Parameters = new Dictionary <string, object> { { "@UploadId", uploadId }, { "@OutsiderId", pair.Key }, { "@Level", pair.Value.Level }, }; command.ExecuteNonQuery(); } var commited = command.CommitTransaction(); if (!commited) { return(this.StatusCode((int)HttpStatusCode.InternalServerError)); } } return(this.Ok(uploadId)); }