117

I'm using EF4 and new to it. I have a many to many in my project and cannot seem to work out how to insert or update. I have build a small project just to see how it should be coded.

Suppose I have 3 tables

  1. Class : ClassID-ClassName
  2. Student : StudentID-FirstName-Surname
  3. StudentClass : StudentID-ClassID

After adding all the relationship and updated the model via model browser I have noticed that StudentClass does not appear, this seems to be default behaviour.

Now I need to do both an Insert and Update. How do you do it? Any code samples or link where I can download an example, or can you spare 5 mins?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
user9969
  • 15,632
  • 39
  • 107
  • 175

5 Answers5

149

In terms of entities (or objects) you have a Class object which has a collection of Students and a Student object that has a collection of Classes. Since your StudentClass table only contains the Ids and no extra information, EF does not generate an entity for the joining table. That is the correct behaviour and that's what you expect.

Now, when doing inserts or updates, try to think in terms of objects. E.g. if you want to insert a class with two students, create the Class object, the Student objects, add the students to the class Students collection add the Class object to the context and call SaveChanges:

using (var context = new YourContext())
{
    var mathClass = new Class { Name = "Math" };
    mathClass.Students.Add(new Student { Name = "Alice" });
    mathClass.Students.Add(new Student { Name = "Bob" });

    context.AddToClasses(mathClass);
    context.SaveChanges();
}

This will create an entry in the Class table, two entries in the Student table and two entries in the StudentClass table linking them together.

You basically do the same for updates. Just fetch the data, modify the graph by adding and removing objects from collections, call SaveChanges. Check this similar question for details.

Edit:

According to your comment, you need to insert a new Class and add two existing Students to it:

using (var context = new YourContext())
{
    var mathClass= new Class { Name = "Math" };
    Student student1 = context.Students.FirstOrDefault(s => s.Name == "Alice");
    Student student2 = context.Students.FirstOrDefault(s => s.Name == "Bob");
    mathClass.Students.Add(student1);
    mathClass.Students.Add(student2);

    context.AddToClasses(mathClass);
    context.SaveChanges();
}

Since both students are already in the database, they won't be inserted, but since they are now in the Students collection of the Class, two entries will be inserted into the StudentClass table.

Community
  • 1
  • 1
Yakimych
  • 17,612
  • 7
  • 52
  • 69
  • Hi,Thanks for your reply.My Scenario is that I need to insert 1 entry into the class and as x your example 2 entries into the StudentClass and no entry into Student.I am confused how I do that – user9969 Nov 23 '10 at 11:54
  • THAT WORKED A TREAT.Regarding the update .is anything special I need doing?EG .just updating the className for instance. – user9969 Nov 23 '10 at 13:50
  • Not sure what you mean by `is there anything special I need doing`... Yes, you can update the name and Add/Remove items to/from the student collection. Just make sure you don't change the `Id`. If you have a specific problem with the update, feel free to post your scenario here as a comment and I'll have a look. – Yakimych Nov 23 '10 at 13:53
  • Really appreciate your help.Thanks.Doing the update it create another one the same .Do I need to Attach or set something to "Modified". I tried ctx.Attach(Class) but fails as already in context.How do I structure the update?THANKS A MILLION – user9969 Nov 23 '10 at 15:24
  • Do I need to set up a Cascade on Update? – user9969 Nov 23 '10 at 15:29
  • Basically, you just query for the entity, modify properties and call `SaveChanges` no additional `Attach`, etc. calls are necessary, but the problem you've encountered may depend on your particular scenario. Can you provide your code for doing the update? – Yakimych Nov 23 '10 at 18:59
  • 3
    This will add an overhead of fetching from the database the items one needs to add. The Attach method can be used to add only a relation. See http://msdn.microsoft.com/en-us/data/jj592676.aspx and also http://stackoverflow.com/questions/11355019/add-many-to-many-relation-without-fetching-child-entity-from-database – Gnomo Jan 09 '14 at 11:48
  • How about deleting from existing collection? When I save the object it doesn't affect its collection – levi Dec 11 '15 at 12:54
  • It is important that AutodetectChanges is set to TRUE for this to work. ;) – David Bachmann Jeppesen Jan 27 '16 at 14:16
  • 5
    AddToClasses is the DbSet for Class? – Jo Smo Feb 05 '16 at 15:47
  • What if a class had alot of students? It results in a heavy fetching operation before every add. Is there a way to avoid this without manually creating a relations table and two sided one-to-many? –  Mar 23 '16 at 21:49
  • How about if the joining (many-many) table has a payload (additional fields)? EF does create an entity in this scenario. How would you add a record to the joining table then? – Caltor Aug 21 '16 at 15:43
  • 1
    Saved my day, example about adding class to the existing student helped me to solve the problem which i have been banging my head for 2 hrs. Thanks – Karthikeyan VK May 22 '17 at 14:18
  • In my code "mathClass.Students.Add..." line returns error: "Object reference not set to an instance of an object". Student is Null. – rostamiani Dec 11 '18 at 05:07
  • Thank you for the explanation in the beginning. – joegreentea May 16 '19 at 18:06
  • I believe: context.AddToClasses(mathClass); is same as context.Classes.Add(mathClass); You should only do it when you are creating a class. When updating you don't need to. But as soon as you retrieve the book to update do: db.Entry(mathClass).State = EntityState.Modified; – user1040323 Jun 12 '19 at 16:32
  • 1
    Don't forget to initialize your collections in the constructors for Class and Student. For example: public Class() { this.Students = new HashSet(); } – user1040323 Jun 12 '19 at 16:34
51

Try this one for Updating:

[HttpPost]
public ActionResult Edit(Models.MathClass mathClassModel)
{
    //get current entry from db (db is context)
    var item = db.Entry<Models.MathClass>(mathClassModel);

    //change item state to modified
    item.State = System.Data.Entity.EntityState.Modified;

    //load existing items for ManyToMany collection
    item.Collection(i => i.Students).Load();

    //clear Student items          
    mathClassModel.Students.Clear();

    //add Toner items
    foreach (var studentId in mathClassModel.SelectedStudents)
    {
        var student = db.Student.Find(int.Parse(studentId));
        mathClassModel.Students.Add(student);
    }                

    if (ModelState.IsValid)
    {
       db.SaveChanges();
       return RedirectToAction("Index");
    }

    return View(mathClassModel);
}
Stritof
  • 810
  • 7
  • 11
  • this saved my day thank you!!! they key part being the item.Collection(i => i.Students).Load(); part – Gerrie Pretorius Dec 26 '14 at 16:14
  • Just a side note, I had a InvalidOperationException when doing that, something like my entity didn't exist in the context. I just called context.MyEntityCollection.Attach(myEntity) to deal with it. – Kilazur Jul 02 '15 at 12:47
  • Considering the question is Insert AND Update, this answer completes the question and the accepted answer. Thank you very much! – Vahx Feb 25 '17 at 16:32
  • So mathClassModel has 2 collections, Students having its entities, and SelectedStudents having just its IDs, why? Does Entity Framework automapped them like when you have just one ChildId and Child entity? I dont think so – Fernando Torres Jul 31 '17 at 06:40
  • Helpful. If you don't Include() the many:many associations in a query, then EF does will not track changes to them. Your suggestion to use Context.Entry(entity).Collection(propertyName).Load(), per the docs, provides access to change tracking and loading information for a collection navigation that associates this entity to a collection of another entities. – Jeff Barnard Jul 07 '23 at 14:35
7

I use the following way to handle the many-to-many relationship where only foreign keys are involved.

So for inserting:

public void InsertStudentClass (long studentId, long classId)
{
    using (var context = new DatabaseContext())
    {
        Student student = new Student { StudentID = studentId };
        context.Students.Add(student);
        context.Students.Attach(student);

        Class class = new Class { ClassID = classId };
        context.Classes.Add(class);
        context.Classes.Attach(class);

        student.Classes = new List<Class>();
        student.Classes.Add(class);

        context.SaveChanges();
    }
}

For deleting,

public void DeleteStudentClass(long studentId, long classId)
{
    Student student = context.Students.Include(x => x.Classes).Single(x => x.StudentID == studentId);

    using (var context = new DatabaseContext())
    {
        context.Students.Attach(student);
        Class classToDelete = student.Classes.Find(x => x.ClassID == classId);
        if (classToDelete != null)
        {
            student.Classes.Remove(classToDelete);
            context.SaveChanges();
        }
    }
}
lenglei
  • 1,168
  • 10
  • 10
5

I wanted to add my experience on that. Indeed EF, when you add an object to the context, it changes the state of all the children and related entities to Added. Although there is a small exception in the rule here: if the children/related entities are being tracked by the same context, EF does understand that these entities exist and doesn't add them. The problem happens when for example, you load the children/related entities from some other context or a web ui etc and then yes, EF doesn't know anything about these entities and goes and adds all of them. To avoid that, just get the keys of the entities and find them (e.g. context.Students.FirstOrDefault(s => s.Name == "Alice")) in the same context in which you want to do the addition.

T.S.
  • 18,195
  • 11
  • 58
  • 78
Athina
  • 1,126
  • 9
  • 5
3

In entity framework, when object is added to context, its state changes to Added. EF also changes state of each object to added in object tree and hence you are either getting primary key violation error or duplicate records are added in table.