List <t> .ForEach(Action)和Sql Transaction的奇怪行为

时间:2018-03-09 12:47:54

标签: c# dapper sqltransaction

方案

我正在编写一个系统来执行考试。考试负责人(监考人员)开始考试,此时,参加考试的人(考生)可以开始考试。如果候选人试图提前开始,他们会收到一条消息,告诉他们等待监考人员。记录两个开始时间(考试开始和候选开始)。

当监考人员开始考试时,我们为考试设置exam start time,然后我们会找出每个考生在考试中可以有多少时间,然后将其添加到exam start time,从而给我们称之为scheduled finish

public void SetExamStart(Exam exam, List<ExamCandidate> examCandidates)
{
    DateTime startTime = _timeService.GetCurrentDateTime();

    exam.ExamStarted = startTime;

    _work.ExamRepository.Update(exam);

    examCandidates.ForEach(ec =>
    {
        ExamPaper examPaper = ec.ExamPaper;

        if (!examPaper.ExamDuration.HasValue)
        {
            return;
        }

        Int32 examPaperDuration = examPaper.ExamDuration.Value;

        DateTime scheduledFinish = startTime.AddMinutes(examPaperDuration);

        ec.ScheduledFinish = scheduledFinish;

        _work.ExamCandidateRepository.Update(ec);
    });
}

对存储库的调用属于同一事务。 ConnectionService是注入存储库的依赖项,并且基于每个Web请求将存储库注入UnitOfWork(_work)。

public virtual void Update(TEntity entity)
{
    IDbConnection connection = ConnectionService.Connection;

    try
    {
        connection.Execute(_queryGenerator.UpdateQuery, entity, ConnectionService.Transaction);
    }
    catch (SqlException ex)
    {
        throw new DataAccessException("An error occured on query execution", ex);
    }            
}

当候选人试图开始时:

public void StartCandidateExam(Guid examCandidateId)
{
    ExamCandidate examCandidate = _work.ExamCandidateRepository.Get(examCandidateId);

    Exam exam = examCandidate.Exam;

    if (!exam.ExamStarted.HasValue) {
        throw new ExamNotStartedException("Please wait for the invigilator to start the exam");
    }

    _candidateExamService.StartExam(examCandidate);

    _work.SaveChanges();
}

public void StartExam(ExamCandidate examCandidate)
{
    DateTime currentTime = _timeService.GetCurrentDateTime();

    examCandidate.Started = currentTime;

    _work.ExamCandidateRepository.Update(examCandidate);
}

在整个项目中,我对某些实体使用了Lazy<T>。例如ExamPaper实体上的ExamCandidate属性。如下所示:

public class ExamCandidate
{
    //other properties

    public DateTime? ScheduledFinish { get; set; }

    public int ExamPaperID { get; set; }

    public LazyEntity<ExamPaper> ExamPaper { get; set; }
}

public class LazyEntity<T> : Lazy<T>
{
    public LazyEntity(Func<T> func) : base(func) {}

    public static implicit operator T(LazyEntity<T> lazy)
    {
        return lazy.Value;
    }
}

// setting the lazy in the exam candidate repository
examCandidate.ExamPaper = new LazyEntity<ExamPaper>(() =>
{
    return Work.ExamPaperRepository.Get(examCandidate.ExamPaperID);
});

问题:

一名考生在监考人员之后很快就开始了他们的考试非常,导致他们的ScheduledFinish没有被设置。这对于几秒钟后开始的其他候选人来说效果很好。

以下是日志:

+------------------------+-------------------------+
|       Log Type         |        Log Date         |
+------------------------+-------------------------+
| Exam Started           | 2018-03-08 15:00:58.370 |
| Candidate Exam Started | 2018-03-08 15:00:58.387 |
+------------------------+-------------------------+

+-------------------------+-------------------------+--------+-------------+
|         Started         |     ScheduledFinish     | ExamID | ExamPaperID |
+-------------------------+-------------------------+--------+-------------+
| 2018-03-08 15:00:58.387 | NULL                    |     42 |          34 |
| 2018-03-08 15:01:01.727 | 2018-03-08 15:30:58.370 |     42 |          34 |
| 2018-03-08 15:01:02.507 | 2018-03-08 15:30:58.370 |     42 |          56 |
| 2018-03-08 15:01:02.770 | 2018-03-08 15:30:58.370 |     42 |          56 |
| 2018-03-08 15:01:02.960 | 2018-03-08 15:30:58.370 |     42 |          34 |
+-------------------------+-------------------------+--------+-------------+

我看不出这个原因,考试的开始和每个预定完成的设置都是在相同的操作中完成的,在相同的交易下,候选人在这种情况发生之前无法启动。

我有什么遗失的吗?

- 编辑 澄清一下,这个问题在一年之内发生了两次。在监考人员开始后,候选人很快就开始了。

1 个答案:

答案 0 :(得分:0)

我认为你的问题是你首先在SetExamStart中设置exam.ExamStarted = startTime而在候选人中设置ScheduledFinish。可以调用StartCandidateExam,因为ExamStarted.HasValue为true,但您的ExamCandidate ScheduledFinish可以为null。

将SetExamStart更改为此以确保已设置ScheduledFinish:

public void SetExamStart(Exam exam, List<ExamCandidate> examCandidates)
{
    DateTime startTime = _timeService.GetCurrentDateTime();

    examCandidates.ForEach(ec =>
    {
       ExamPaper examPaper = ec.ExamPaper;

       if (!examPaper.ExamDuration.HasValue)
       {
          return;
       }

       Int32 examPaperDuration = examPaper.ExamDuration.Value;

       DateTime scheduledFinish = startTime.AddMinutes(examPaperDuration);

       ec.ScheduledFinish = scheduledFinish;

       _work.ExamCandidateRepository.Update(ec);
    });

    exam.ExamStarted = startTime;

   _work.ExamRepository.Update(exam);
}

您可能必须更改此logik

 if (!examPaper.ExamDuration.HasValue) 
 ...