I have pored through StackOverflow, Google and asp.net trying to find a clear cut, basic example of how to do this. All the examples have been abstract or involved complications that do not apply. I haven't been able to extract much useful from them. So far, none of them have completely answered my question or addressed my issue(s).
I am working on an MVC project with the following model:
Article.cs:
public class Article
{
public int ArticleId { get; set; }
public string Title { get; set; }
.
.
.
public virtual ICollection<Category> Categories { get; set; }
public Article()
{
Categories = new HashSet<Category>();
}
}
Category.cs:
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public virtual ICollection<Article> Articles { get; set; }
public Category()
{
Articles = new HashSet<Article>();
}
}
ArticleEntities.cs:
public class ArticleEntities : DbContext
{
public DbSet<Article> Articles { get; set; }
public DbSet<Category> Categories { get; set; }
}
An article can have many categories and a category can belong to many articles.
So far I can save/update/create all the article fields except the Categories.
I am representing them as a checkboxes in the view. I can get the values for the selected checkboxes into the controller, but, every attempt I have made to store them in the db with the article has failed.
How do I:
1) When saving an edited article, update the existing relations in the relation table without creating duplicates?
2) When saving a new article, create the chosen relations in the relation table?
For most relationships, this can be done by updating the appropriate foreign key fields. For many-to-many relationships, the Entity Framework doesn't expose the join table directly, so you must explicitly add and remove entities to and from the appropriate navigation properties. The following illustrations show the pages that you'll work with.
Pick one end of a many-to-many relationship to update. EF will sort out the other end for you. Make sure that the collection you will change is loaded, either by putting virtual on the property, using .Include () in the initial load or using .Load () later to get the collection.
In this tutorial you'll update related data. For most relationships, this can be done by updating either foreign key fields or navigation properties. For many-to-many relationships, the Entity Framework doesn't expose the join table directly, so you add and remove entities to and from the appropriate navigation properties.
The easiest approach to setting up relationships is by using EF Core’s conventions, which is what I have done. But if you want to explicitly define the relationships you can, using the .HasOne/.HasMany Fluent API commands, as shown below.
I assume that you get a list of CategoryId
s from the controller post action, a List<int>
or more general just an IEnumerable<int>
.
1) When saving an edited article, update the existing relations in the relation table without creating duplicates?
Article article; // from post action parameters
IEnumerable<int> categoryIds; // from post action parameters
using (var ctx = new MyDbContext())
{
// Load original article from DB including its current categories
var articleInDb = ctx.Articles.Include(a => a.Categories)
.Single(a => a.ArticleId == article.ArticleId);
// Update scalar properties of the article
ctx.Entry(articleInDb).CurrentValues.SetValues(article);
// Remove categories that are not in the id list anymore
foreach (var categoryInDb in articleInDb.Categories.ToList())
{
if (!categoryIds.Contains(categoryInDb.CategoryId))
articleInDb.Categories.Remove(categoryInDb);
}
// Add categories that are not in the DB list but in id list
foreach (var categoryId in categoryIds)
{
if (!articleInDb.Categories.Any(c => c.CategoryId == categoryId))
{
var category = new Category { CategoryId = categoryId };
ctx.Categories.Attach(category); // this avoids duplicate categories
articleInDb.Categories.Add(category);
}
}
ctx.SaveChanges();
}
Note that the code also works when you have a ArticleViewModel
instead of an Article
, given that the property names are the same (SetValues
takes an arbitrary object
).
2) When saving a new article, create the chosen relations in the relation table?
More or less the same idea as above but simpler because you don't need to compare with an original state in the database:
Article article; // from post action parameters
IEnumerable<int> categoryIds; // from post action parameters
using (var ctx = new MyDbContext())
{
foreach (var categoryId in categoryIds)
{
var category = new Category { CategoryId = categoryId };
ctx.Categories.Attach(category); // this avoids duplicate categories
article.Categories.Add(category);
// I assume here that article.Categories was empty before
}
ctx.Articles.Add(article);
ctx.SaveChanges();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With