mhesabi
mhesabi

Reputation: 1140

EF Code First - Populating data in Many to Many

I'm new to ASP.Net MVC and want to create a simple Blog project, therefore I have two entity posts and categories. each post can belong to many categories and each category can belong to many posts.

Models.cs

public class Category
{
    [Key]
    public int CategoryId { get; set; }
    public int? ParentId { get; set; }
    public virtual Category Parent { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public virtual ICollection<News> News { get; set; }

    public Category()
    {
        News = new List<News>();
    }
}
public class News
{
    [Key]
    public int NewsId { get; set; }
    public string Title { get; set; }
    public string Summary { get; set; }
    public string Content { get; set; }
    public string Source { get; set; }
    public string SourceURL { get; set; }
    public string Images { get; set; }
    public string Password { get; set; }

    public DateTime CreatedAt { get; set; }
    public DateTime? ModifiedAt { get; set; }
    public DateTime? DeletedAt { get; set; }
    public string CreatedBy { get; set; }
    public string DeletedBy { get; set; }
    public virtual PublishPeriod PublishPeriodId { get; set; }
    public virtual ICollection<Category> Categories { get; set; }

    public News()
    {
        Categories = new List<Category>();
    }

}

ModelsMap.cs

  public class CategoryMap:EntityTypeConfiguration<Category>
    {
        public CategoryMap()
        {
            Property(one => one.Title).HasMaxLength(100).IsRequired();
            HasOptional(x => x.Parent).WithMany().HasForeignKey(x => x.ParentId);
        }
    }

    public class NewsMap:EntityTypeConfiguration<News>
    {
        public NewsMap()
        {
            Property(x => x.CreatedBy).HasMaxLength(150);
            Property(x => x.DeletedBy).HasMaxLength(150);
            Property(x => x.Title).IsRequired().HasMaxLength(150);
            Property(x => x.Summary).IsRequired();
            Property(x => x.Content).IsRequired().HasColumnType("ntext");
            Property(x => x.CreatedAt).HasColumnType("datetime");
            Property(x => x.Password).IsOptional().HasMaxLength(128);
            Property(x => x.DeletedAt).IsOptional();
            Property(x => x.ModifiedAt).IsOptional();
            HasMany(x => x.Categories).WithMany(x => x.News).Map(x =>
            {
                x.ToTable("NewsCategories");
                x.MapLeftKey("News_NewsId");
                x.MapRightKey("Category_CategoryId");
            });
        }
    }

And DB Context

  public DbSet<Category> Categories { get; set; }
    public DbSet<News> News { get; set; }
    public DbSet<PublishPeriod> PublishPeriod { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Configurations.Add(new CategoryMap());
        modelBuilder.Configurations.Add(new NewsMap());
        modelBuilder.Configurations.Add(new PublishPeriodMap());

I have a create view for posts that displays categories in a list with checkboxs and each checkbox value is category's ID. How can I insert or update posts and keep relation between post and categories.

NewsController

//
// POST: /Admin/News/Create
[HttpPost]
public ActionResult Create(News news, List<string> Category)
{
    ViewBag.Categories = catRepository.All.OrderBy(x => x.Title);
    if (ModelState.IsValid)
    {
        foreach (var item in Category)
        {
            news.AddCategory(catRepository.Find(int.Parse(item)));
        }
        news.CreatedAt = DateTime.Now;
        news.CreatedBy = "M.Hesabi";
        newsRepository.InsertOrUpdate(news);
        newsRepository.Save();
        return RedirectToAction("Index");
    }
    else
    {
        return View();
    }
}

UPDATE: I created a method in News Model as @DanS said and edited my controller.

Upvotes: 0

Views: 190

Answers (1)

DanS
DanS

Reputation: 792

I'd recommend creating a method on the News class:

public void AddCategory(Category category) {
    Categories.Add(category);
    category.News.Add(this);
}

From your Controller you can then add each selected Category to the News instance, and then add the News to the DbContext prior to calling SaveChanges. This may depend, however, on how your repositories make use of the context -- in that if they open their own, instead of accessing a shared context, you might have to attach the categories to the News repository's context prior to saving. Hopefully this helps...

Update

IEntityChangeTracker error:

It appears as if MVCScaffolding uses a separate context for each repository. As mentioned, having separate contexts can lead to some additional required steps. As it stands now, your categories are tracked by Context A while your news is tracked by Context B-- You could detach/attach the category entities between the two contexts, but I'd say the recommended solution would be to change your repositories to accept a shared context through their constructors.

I'm assuming that you are instantiating the repositories in the controller's constructor, rather than using dependency injection, so you would modify your constructor code to do something like the following:

myContext = new YourContextClass();
catRepository = new CategoryRepository(myContext);
newsRepository = new NewsRepository(myContext);

You would then have to add the constructors to your repositories to assign the internal context property, and finally, adjust your controller to properly dispose of the context.

protected override void Dispose(bool disposing)
{
    if (disposing)
        myContext.Dispose();

    base.Dispose(disposing);
}

Upvotes: 1

Related Questions