如何防止实体框架添加重复条目?

时间:2015-08-29 20:33:21

标签: c# asp.net-mvc entity-framework

我正在开发用于跟踪锻炼的应用程序以及我尝试添加的功能,以便用户可以添加具有多个相关锻炼记录的锻炼,其中每个记录都有相关的锻炼。这些中的每一个都由一个单独的实体建模,即Workout,ExerciseRecord和Exercise,具有以下关系:

  • 锻炼与许多练习记录有关。
  • ExerciseRecord与一项练习有关。

我还为每个实体实现了一个工作类和存储库单元(虽然我不确定这是否必要 - 我对ASP.Net MVC来说还是新手!)。

我添加锻炼的代码如下:

[HttpPost]
public ActionResult Edit(WorkoutViewModel viewModel)        
{        
    var exerciseRecordList = viewModel.ExerciseRecords;

    foreach (ExerciseRecord r in exerciseRecordList)
    {
        var e = exerciseList.Exercises
            .Where(ex => ex.ExerciseId == r.ExerciseId)
            .FirstOrDefault();

        r.Exercise = e;

        if (viewModel.WorkoutId != 0)
        {
            r.WorkoutId = viewModel.WorkoutId;
        }
    };

    Workout workout;

    if (viewModel.WorkoutId == 0)
    {
        workout = new Workout
        {
            WorkoutId = viewModel.WorkoutId,
            WorkoutDate = viewModel.WorkoutDate,
            Duration = viewModel.Duration,
            Exercises = exerciseRecordList
        };
    }
    else
    {
        workout = unitOfWork.WorkoutRepository.GetByID(viewModel.WorkoutId);
        workout.WorkoutDate = viewModel.WorkoutDate;
        workout.Duration = viewModel.Duration;
        workout.Exercises = exerciseRecordList;
    }

    if (ModelState.IsValid)
    {
        if (workout.WorkoutId == 0)
        {
            unitOfWork.WorkoutRepository.Insert(workout);
            unitOfWork.Save();
        }
        else
        {
            unitOfWork.WorkoutRepository.Update(workout);
            unitOfWork.Save();
        }

        TempData["message"] = "Workout has been saved";
        return RedirectToAction("List");
    }
    else
    {
        // When there has been a problem with the workout data values
        TempData["message"] = "Unable to save workout";
        return View(viewModel);
    }
}

此代码在创建新锻炼时有效,但它似乎会向数据库添加重复练习。当我收到以下错误时,我也无法使用它来编辑现有的锻炼:

INSERT语句与FOREIGN KEY约束冲突" FK_dbo.ExerciseRecords_dbo.Exercises_ExerciseId"。冲突发生在数据库" WorkoutTracker",table" dbo.Exercise",column' ExerciseId'中。 该声明已被终止。

我认为我的问题是尽管与数据库中已经存在的每个ExerciseRecord相关联的Exercise对象,但是它们需要附加到上下文,以便实体框架知道它们已经存在。

我是否采用这种方法走上了正确的轨道,或者是否有更容易的方法来实现我尝试做的事情,这基本上归结为:

  1. 创建锻炼对象
  2. 对于每个锻炼,创建 x 个ExerciseRecord对象
  3. 对于每个ExerciseRecord对象,关联一个现有的Exercise对象

2 个答案:

答案 0 :(得分:1)

我刚刚发现了问题所在。我的“创建/编辑”视图有一个带有自动完成功能的文本框,允许用户选择练习名称。选择后,然后为ExerciseRecord设置ExerciseId外键。

@Html.TextBoxFor(m => m.ExerciseRecords[i].Exercise.ExerciseName, new { @class = "autocomplete form-control", id = "", name = "ExerciseName_" + i, data_url = @Url.Action("AutoComplete") })
@Html.HiddenFor(m => m.ExerciseRecords[i].ExerciseId, new { id = "" + i, name = "ExerciseId_" + i, @class = "hidden-id" })

ExerciseRecords和Exercises之间存在多对一关系,因此ExerciseRecord对象具有ExerciseId外键以及Exercise对象导航属性。

public class ExerciseRecord
{
    public int ExerciseRecordId { get; set; }
    public int Reps { get; set; }
    public int Sets { get; set; }
    public decimal Weight { get; set; } 

    public virtual Exercise Exercise { get; set; }
    public int ExerciseId { get; set; }

    public virtual Workout Workout { get; set; }  
    public int WorkoutId { get; set; }
}

发生的事情是,在提交表单时,都设置了ExerciseId外键,以及相关的Exercise.ExerciseName(但没有其他的Exercise对象字段)。这似乎导致实体框架使用设置的ExerciseName创建重复练习,因为所有其他字段都设置为空值或0。

我现在通过将创建/编辑视图中的练习名称的文本框更改为以下内容来修改此问题:

<input type="text" class="autocomplete form-control" id="" data-url=@Url.Action("AutoComplete") value=@(Model.ExerciseRecords[i].Exercise == null?"":Model.ExerciseRecords[i].Exercise.ExerciseName)>

这仍然允许我选择一个练习名称并设置相应的ExerciseId,但是不设置相关练习对象的ExerciseName属性。

感谢大家的帮助。

答案 1 :(得分:0)

尝试按照您尝试做的顺序执行此操作基本归结为:

   //Find (or Create) a `Workout` entity
   if (viewModel.WorkoutId == 0)
   {
      workout = new Workout();
      unitOfWork.WorkoutRepository.Insert(workout);
      unitOfWork.SaveChanges();   //You need to save here to ensure the ID is set
   }
   else
   {
      workout = unitOfWork.WorkoutRepository.GetByID(viewModel.WorkoutId);
   }
   workout.WorkoutDate = viewModel.WorkoutDate;
   workout.Duration = viewModel.Duration;

   //You appear to already have a set of ExerciseRecords already 
   //linked to an Exercise, so now link them to the workout
   foreach (ExerciseRecord r in viewModel.ExerciseRecords)
   {

      //I can't work out why you are fetching Exercises by the
      //ExerciseRecord's ExerciseId then using that to set the Exercise
      //property. But I think that is why you are getting duplicates,
      //so I have removed that fetch. The foreign key is sufficient and it 
      //is already set
      r.WorkOutID = workout.ID; 
   };

    unitOfWork.SaveChanges();

参考文献:

Why does Entity Framework Reinsert Existing Objects into My Database?

Making Do with Absent Foreign Keys