Was trying out some code with the EF "code first" method and ran into a strange problem.
My datacontext:
public class BookmarkerDataContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasKey(u => u.UserId);
base.OnModelCreating(modelBuilder);
}
}
Where the user object is:
public class User
{
public long UserId { get; set; }
public ICollection<Tag> Tags { get; set; }
}
In my code I am doing something fairly simple:
public void UpdateUserTags(User user,ICollection<Tag> taglist)
{
user.Tags = new List<Tag>(user.Tags.Union(taglist));
datacontext.Users.Add(user);
datacontext.SaveChanges();
}
The user object I am passing to this function is the result of something like:
datacontext.Users.SingleOrDefault(u => u.UserId==id)
Everytime I call the UpdateUserTags function it seems to create a new Row in the User table instead of updating it. Am I doing something wrong here?
#Donald is correct, you need to Attach to the ObjectContext when making updates.
However, that is only if your entity is detached.
If sounds like you have already retrieved the single entity from the graph:
var user = datacontext.Users.SingleOrDefault(u => u.UserId==id);
Which means you don't need to Attach or get it again. Just do this:
var user = datacontext.Users.SingleOrDefault(u => u.UserId==id);
user.Tags = new List<Tag>(user.Tags.Union(taglist));
context.SaveChanges();
However, i wouldn't recommend replacing the entire Tags collection, add the tags:
user.Tags.Add(someTag);
HTH
I believe you want to Attach your object to the data context, instead of Adding it.
public void UpdateUserTags(User user,ICollection<Tag> taglist)
{
datacontext.Attach(user);
user.Tags = new List<Tag>(user.Tags.Union(taglist));
datacontext.SaveChanges();
}
Once it is attached, then the context becomes aware of the object. Once you save changes, they should be persisted to the database.
Won't this line
datacontext.Users.Add(user);
Always mark the user record as needing to be ADDED to the users table.
I think you have to DETACH the user from the old context and ATTACH it to the new to properly be able to update the record.
but I'm no EF wonk so ymmv
This has nothing to do with your particular issue but for lazy loading you're going to want Tags to be marked virtual
public virtual ICollection<Tag> Tags { get; set; }
Also, it looks like you're trying to add new tags for a user (or update their tags) if that is the case then you're going to want to use Attach as Donald suggested
Related
I have this piece of code
public int Update(Item item)
{
using (var ctx = new DataConext())
{
ctx.Entry(item).State = EntityState.Modified;
return ctx.SaveChanges();
}
}
Class Item
{
public string Name {get;set;}
public ICollection<Foobar> Foos {get;set;}
}
Class Foobar
{
public string FirstName {get;set;}
public string LastName {get;set;}
}
Lets say:
item.Foos.ElementAt(0).FirstName = "edited name"
SaveChanged() is executed but I have the 'old' values on the database and not 'edited name'...
I can see the correct changes in Local in debug.
Looks like your object came from a different context that the one you are using now. In that case you can't do that with a generic because you need to do a foreEach in your Foobar collection and change the state for each item individually.
What you have here is a disconnected entity graph so the entity is disconnected and change tracking is lost. You only set the state of the main entity and so EF assumes that everything else is unchanged.
Jullie Lerman's books is a good source to understand how this works
What I would do is I would keep this method for simple entities but make it virtual so you can inherit this repo to create specific entity repos and override the update method with a more specific implementation suitable to an entity like the one in your example.
An article that helped my to design such a repo was this: http://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application.
You are loading the object in a context and saving in another. Remove using (var ctx = new DataConext()) block, and search for a way to reach the context that loaded the item, then call SaveChanges(); Another way is pass the context to the method, like this public int Update(Item item, DbContext context) and save the changes.
Class Item
{
public string Name {get;set;}
public ICollection<Foobar> Foos {get;set;}
}
You need Include to include the Foos to the object manager. Right now, it is eager loading. Wherever you are loading the item, you have to include it.
You should use include, or you can use virtual to have them lazy load.
I have a teacher entity that has a child entity of ICollection called YogaClasses. When I modify the list of YogaClasses for a teacher and save, entity framework adds the new rows of YogaClasses to the table 'YogaClass' but it doesn't remove or edit the old rows. So I'm left with double the data. Shouldn't "context.SaveChanges()" in my repo be smart enough to know to delete removed classes and add the new ones (edit), not just add new ones?
In my controller I have something like this for a Teacher edit.
string userId = User.Identity.GetUserId();
Teacher teacher = teacherRepository.Find(userId);
//other code left out here
teacher.YogaClasses = GetSelectedClasses(Request.Form[2]);
// other stuff here
teacherRepository.Save();
In my reposity I have this;
public void Save()
{
context.SaveChanges();
}
Here is my teacher and YogaClass entity
public class Teacher
{
public int TeacherId { get; set; }
public virtual ICollection<YogaClass> YogaClasses { get; set; }
}
public class YogaClass
{
public int YogaClassId { get; set; }
[Index]
[Required]
public int TeacherRefId { get; set; }
[ForeignKey("TeacherRefId")]
public virtual Teacher Teacher { get; set; }
}
The problem here is the relationship has not been loaded at the time you set teacher.YogaClasses. So when you set teacher.YogaClasses, it assumes the new relationship will be added. (lazyloading is still a bit late in this case). The solution is somehow you ensure to load all the relationships first (using either Include or some fake access to trigger lazyloading first), then you set the new value normally:
//fake access to trigger lazy loading (of course this works only if
//lazy loading is enabled)
var someClasses = teacher.YogaClasses;
teacher.YogaClasses = GetSelectedClasses(Request.Form[2]);
You can also clear the old classes and add new ones with a foreach loop:
teacher.YogaClasses.Clear();
foreach(var c in GetSelectedClasses(Request.Form[2])){
teacher.YogaClasses.Add(c);
}
I have a dirty solution but it doesn't seem like it's the best one.
before I save the teacher context in my repo I can call this
public void DeleteYogaClasses(Teacher teacher)
{
foreach (var yogaClass in teacher.YogaClasses.ToList())
{
context.Entry(yogaClass).State = EntityState.Deleted;
}
context.SaveChanges();
}
you need to use Attach method of the context and pass in your existing teacher object and then make the changes and call SaveChanges method. From your code it is not clear what is your context, so hard to give working code. Here is a link explaining how to add/modify
You said you need to modify YogaClasses, so from this what I assume is a Teacher object already has some YogaClasses entries and you want to update some of those entries. What you need to do is, have a list of ids of YogaClasses that you need to modify then iterate on that ids list and in that iteration loop find the existing yogaClass and attach it to the context, modify it and then call save changes (preferablly when all the changes are done so that it is not a performance hit)
Here is a suodocode for this
UpdateTeacher(int teacherId)
{
var teacher = teacherRepository.Find(teacherId);
UpdateYoga(teacher);
}
private void UpdateYoga(Teacher teacher)
{
foreach(var yoga in teacher.YogaClasses)
{
db.Context.Attach(yoga);
yoga.YogaStyle = whatEverValue;
}
db.context.SaveChanges();
}
I am building a windows form application, and I use multiple DBContext instances (mostly one per Business layer call).
After literally days of dealing with an issue (while inserting new entities, the ones they referred to were added as new ones, instead of using the existing entities), I found out that the problem was I had to attach the existing entities to the context.
All was good for about 2 hours, when I then got errors while attaching: the entity with the same key exists in the context.
I tried testing before attaching (similar method for every entity type):
private void attachIfNeeded(POCO.Program myObject, myContext context)
{
if (!context.Set<POCO.Program>().Local.Any(e => e.ID == myObject.ID))
{
context.programs.Attach(myObject);
return true;
}
else
{
myObject = context.Set<POCO.Program>().Local.Single(e => e.ID == myObject.ID);
return false;
}
}
But the tests return false, but it still fails when attaching.
So basically, if I don't attach, it will add a new entity instead of using the existing (and intended) one. If I do attach, there's an error I can't figure out.
I have looked around (doing this the whole day now) and I actually (think I) know what the problem is:
The entity I am trying to add has multiple relationships, and other entities can be reached by multiple paths. Could that cause the problem?
Please help with this, solutions out there really make no sense to me and haven't worked.
I am really close to the point where I will try-catch around the attach statement and be done with it. But I will hate doing it.
Here are my entities (not all of them, but this should be enough):
public class Word
{
[Key]
public int ID {get;set;}
[Required]
public string word { get; set; }
public WordCategories category { get; set; }
public Word parent {get;set;}
public List<Unit> units { get; set; }
public Program program { get; set; }
public List<Lesson> lessons { get; set; }
public Word()
{
units = new List<Unit>();
lessons = new List<Lesson>();
}
}
public class Unit
{
[Key ]
public int ID { get; set; }
[Required]
public string name { get; set; }
public string description { get; set; }
public List<Lesson> lessons { get; set; }
public Program program {get;set;}
public List<Word> words { get; set; }
public Unit()
{
lessons=new List<Lesson>();
words = new List<Word>();
}
}
And here is where I am calling the attach method. The error is thrown on the first attach:
public int addWords(List<POCO.Word > words,int programID, int unitID,int lessonID)
{
CourseHelperDBContext context = getcontext();
int result;
foreach(POCO.Word a in words)
{
foreach (POCO.Unit b in a.units)
attachIfNeeded(b, context);
foreach(POCO.Lesson c in a.lessons )
attachIfNeeded(c,context);
attachIfNeeded(a.program,context);
if (a.parent != null)
attachIfNeeded(a.parent,context);
}
context.words.AddRange(words);
result = context.SaveChanges();
return result;
}
I cannot believe I'm having so many issues with this. I just want to store those entities, add some (I haven't gotten to the point where I would change them) and save it.
So far I've figured:
Some words are new, some exist and some are changed (mostly parent property);
All units exist, as do programs and lessons (so I need to attach them);
The object graph contains multiple paths to entities, some of which exist, some of which are new;
I am using a new context for every request. I run into other issues when I was using the same all the time. I found solutions that pointed to this pattern, and I think it's OK since that's what you'd do on an ASP MVC project.
All these could be causing problems, but I don't know which and how to work around them.
I think I can make this work by adding one word at a time, and pulling programs, lessons and units every time... but that means many many round trips to the DB. This can't be the way.
Back to this after quite some time, the problem in this case was that I needed to retrieve the entities that were present on my relationships.
The solution was neither attach (because it would fail if the entity is already attached) nor add (because it already existed on the DB).
What I should have done was to retrieve every entity related to the one I was adding.
This helped:
Entity Framework creating new entity with relationship to existing entity, results in attempt to create new copy of the existing entity
After attaching the entity, try setting the entity state to modified.
context.programs.Attach(myObject);
context.Entry(myObject).State = EntityState.Modified;
I think there's a mistake in your test logic.
If entity does not exist in database, you should be adding instead of attaching. Your code is attaching if it can't find an entity when it should really be adding.
Code to add a new entity (Create/Insert)
context.Set<T>.Add(entity);
Code to attach an entity (Update)
context.Set<T>.Attach(entity);
context.Entry(entity).State = EntityState.Modified;
If your code is failing on the first attach, that would be attachIfNeeded(b,context); ? I don't think you have shown us the code for this.
I share my experience with the same exception.
First, here is my code:
public void UpdateBulk(IEnumerable<Position> pDokumentPosition, DbDal pCtx)
{
foreach (Position vPos in pDokumentPosition)
{
vPos.LastDateChanged = DateTime.Now;
pCtx.Entry(vPos).State = EntityState.Modified;
}
pCtx.SaveChanges();
}
I got the same exception on the EntityState.Modified line.
In my case the problem was that, when set the vPos state to modified, then all the related objects (vPos.Document and vPos.Produkt) loaded in the context with unchanged state.
In the foreach first step it not makes any exception, just on the second step, because eg. the related Dokument entity has already been loaded in the memory/context (so the key property of the Dokument too).
And how i solve it? (maybe not the best solution):
I detach the related entites in every step with this lines:
if (vPos.Dokument != null)
{
pCtx.Entry(vPos.Dokument).State = EntityState.Detached;
}
if (vPos.Produkt!=null)
{
pCtx.Entry(vPos.Produkt).State = EntityState.Detached;
}
If you have better solution, I'm looking forward to it...
You can try this
context.words.Add(words);
result=context.SaveChanges();
Started a new ASP.NET MVC 3 application and getting the following error:
The primary key value cannot be deleted because references to this key
still exist.
How to solve this?
Models (EF code-first)
public class Journal
{
public int JournalId { get; set; }
public string Name { get; set; }
public virtual List<JournalEntry> JournalEntries { get; set; }
}
public class JournalEntry
{
public int JournalEntryId { get; set; }
public int JournalId { get; set; }
public string Text { get; set; }
}
Controller
//
// POST: /Journal/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Journal journal = db.Journals.Find(id);
db.Journals.Remove(journal);
db.SaveChanges(); // **exception occurs here**
return RedirectToAction("Index");
}
DB Setup
public class FoodJournalEntities : DbContext
{
public DbSet<Journal> Journals { get; set; }
public DbSet<JournalEntry> JournalEntries { get; set; }
}
Found the solution:
public class FoodJournalEntities : DbContext
{
public DbSet<Journal> Journals { get; set; }
public DbSet<JournalEntry> JournalEntries { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Journal>()
.HasOptional(j => j.JournalEntries)
.WithMany()
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
}
Source
If you delete a record from a table(lets say "blah"), which has other relationships with other tables (xyz,abc). By default, the database will prevent you from deleting a row in "blah" if there are related rows in one of the other tables.
Solution #1:
You can manually delete the related rows first,this may require a lot of work.
Solution #2:
an easy solution is to configure the database to delete them automatically when you delete a "blah" row.
Follow this open your Database diagram,and click on the properties on the relationship
In the Properties window, expand INSERT and UPDATE Specification and set the DeleteRule property to Cascade.
Save and close the diagram. If you're asked whether you want to update the database, click Yes.
To make sure that the model keeps entities that are in memory in sync with what the database is doing, you must set corresponding rules in the data model. Open SchoolModel.edmx, right-click the association line between "blah" and "xyz", and then select Properties.
In the Properties window, expand INSERT and UPDATE Specification and set the DeleteRule property to Cascade.
Solution and images taken from http://www.asp.net/web-forms/tutorials/getting-started-with-ef/the-entity-framework-and-aspnet-getting-started-part-2
In EF Core (3.1.8), the syntax is a bit different than the accepted answer but the same general idea, what worked for me is below:
modelBuilder.Entity<Journal>()
.HasMany(b => b.JournalEntries)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
In your query to select the item to delete or remove from the database you want to make sure that you are explicitly including the items as well, otherwise it will continue to throw a FK error, something like below.
var item = _dbContext.Journal.Include(x => x.JournalEntries).SingleOrDefault(x => x.Id == id);
I found it...
Go TO SQL Server
Make his Database diagrammed
Right click on relation ship line between parent and child and open the property of it.
Set INSERT And Update Specification and simply set DELETE RULE TO CASCADE.
Remember No code is required in Project FOR this PURPOSE and simply debug and enjoy it.
I'm new to MVC and Entity frame work but i've come across an issue and i'm not sure how to resolve it some guidance is more then welcome. I'm editing an object which has a sub list of different objects. I've got a working edit screen but i cant get the details stored back the database. I think the issue is around the context of the db but as i said i'm new to this. Here are the 2 classes i'm using:
public class Role
{
[Key]
public string Role { get; set; }
public virtual ICollection<Template> Templates { get; set; }
}
public class Template
{
[Key][Required]
public string TemplateID { get; set; }
[Required]
public string Header { get; set; }
[Required][DataType(DataType.MultilineText)]
public string Description { get; set; }
public virtual ICollection<CorrespondenceRole> Roles { get; set; }
}
I also have this in my context
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Template>()
.HasMany(c => c.Roles)
.WithMany(c => c.Templates)
.Map(m => m.ToTable("Template_Roles")
.MapLeftKey("TemplateID")
.MapRightKey("Role"));
}
If i use
public ActionResult Edit(Template ctemplate)
{
db.Entry(ctemplate).State = EntityState.Modified;
db.SaveChanges();
}
The changes to the template object are recorded to the database but any changes to the roles list are totally ignored even if i hardcode ctemplate.roles.add(somerole) before the db save.
However if i use
public ActionResult Edit(Template ctemplate)
{
Template ct = db.Templates.Find(ctemplate.TemplateID);
ct.Roles.Add(db.Roles.Find("Other Party"));
db.Entry(ct).State = EntityState.Modified;
db.SaveChanges();
}
The "role" is saved against in the template in the database. So i could use this code to find the object then copy all the form fields into it and save it but it seems long winded when the first option does actually work but it just does not save my list. Any comments or suggestions how best to save the changes to the object and its associated list of different objects? Thanks.
UPDATE
I found this worked for me, but i have no idea if its the best way to go about it.
db.Entry(ct).State = EntityState.Modified;
foreach (var entity in db.Roles.Where(cr => cr.TemplateID == ct.ID))
db.Roles.Remove(entity);
foreach (Role cr in ct.Roles)
db.Roles.Add(cr);
db.SaveChanges();
This worked for a similar problem for me !!
foreach (var entity in db.Roles.Where(cr => cr.TemplateID == ct.ID)) {
db.Entry(entity).State = EntityState.Modified;
}
db.Roles.Remove(entity);
Are the objects present in ctemplate in the save procedure? In your view are you also doing this:
foreach (var item in model.sub_object)
{
#Html.TextBoxFor(m => item.whatever)
}
Try something like this:
for (int i; i < model.sub_object.count(); i++)
{
#Html.TextBoxFor(m => model.sub_object[i].field)
}
There's not a fast way that I'm aware of. When you fetch your Template from the database, you want to load the associated roles as well (look up Eager Loading); that's not related to your problem but will save you on db round-trips.
Once your Roles are loaded by the context, you'll have to iterate over them and modify whatever's changed and add new ones and delete the old ones. It's manual, but it ensures that the datacontext knows what you want changed on the objects.
After that, calling save on the template should trickle down to the roles.
If your problem is what I think it is, this will solve it. The problem is that you need to change the EntityState of each element on your lists. Try this:
public ActionResult Edit(Template ctemplate)
{
db.Entry(ctemplate).State = EntityState.Modified;
foreach (CorrespondenceRole role in ctemplate.Roles)
{
//Change role State -> EntityState.Modified
}
db.SaveChanges();
}
If your elements are attached to your context, they should persist to the database... Let me know if you have any question...
Hope this helps.