Monday 31 October 2016

c# - Entity Framework claims to save changes but sometimes doesn't



I have a very strange problem - sometimes my database fails to populate with Plans and Buckets, then throws the exception shown at the end when trying to save Tasks to the db. Other times the code works perfectly. I would appreciate any insight into why this could be. Could it be a timing issue with the delete commands?




EntitiesModelContainer db = new EntitiesModelContainer();

List ignoredPlans = db.Plans.Where(p => p.Ignore).ToList();

// Delete old data completely
// foreign keys will cascade delete some tables such as buckets and tasks
db.Database.ExecuteSqlCommand("DELETE FROM Plans");
db.Database.ExecuteSqlCommand("DELETE FROM Assignees");

PlansResponse plans = GraphAPI.GetPlans();

foreach (PlanResponse plan in plans.Plans)
{
Plan planRecord = new Plan()
{
Id = plan.ID,
Title = plan.Title,
Ignore = ignoredPlans.Find(p => p.Id == plan.ID) != null
};
db.Plans.Add(planRecord);
bool changes = db.ChangeTracker.HasChanges();

int result = db.SaveChanges();

if (!planRecord.Ignore)
{
BucketsResponse buckets = GraphAPI.GetBuckets(plan);
foreach (BucketResponse bucket in buckets.Buckets)
{
Bucket bucketRecord = new Bucket()
{
Id = bucket.ID,

Name = bucket.Name,
PlanId = bucket.PlanID
};
db.Buckets.Add(bucketRecord);
db.SaveChanges();
}
TasksResponse tasks = GraphAPI.GetTasks(plan);
foreach (TaskResponse task in tasks.Tasks)
{
Task taskRecord = new Task()

{
Id = task.ID,
Title = task.Title,
Progress = task.PercentComplete,
Description = task.HasDescription ? GraphAPI.GetTaskDetails(task).Description : null,
CreatedDateTime = task.CreatedDateTime,
DueDateTime = task.DueDateTime,
CompletedDateTime = task.CompletedDateTime,
PlanId = task.PlanID,
BucketId = task.BucketID

};
db.Tasks.Add(taskRecord);
db.SaveChanges();


The last line is where the error occurs.




SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_PlanTask". The conflict occurred in database "Dashboard-dev", table "dbo.Plans", column 'Id'. The statement has been terminated.





The strange thing is, it doesn't always throw! Any help would be appreciated.



Update: You're right - it appears the code is running twice, concurrently. Why? I didn't (intentionally) mean to do this...



public class DBController : Controller
{
public ActionResult Index()
{
DBAccess.UpdateData();

return View();
}
}


The snippet posted above is the start of this method UpdateData().


Answer



Since it's complaining about the foreign key PlanId (I'm guessing), I'd say GetTasks somehow returns an incorrect PlanId.



To get to the bottom of it I'd set up a profiler session and examine the SQL statements when there's an error. Or just examine the objects in the debugger when the exception happens, I guess. The values of the properties should give a clue as to what's going on.




Edit: I just noticed your first line says 'sometimes it fails to populate with plans and tasks', missed that. I don't understand how it would continue if it couldn't save a plan, but a SQL profiler session might be able to answer that.



Edit after the fact: of course the simplest answer could be concurrency, especially if this is a web application, two requests could be coming in at the same time and overlapping.


No comments:

Post a Comment

c++ - Does curly brackets matter for empty constructor?

Those brackets declare an empty, inline constructor. In that case, with them, the constructor does exist, it merely does nothing more than t...