Insert/Update Many to Many Entity Framework . How to Do It

Insert/Update Many to Many Entity Framework . How do I do it?

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.

In EF Core 5, how can I insert an entity with a many to many relation by setting only the foreigns keys IDs, without querying first?

The issue will be due to the tracking, or lack of tracking on the Tags. Since you don't want to query the database, then you can opt to Attach tag instances that you can guarantee are legal tag rows. If you have a reasonable # of Tag IDs to use you could create and attach the full set to reference. Otherwise you could derive it from the data IDs coming in.

I.e. if we have 20 Tags to select from, ID 1-20:

for (int tagId = 1; tagId <= 20; tagId++)
{
var tag = new Tag { Id = tagId };
context.Tags.Attach(tag);
}

We don't need to track these tags separately in a list. Once associated with the DbContext we can use context.Tags, or to be extra cautious about reads, context.Tags.Local
then when populating your Posts:

var post = new Post { Text = "some text"});
post.Tags.Add(context.Tags.Local.Single(x => x.Id == 1));
post.Tags.Add(context.Tags.Local.Single(x => x.Id == 2));
posts.Add(post);
//...

context.Posts.AddRange(posts);

If you have a large # of tags and pass a structure in for the posts that nominate the Tag IDs you want to associate with each new post, then you can build a list from that:

var tags = postViewModels.SelectMany(x => x.TagIds)
.Distinct()
.Select(t => new Tag { Id == t)).ToList();

Such as the case where a provided set of post ViewModels contains a list of TagIds. We select all of the distinct Tag IDs, then build Tags to associate.

The caveat here is if the DbContext might already by tracking a Tag with any of the desired IDs. Calling Attach for a Tag that the DbContext might already have loaded will result in an exception. Whether you build a complete set of tags or build a set from the provided post, the solution should check the DbContext for any locally cached/tracked tags and only attach ones that aren't already tracked.

var tags = postViewModels.SelectMany(x => x.TagIds)
.Distinct()
.Select(t => new Tag { Id == t))
.ToList();
foreach(var tag in tags)
{
if (!context.Tags.Local.Any(x => x.TagId == tag.Id))
context.Tags.Attach(tag);
}

There may be a better way to build the Tags to attach to exclude existing tracked tags (such as using Except, though that requires an EqualityComparer) but we guard against attaching a Tag that is already tracked. From there we create the Posts and associate the desired tags as per the first example using context.Tags.Local. Every tag referenced in each post should have been attached or already tracked and available.

The remaining caveat here is that this assumes that the provided Tag actually exists in the database. We don't want to set the attached Tag's EntityState to anything like Added or Modified to avoid creating incomplete/invalid or replacing data in the Tags table.

Many to many Entity Framework Core how to insert

When you add the Post with specifying the PostId,it will get the following error:

Cannot insert explicit value for identity column in table 'Posts' when
IDENTITY_INSERT is set to OFF.

Just remove the PostId and the code will work fine:

ICollection<Tag> tags = new List<Tag> {
db.Tags.FirstOrDefault(i => i.TagId="Tag1"),
db.Tags.FirstOrDefault(i => i.TagId="Tag2")};
db.Posts.Add(new Post { Title="Title3", Content="Content3", Tags=tags});

db.SaveChanges();

Many to many entity framework core insert and update

Insert first. Try this.

var journeyAddress = _context.JourneyAddress.Where(p => p.journeyId == id);
_repo.Update(journey);
_context.JourneyAddress.RemoveRange(journeyAddress);
_context.SaveChanges();

_repo.Update will add journey.journeyAddress too. So you don't have to add again.

EDITED.

User = Journey

User.UserCourse = Journey.JourneyAddress

UserCourse = JourneyAddress

Sample Image

Before call function,

Sample Image

After call function,

Sample Image

How do I add and update items using Entity Framework Core 5 that have many to many relationships?

Finally! I got it working. I'm not sure this is the 'correct' way, but it seems to work.

I was under the impression that EF Core 5 didn't require joining tables in many-to-many relationships. However when I tried to execute without a joining table I was getting an error about a joining table not being present. I therefore added one as suggested.

I then manually created the TransportProvider, manually checked for a Tag and created if it didn't exist, then manually entered the joining table record. I still feel this probably isn't the most efficient way of doing things, but it works. Code in case anyone is interested:

public async Task<IActionResult> Create([Bind("ID,CompanyName,ContactName,ContactTelephone1,ContactTelephone2,ContactEmail,CompanyWebsite,AddressLine1,AddressLine2,Suburb,Province,PostCode,Country,Lat,Lng,SelectedTags,Notes,Disabled")] TransportProvider transportProvider)
{
if (ModelState.IsValid)
{
var selectedTags = !string.IsNullOrEmpty(transportProvider.SelectedTags) ? transportProvider.SelectedTags.Split(',') : new string[0];

transportProvider.TransportProviderTags = new List<TransportProviderTag>();

_context.TransportProviders.Add(transportProvider);

await _context.SaveChangesAsync();

foreach (var selectedTag in selectedTags)
{
var tag = _context.Tags.SingleOrDefault(t => t.Name.ToLower() == selectedTag);
if (tag == null)
{
tag = new Tag();
tag.Name = selectedTag;
_context.Tags.Add(tag);

await _context.SaveChangesAsync();
}

var tpt = new TransportProviderTag();
tpt.TransportProviderID = transportProvider.ID;
tpt.TagID = tag.ID;
transportProvider.TransportProviderTags.Add(tpt);

await _context.SaveChangesAsync();
}

return RedirectToAction(nameof(Index));
}

return View(transportProvider);
}

Updated context class:

public class AntelopeContext : DbContext
{
public AntelopeContext(DbContextOptions<AntelopeContext> options) : base(options)
{
}

public DbSet<TransportProvider> TransportProviders { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<TransportProviderTag> TransportProviderTags { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TransportProvider>().ToTable("TransportProvider");
modelBuilder.Entity<Tag>().ToTable("Tag");
modelBuilder.Entity<TransportProviderTag>().ToTable("TransportProviderTag");

modelBuilder.Entity<TransportProviderTag>()
.HasKey(tpt => new { tpt.TransportProviderID, tpt.TagID });
modelBuilder.Entity<TransportProviderTag>()
.HasOne(tpt => tpt.TransportProvider)
.WithMany(tp => tp.TransportProviderTags)
.HasForeignKey(tpt => tpt.TransportProviderID);
modelBuilder.Entity<TransportProviderTag>()
.HasOne(tpt => tpt.Tag)
.WithMany(t => t.TransportProviderTags)
.HasForeignKey(tpt => tpt.TagID);
}
}

And thanks @MilutinSpaic and @mj1313 for steering me in the right direction. Hopefully this will help someone else

How to add/update many to many relations with one end of entity in EF core

I create a demo to add and edit a teacher.(_context is database context)

Add a teacher:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Teacher teacher)
{
//get your desired dutyId with your own logic
var SelectedDutyIds = new int[] { 1 };
var teacherDuties = new List<TeacherDuty>();

if (ModelState.IsValid)
{
_context.Add(teacher);
await _context.SaveChangesAsync();


foreach (var id in SelectedDutyIds)
{
var item = new TeacherDuty()
{
TeacherId = teacher.Id,
DutyId = id,
};
teacherDuties.Add(item);
}
_context.AddRange(teacherDuties);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(teacher);
}

Edit the teacher: remove all the existing TeacherDuties of the teacher firstly and then add new ones.

 [HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(long id, Teacher teacher)
{
if (id != teacher.Id)
{
return NotFound();
}

if (ModelState.IsValid)
{
try
{
//your new dutyIds
var newSelectedDutyIds = new int[] { 3 };
var teacherDuties = new List<TeacherDuty>();


var tdList = await _context.TeacherDuties.Where(td => td.TeacherId == teacher.Id).ToListAsync() ;
_context.RemoveRange(tdList);

foreach (var newid in newSelectedDutyIds)
{
var item = new TeacherDuty()
{
TeacherId = teacher.Id,
DutyId = newid,
};
teacherDuties.Add(item);
}
_context.AddRange(teacherDuties);
_context.Update(teacher);

await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TeacherExists(teacher.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(teacher);
}

Refer to Entity framework core update many to many

Insert/Update Many to Many in an Entity Framework 6 Jointable

In your view, you are sending codigoModalidad and nombreCurso. You have to post these values to the controller.

Replace this:

public ActionResult Create([Bind(Include="codigoCurso,nombreCurso")] Curso curso)

for this:

public ActionResult Create(string nombreCurso, int codigoModalidad)

Then, inside your controller action, you have to create the Curso and relate it with Modalidad:

[HttpPost]
public ActionResult Create(string nombreCurso, int codigoModalidad)
{
Modalidad modalidad = new Modalidad();
modalidad.ModalidadId = codigoModalidad;
//if modalidad already exists in database, and you just want to make a relationship
ctx.Entry(modalidad).State = EntityState.Unchanged;
//if modalidad does not exists in database, and you want to insert it
//ctx.Entry(modalidad).State = EntityState.Added;

Curso curso = new Curso();
curso.nombreCurso = nombreCurso;
curso.Modalidades.Add(modalidad); //add our existing modalidad to our new course

ctx.Cursos.Add(curso);
ctx.SaveChanges();
}

EDIT 1

Inside Curso constructor:

public Curso() 
{
Modalidades = new HashSet<Modalidad>();
}

Inside Modalidad constructor:

public Modalidad()
{
Cursos = new HashSet<Curso>();
}

Hope it helps!



Related Topics



Leave a reply



Submit