public async Task SaveSurveyResponseAsync([FromBody] ClientModels.SurveyAnswer surveyAnswer) { if (surveyAnswer == null) { throw new ArgumentNullException(nameof(surveyAnswer)); } if (ModelState.IsValid) { try { var queue = await _stateManager.GetOrAddAsync <IReliableConcurrentQueue <ClientModels.SurveyAnswer> >("surveyQueue"); if (surveyAnswer.Id == null) { surveyAnswer.Id = Guid.NewGuid().ToString(); } using (var tx = _stateManager.CreateTransaction()) { await queue.EnqueueAsync(tx, surveyAnswer); await tx.CommitAsync(); } } catch (Exception ex) { ServiceEventSource.Current.ServiceRequestFailed(ex.ToString()); throw; } } }
public async Task MergeSurveyAnswerToAnalysisAsync([FromBody] ClientModels.SurveyAnswer surveyAnswer) { try { var surveyAnswersSummaryCache = Connection.GetDatabase(); var success = false; do { var result = await surveyAnswersSummaryCache.StringGetAsync(surveyAnswer.SlugName); var isNew = result.IsNullOrEmpty; var transaction = surveyAnswersSummaryCache.CreateTransaction(); SurveyAnswersSummary surveyAnswersSummary = null; if (isNew) { transaction.AddCondition(Condition.KeyNotExists(surveyAnswer.SlugName)); surveyAnswersSummary = new SurveyAnswersSummary(surveyAnswer.SlugName); } else { surveyAnswersSummary = JsonConvert.DeserializeObject <SurveyAnswersSummary>(result); transaction.AddCondition(Condition.StringEqual(surveyAnswer.SlugName, result)); } ServiceEventSource.Current.Message("Slug name:{0}|Total answers:{1}", surveyAnswersSummary.SlugName, surveyAnswersSummary.TotalAnswers); // Add and merge the new answer to new or existing summary surveyAnswersSummary.AddNewAnswer(surveyAnswer.ToSurveyAnswer()); transaction.StringSetAsync(surveyAnswer.SlugName, JsonConvert.SerializeObject(surveyAnswersSummary)); //This is a simple implementation of optimistic concurrency. //If transaction fails, another user must have edited the same survey. //If so, try to process this survey answer again. //Another approach is to store each survey answer option as a separate hash in Redis and simply increment //the hash value. This technique will not cause collisions with other threads but will require a redesign of //how survey answer summaries are stored in redis. This approach is left to the reader to implement. success = await transaction.ExecuteAsync(); } while (!success); } catch (Exception ex) { ServiceEventSource.Current.ServiceRequestFailed(ex.ToString()); throw; } }
public static ApiModels.SurveyAnswer ToSurveyAnswer(this ClientModels.SurveyAnswer surveyAnswer) { if (surveyAnswer == null) { throw new ArgumentNullException(nameof(surveyAnswer)); } return(new ApiModels.SurveyAnswer() { CreatedOn = surveyAnswer.CreatedOn, QuestionAnswers = surveyAnswer.QuestionAnswers.Select(qa => qa.ToQuestionAnswer()).ToList(), SlugName = surveyAnswer.SlugName, Title = surveyAnswer.Title }); }