实体框架在很长一段时间内随机插入许多重复记录

时间:2017-11-02 08:48:18

标签: asp.net entity-framework

很少且显然是随机的,实体框架会插入许多重复记录。谁能解释为什么会出现这种情况?这是我见过的第二个项目:

protected void btnAddQual_Click(object sender, EventArgs e)
    {            
        QEntities ds = new QEntities();
        Qualification qual = new Qualification();
        qual.PersonID = ds.Persons.Where(x => x.Username == User.Identity.Name).Single().PersonID;
        qual.QualificationName = txtQualAddName.Text;
        qual.QualificationProvider = txtQualAddProvider.Text;
        qual.QualificationYear = txtQualAddYear.Text;            
        qual.Inactive = false;
        qual.LastUpdatedBy = User.Identity.Name;
        qual.LastUpdatedOn = DateTime.Now;
        ds.Qualifications.Add(qual);
        ds.SaveChanges();
    }

资格表:

public partial class Qualification
{
    public int QualificationID { get; set; }
    public int PersonID { get; set; }
    public string QualificationName { get; set; }
    public string QualificationProvider { get; set; }
    public string QualificationYear { get; set; }
    public bool Inactive { get; set; }
    public string LastUpdatedBy { get; set; }
    public Nullable<System.DateTime> LastUpdatedOn { get; set; }

    public virtual Persons Persons { get; set; }
}

我已经看到它在一次按键点击中创建了3到32条记录,当它出现时,可以在很长一段时间内传播的时间戳(上次是28条记录,除主键外全部相同)和时间戳,在23分钟内分布不均匀)

我之前已将此归结为基于用户或浏览器的行为,但昨晚它发生在我使用该机器上。 当时我并没有注意到任何不寻常的事情,但是它不经常发生使它成为追踪的魔鬼。任何人都可以建议一个原因吗?

使用其他信息进行编辑:

这是使用.net framework 4.5.2和EF 6.1.3

编辑以解释赏金:

我刚看到这发生在以下代码中:

  using(exEntities ds = new exEntities())
        {
            int initialStations;
            int finalStations;
            int shouldbestations = numStations * numSessions * numRotations * numBlock;

            initialStations = ds.Stations.Count();


            for(int b = 1; b <= numBlock; b++)
            {
                for (int se = 1; se <= numSessions; se++)
                {
                    for (int r = 1; r <= numRotations; r++)
                    {
                        for (int st = 1; st <= numStations; st++)
                        {
                            Stations station = new Stations();
                            station.EID = eID;
                            station.Block = b;
                            station.Rotation = r;
                            station.Session = se;
                            station.StationNum = st;
                            station.LastUpdatedBy = User.Identity.Name + " (Generated)";
                            station.LastUpdatedOn = DateTime.Now;
                            station.Inactive = false;

                            ds.Stations.Add(station);
                        }
                    }
                }
            }
            ds.SaveChanges();

在这个例子中,每个循环的迭代次数是: 分别为1,2,6,5。

这一次点击(同一时间戳)复制了整套记录

1 个答案:

答案 0 :(得分:1)

在这种情况下,您需要向应用程序中添加日志记录。根据您的代码,我不认为Entity Framework会在复制您的数据,而是认为您的代码是以不适合您的方式触发的。我看到EF重复记录的原因是开发人员传递了从一个DBContext加载的实体,然后将它们与在另一个DBContext中创建的实体相关联,而无需检查DbContext并先附加它们。 EF会将其视为“新”,然后重新插入。从您提供的代码来看,我认为情况并非如此,但需要提防。

首先,特别是在处理Web应用程序时,应编写任何事件处理程序或POST / PATCH API方法,以将每个调用视为不可信。在正常使用情况下,这些方法应该可以实现您期望的效果,但是在滥用或黑客尝试下,可以在不应该使用的条件下调用它们,或者携带不应该使用的负载。例如,您可能希望仅在更新记录1234并且用户按下“保存”按钮(一次)后,才会触发记录ID为1234的事件处理程序,但是可以:

  • 如果客户端代码在事件成功之前不禁用单击按钮,则多次接收事件。
  • 通过调试工具触发客户端时,无需客户端单击按钮即可接收事件。
  • 由于用户在调试工具中更改了记录ID,因此在保存其他记录时接收到ID为1234的事件。

不进行任何操作,验证并记录所有内容,如果不正确,请终止会话。 (强制注销)

对于日志记录,除了标准的异常日志记录之外,我建议为生产调试版本添加带有附加编译器常量的Information跟踪,以监视这种情况被多次触发的情况之一。我个人使用Diagnostics.Trace,然后将Log4Net之类的日志记录处理程序挂接到其中。

我建议添加如下内容:

#if DEBUG
  Trace.TraceInformation(string.Format("Add Stations called. (eIS: {0})", eID));
  Trace.TraceInformation(Environment.StackTrace);
#endif

然后,当您进行计数检查并发现问题时:

Trace.TraceWarning(string.Format("Add Station count discrepancy. Expected {0}, Found {1}", shouldBeStations, finalStations));

我将编译器置于条件下,因为Environment.StackTrace会产生生产系统中通常不希望的成本。您可以将“ DEBUG”替换为另一个自定义编译常数,以使部署可以检查此问题。让它在野外运行并监视您的跟踪输出(数据库,文件等),直到问题出现为止。当警告出现时,您可以返回“信息”轨迹以查看何时何地触发了代码。您还可以在加载屏幕的调用中进行类似的跟踪,在该屏幕上将触发此事件以记录ID和用户详细信息,以查看是否/如何通过代码错误或某些环境条件或黑客尝试触发了错误的eID事件

或者,对于日志记录,您还可以考虑在应用程序配置中添加一个设置,以基于日志记录级别或标志来打开和关闭日志记录事件,以开始/停止捕获方案而无需重新部署。

这类问题很难诊断和解决,例如StackOverflow,但是希望日志记录可以帮助突出显示您未考虑的问题。最坏的情况是,考虑让有EF和您的核心技术经验的人员短期访问系统,因为对整个工作情况的第二次审视也可能有助于指出潜在的原因。

一个小技巧。而不是像这样:

qual.PersonID = ds.Persons.Where(x => x.Username == User.Identity.Name).Single().PersonID;

使用:

qual.PersonID = ds.Persons.Where(x => x.Username == User.Identity.Name).Select(x => x.PersonID).Single();

第一条语句在实体尚未缓存的地方执行“ SELECT * FROM tblPersons WHERE ...”,并拉回所有只需要PersonID的列。第二个执行“从tblPersons WHERE ...中选择SELECT PersonID”