Hello,
I develop a quiz application. In my database design, there is many-to-many relationship between Test takers and questions as same exam questions may concern various test takers and each test taker will have many questions of the same exam. My problem is that I cannot write right action in controller so that when each user submit their answer while they are logged in, and if it's true, the relevant question score for each user must be saved in database. So, considering this, my model is like this:
Question
public class Question { public Question() { Answers = new HashSet<Answer>(); } public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } public Exam Exam { get; set; } public int ExamId { get; set; } public TimeSpan? Remainedtime { get; set; } public int Score { get; set; } public ICollection<Answer> Answers { get; set; } public ICollection<UserQuestion> UserQuestions { get; set; } }
ExamUser
public class ExamUser: IdentityUser { public TestTaker TestTaker { get; set; } public int? TestTakerId { get; set; } }
TestTaker
public class TestTaker { public int Id { get; set; } [Required] public string Name { get; set; } public string Phone { get; set; } public string Education { get; set; } public string Job { get; set; } public DateTime Birth { get; set; } public ExamUser ExamUser { get; set; } public int Result { get; set; } public ICollection<UserQuestion> UserQuestions { get; set; } }
UserQuestion (moderate table for many-to-many relationship)
public class UserQuestion { public int TestTakerId { get; set; } public TestTaker TestTaker { get; set; } public int QuestionId { get; set; } public Question Question { get; set; } }
The reason why I didn't connect ExamUser directly to questions is that its built in Id is string (it might cause problem while inserting to database).
In DbContext
public class IntellectDbContext:IdentityDbContext<ExamUser> { public IntellectDbContext(DbContextOptions<IntellectDbContext> dbContextOptions) : base(dbContextOptions) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<UserQuestion>() .HasKey(a => new { a.TestTakerId, a.QuestionId }); modelBuilder.Entity<UserQuestion>() .HasOne(a => a.TestTaker) .WithMany(b => b.UserQuestions) .HasForeignKey(a => a.TestTakerId); modelBuilder.Entity<UserQuestion>() .HasOne(a => a.Question) .WithMany(c => c.UserQuestions) .HasForeignKey(a => a.QuestionId); } public DbSet<Answer> Answers { get; set; } public DbSet<Question> Questions { get; set; } public DbSet<Exam> Exams { get; set; } public DbSet<ExamUser> ExamUsers { get; set; } public DbSet<UserQuestion> UserQuestions { get; set; } public DbSet<TestTaker> TestTakers { get; set; } }
View
@model Intellect.Models.ViewModels.AdminViewModel @{ Layout = "AdminLayout"; }<div class="questioncontainer"> <form asp-action="Question" asp-controller="Home" asp-route-id="@Model.NextQuestion.Id" asp-route-count="@ViewBag.Equestions"><div class="row"><div class="col-lg-3"></div><div class="col-lg-6 col-sm-12"><table><tr><th>Qaliq vaxt</th></tr><tr><td><input asp-for="Question.Score" id="time" name="timer" /></td></tr></table><div class="question">@Model.CurrentQuestion.Description </div></div><div class="col-lg-3"></div></div><div class="row"><div class="col-lg-3 col-sm-12"> @foreach (Answer item in Model.Answers) {<input asp-for="@item.Id" name="@item.Id" hidden /><input type="radio" asp-for="@item.Id" name="myanswer" value="@item.Id" />@item.Description<br> }</div><div class="col-lg-3"></div></div><div class="row"><div class="col-lg-6 col-sm-4"></div><div class="col-lg-3 col-sm-4"></div><div class="col-lg-3 col-sm-4"><div class="nextbtn"> @if (ViewBag.Equestions == 0) {<input type="submit" value="Finish" /> } else {<input type="submit" value="Next" /> }</div></div></div></form></div> @section Script{ <script> function StartTimer(seconds) { var intSeconds = seconds; var timer = setInterval(myTimer, 1000); function myTimer() { if (intSeconds < 0) { Alert("bitdi") clearInterval(timer) return; } document.getElementById("time").value = intSeconds; intSeconds--; } } StartTimer(60);</script> }
Here is the Controller where error lies
public class HomeController : Controller
{
private readonly IntellectDbContext _intellectDbContext;
private readonly UserManager<ExamUser> _userManager;
private readonly SignInManager<ExamUser> _signInManager;
static int exam_id = 0;
static int? PreviousId = 0;
static int result = 0;
static int correctAnswer = 0;
static List<Question> RemainedQuestions = new List<Question>();
static List<int> trueAnswers = new List<int>();
public HomeController(IntellectDbContext intellectDbContext, UserManager<ExamUser> userManager, SignInManager<ExamUser> signInManager)
{
_intellectDbContext = intellectDbContext;
_userManager = userManager;
_signInManager = signInManager;
}
[HttpGet]
public async Task<IActionResult> Test(int Id)
{
exam_id = Id;
AdminViewModel admodel = new AdminViewModel();
admodel.Equestions = await _intellectDbContext.Questions.Include(q => q.Answers).Where(q => q.ExamId == Id).ToListAsync();
admodel.CurrentQuestion = await _intellectDbContext.Questions.Where(q => q.ExamId == Id).FirstOrDefaultAsync();
RemainedQuestions = admodel.Equestions;
PreviousId = admodel.CurrentQuestion.Id;
return View(admodel);
}
[HttpGet]
public async Task<IActionResult> Question(int Id, int count)
{
AdminViewModel admodel = new AdminViewModel();
admodel.CurrentQuestion = await _intellectDbContext.Questions.Where(x => x.Id == Id).SingleOrDefaultAsync();
admodel.Answers = await _intellectDbContext.Answers.Where(y => y.QuestionId == Id).ToListAsync();
admodel.Equestions = await _intellectDbContext.Questions.Where(q => q.ExamId == exam_id).ToListAsync();
if (count > 1)
{
var question = RemainedQuestions.Single(r => r.Id == admodel.CurrentQuestion.Id);
PreviousId = question.Id;
RemainedQuestions.Remove(question);
admodel.NextQuestion = RemainedQuestions[0];
count -= 1;
}
else
{
admodel.NextQuestion = RemainedQuestions[0];
count -= 1;
}
if (count == -1)
{
return RedirectToAction(nameof(Finish));
}
ViewBag.Equestions = count;
return View(admodel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Question(int Id, int count, int myanswer, int timer)
{
AdminViewModel admodel = new AdminViewModel();
admodel.CurrentQuestion = await _intellectDbContext.Questions.Where(x => x.Id == Id).SingleOrDefaultAsync();
admodel.Answers = await _intellectDbContext.Answers.Where(y => y.QuestionId == Id).ToListAsync();
admodel.Equestions = await _intellectDbContext.Questions.Where(q => q.ExamId == exam_id).ToListAsync();
correctAnswer = _intellectDbContext.Answers.Where(a => a.QuestionId == PreviousId && a.Correct == true).SingleOrDefault().Id;
if (_signInManager.IsSignedIn(User))
{
ExamUser examTaker = await _userManager.GetUserAsync(HttpContext.User);
examTaker.TestTaker = await _intellectDbContext.TestTakers.FirstOrDefaultAsync();
admodel.CurrentQuestion = await _intellectDbContext.Questions.Include(q => q.UserQuestions).Where(u => u.Id == Id).SingleOrDefaultAsync();
if (myanswer == correctAnswer)
{
admodel.CurrentQuestion.Score = timer;
await _intellectDbContext.SaveChangesAsync();
// admodel.CurrentQuestion.Score = result;
}
}
if (count > 1)
{
var question = RemainedQuestions.Single(r => r.Id == admodel.CurrentQuestion.Id);
PreviousId = question.Id;
RemainedQuestions.Remove(question);
admodel.NextQuestion = RemainedQuestions[0];
count -= 1;
}
else
{
admodel.NextQuestion = RemainedQuestions[0];
count -= 1;
}
if(count == -1)
{
return RedirectToAction(nameof(Finish));
}
ViewBag.Equestions = count;
return RedirectToAction(nameof(Question));
}
I need to write right LINQ in highlighted code so that admodel.currentQuestion would give me the question with that Id that concerns TestTaker which is logged in (whose Id is say 10). So, after running the application, when clicking on Next button to go the following question, SQL Exception rises: https://prnt.sc/selfav
Of course, when testTaker enrolls for the exam, userQuestions table is fillied for its Id with that exam's question Ids.
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Enroll(TestTaker testTaker, DateTime birthdate) { if (ModelState.IsValid) { testTaker.Birth = birthdate; testTaker.Result = 0; testTaker.UserQuestions = await _intellectDbContext.Questions.Where(q => q.ExamId == 3).Select(q => new UserQuestion { Question = q, TestTaker = testTaker }).ToListAsync(); _intellectDbContext.TestTakers.Add(testTaker); await _intellectDbContext.SaveChangesAsync(); return RedirectToAction(nameof(Proceed)); } else { ModelState.AddModelError("", "Kecmedi"); return View(); } }
It's like it sees that I'm trying to insert testTaker again to database while I need to set admodel.currentQuestion to that testTaker's Id so that its score would calculated for the TestTaker with id say 10. Also, this solutiuon must work when several test takers take exam, like you know from Trivia game.