为什么我的MVC操作偶尔会给我一个“违反PRIMARY KEY约束”的错误?

时间:2015-02-17 12:59:54

标签: c# sql-server entity-framework

我的MVC项目中有一个Index动作,它接受一个整数作为参数,表示我SQL Server数据库中电影标题的ID。如果该ID已存在于数据库中,则会使用电影标题的数据填充ViewModel并将其传递回视图。如果ID是新ID,则创建,填充,添加到上下文并保存到数据库的新电影Title对象。数据库的ID 不会自动分配。下面的操作代码(为简洁和清晰起见,删除/省略了一些代码):

    [HttpGet]
    public async Task<ActionResult> Index(int? i)
    {
        if (i.HasValue)
        {
            var movieViewModel = new MovieViewModel();
            using (var context = new someEntities())
            {
                //check if this id already exists in the DB
                var matchedTitle = await context.Titles.FindAsync(i.Value);
                if (matchedTitle != null && matchedTitle.Category == "movie")
                {                     
                    //matchedTitle already in the DB.. 
                    //..so populate movieViewModel with matchedTitle
                }
                else
                {
                    //matchedTitle not in the DB, generate a new Title item
                    var newTitle = TitleHelper.GenerateNewTitle(i.Value, /* .. */);                     
                    //add and save
                    context.Titles.Add(newTitle);                        
                    await context.SaveChangesAsync();                        
                    //code omitted for brevity
                }
            }
            Response.StatusCode = (int)HttpStatusCode.OK;
            return View(movieViewModel);
        }
        else
        {
            return View();
        }
    }

此工作正常,但在高流量时偶尔会产生以下错误:

  

违反PRIMARY KEY约束&#39; PK_Title_Title_Id&#39;。无法插入   对象&#39; dbo.Title&#39;中的重复键。重复的键值是   (0147852)。声明已经终止。

我无法故意重现错误。如果我在该视图上点击刷新,则错误消失并且视图正常加载。我错过了支票还是这是竞争条件?我原以为EF能够解决这个问题。

更新

评论中要求的一些其他信息:

  • 如何生成ID?

    他们的ID是根据电影的相应IMDb ID生成的。

  • 如何生成新标题?

    public static Title GenerateNewTitle(int id, //other properties)
    {           
        Title newTitle = new Title
        {
            //property population here
        }
        return newTitle;
    }       
    

2 个答案:

答案 0 :(得分:1)

前一段时间已经解决了这个问题,但是由于我没有提供任何答案,我提供了以下解决方案以供将来参考。

正如Damien_The_Unbeliever在OP的评论中所建议的那样,问题是由quick'n'dirty解决方案的半完整,未经测试的代码导致的典型竞争条件(错误2627)虽然只是在高流量时发生。除非在检查之前锁定表,否则在插入之前检查对象是否已存在不是解决方案。否则,有人可能会在检查后和插入之前插入对象。

我目前正在覆盖OnException System.Web.Mvc.Controller来处理我的例外情况,但"JFDI" a quick good way to go是关于此的:{/ p>

using System.Data.Entity.Infrastructure;

try
{
    await context.SaveChangesAsync();
}
catch(DbUpdateException ex)
{
    var sqlException = ex.InnerException.InnerException as SqlException;
    if (sqlException != null && sqlException.Number == 2627)
    {
        //violation of primary key constraint has occurred here, act accordingly
        //e.g. pass the populated viewmodel back to the view and return
    }
}

答案 1 :(得分:0)

如果在category = show的位置进行编辑,则会抛出错误,因为  var matchedTitle = await context.Titles.FindAsync(i.Value)会找到i.value个ID的记录,但matchedTitle.Category == "movie"将不匹配,因为这是show类别的类型,TitleHelper.GenerateNewTitle(/* .. */)此功能必须是仅从i.value分配PK ID,因此它会抛出PK违规错误。

试用下面的代码

    var newTitle = TitleHelper.GenerateNewTitle(i.Value, /* .. */);
    if(newTitle.Title_Id > 0)
{
 // Query to update your record
}
else
{
  //add and save
  context.Titles.Add(newTitle);                        
  await context.SaveChangesAsync();
}