How to perform CRUD with Entity Framework Code-First?
I am having a really hard time updating and deleting many to many relationships with EF Code-first.
I have a fairly simple Model:
public class Issue
{
[Key]
public int IssueId { get;开发者_运维问答 set; }
public int Number { get; set; }
public string Title { get; set; }
public DateTime Date { get; set; }
public virtual ICollection<Creator> Creators { get; set; }
}
public class Creator
{
[Key]
public int CreatorId { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Issue> Issues { get; set; }
}
public class Icbd : DbContext
{
public DbSet<Issue> Issues { get; set; }
public DbSet<Creator> Creators { get; set; }
}
I have been unable to figure out how to update the many-to-many realtionship using the EF context. In my isnert/update action, I have this code:
[HttpPost]
public ActionResult EditIssue ( Issue issue, int[] CreatorIds )
{
if(CreatorIds == null)
ModelState.AddModelError("CreatorIds", "Please specify at least one creator");
if(ModelState.IsValid)
{
// insert or update the issue record (no relationship)
if (issue.IssueId == 0)
db.Issues.Add(issue);
else
db.Entry(issue).State = System.Data.EntityState.Modified;
db.SaveChanges();
// delete - delete current relationships
if (issue.Creators != null)
{
issue.Creators = new List<Creator>();
db.Entry(issue).State = System.Data.EntityState.Modified;
db.SaveChanges();
}
// insert - get creators for many-to-many realtionship
issue.Creators = db.Creators.Where(x => CreatorIds.Contains(x.CreatorId)).ToList();
db.Entry(issue).State = System.Data.EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
IssueEditModel issueEdit = new IssueEditModel{
Creators = db.Creators.ToList(),
Issue = issue,
};
return View(issueEdit);
}
I can insert Issues
and I can insert new issue.Creators
without a problem. But, when I am trying to delete the current issue.Creators
so I can then insert the new ones, issue.Creators
is ALWAYS null so it will never update to an empty list. I don't understand this. issue.Creators
has records in it because when the code proceeds to insert the new creators, I get an error like this:
Violation of PRIMARY KEY constraint 'PK__CreatorI__13D353AB03317E3D'. Cannot insert duplicate key in object 'dbo.CreatorIssues'.
The statement has been terminated.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Data.SqlClient.SqlException: Violation of PRIMARY KEY constraint 'PK__CreatorI__13D353AB03317E3D'. Cannot insert duplicate key in object 'dbo.CreatorIssues'.
The statement has been terminated.
Source Error:
Line 59: issue.Creators = db.Creators.Where(x => CreatorIds.Contains(x.CreatorId)).ToList();
Line 60: db.Entry(issue).State = System.Data.EntityState.Modified;
Line 61: db.SaveChanges();
Line 62:
Line 63: return RedirectToAction("Index");
How do I get issue.Creators
to accurately show the current relationships so I can delete them?
Update: Working Controller
[HttpPost]
public ActionResult EditIssue ( Issue issue, int[] CreatorIds )
{
if(CreatorIds == null)
ModelState.AddModelError("CreatorIds", "Please specify at least one creator");
if(ModelState.IsValid)
{
// insert or update the issue record (no relationship)
if (issue.IssueId == 0)
db.Issues.Add(issue);
else
db.Entry(issue).State = System.Data.EntityState.Modified;
db.SaveChanges();
// insert and update many to many relationship
issue = db.Issues.Include("Creators").Where(x => x.IssueId == issue.IssueId).Single();
issue.Creators = db.Creators.Where(x => CreatorIds.Contains(x.CreatorId)).ToList();
db.Entry(issue).State = System.Data.EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
IssueEditModel issueEdit = new IssueEditModel{
Creators = db.Creators.ToList(),
Issue = issue,
};
return View(issueEdit);
}
The model binder isn't loading them up for you - your issues object coming from the view won't magically contain them unless you setup everything up properly for binding.
Without seeing your view one can't say why, but suffice to say you'll have to load them up, then delete them. You can load a new issues object and then do TryUpdateModel(issues) to get the form values updated into that model. Then delete each issues.Creators (if thats the intended action)
if(ModelState.IsValid)
{
var issueFromDb = db.Issues.Where(x => criteria here);
bool updateSuccessful = TryUpdateModel(issueFromDb);
foreach(var creator in issueFromDb.Creators)
{
//delete creator if thats what you want
}
However if you just want all of your creators to come back from the page without loading, check out binding to an enumerable. there are many posts out there on this, heres one: ASP.NET MVC3 Model Binding using IEnumerable (Cannot infer type from)
If the relationship is there, the Creators should automatically load just by loading the Issue. You don't need to load only the creators. Load your full model to be sure its working as I did in the edit above. Your code 'should' work ok but its possible you need to .Include("Creators") try this:
var testIssue = from o in db.Issues.Include("Creators") where o.IssueId == issue.IssueId select o; foreach(var creator in testIssue.Creators) { //check creator }
this will let you know if "Creators" is loading properly.
You are missing the key tag in the issue class (IssueId).
Once you have the duplicates you may need to go into the database and delete the rows manually
精彩评论