0

I would like to set up a many to many relationship in ASP.NET MVC4. The goal is to extend the default UserProfile class with a list of Timeline objects, which belong to the user. A Timeline could be shared by multiple users, so the Timeline class should have an list of UserProfile objects aswell.

Timeline class:

namespace MvcApplication1.Models
{
    public class Timeline
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Color { get; set; }
        public List<UserProfile> Users { get; set; }
    }

    public class TimelineContext : DbContext
    {
        public DbSet<Timeline> Timelines { get; set; }
        public DbSet<UserProfile> UserProfiles { get; set; }

        // Added the following because I saw it on:
        // http://www.codeproject.com/Tips/548945/Generating-Many-to-Many-Relation-in-MVC4-using-Ent
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Timeline>()
                .HasMany(c => c.Users)
                .WithMany(s => s.Timelines)
                .Map(mc =>
                {
                    mc.ToTable("TimelineOwners");
                    mc.MapLeftKey("TimelineId");
                    mc.MapRightKey("UserId");
                });
        }
    }
}

UserProfile class (default class with an added property):

public class UsersContext : DbContext
{
    public UsersContext()
        : base("DefaultConnection")
    {
    }

    public DbSet<UserProfile> UserProfiles { get; set; }
    public DbSet<Timeline> Timelines { get; set; }

    // Added the following because I saw it on:
    // http://www.codeproject.com/Tips/548945/Generating-Many-to-Many-Relation-in-MVC4-using-Ent
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<UserProfile>()
            .HasMany(c => c.Timelines)
            .WithMany(s => s.Users)
            .Map (mc =>
                {
                   mc.ToTable("TimelineOwners");
                   mc.MapLeftKey("UserId");
                   mc.MapRightKey("TimelineId");
               });
    }
}

[Table("UserProfile")]
public class UserProfile
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }
    public string UserName { get; set; }

    public List<Timeline> Timelines { get; set; }
}

I have a connection table with foreign keys:

enter image description here

enter image description here

enter image description here

When creating an instance of Timeline, the Users list is null:

Timeline timeline = db.Timelines.Find(id); // timeline.Users = null

Can somebody please enlighten me, how should I set this up working?

I'm totally new to ASP.NET MVC4.

Edit 1: I understand I should not extend UserProfile but create another class to store users. Once the many-to-many relationship works, I will refactor and go into that direction. But first I would like to know why is it not working.

Edit 2: The double context also caused problems, two databases were created for the two contexts and the pure join table was empty in one of them.

Adam Szabo
  • 11,302
  • 18
  • 64
  • 100

2 Answers2

2

I suggest that you work through this article about the options how you can load navigation properties with Entity Framework. This is very basic knowledge which is important for every kind of relationship, not only many-to-many relationships.

Looking at that article you will find then that this line...

Timeline timeline = db.Timelines.Find(id);

...does not load any related entities. So, it's expected that timeline.Users is null, even if the entities are related in the database.

If you want to load the Users you can use eager loading:

Timeline timeline = db.Timelines.Include(t => t.Users)
    .SingleOrDefault(t => t.Id == id);

This is a single database query. Or to enable lazy loading you have to mark your navigation properties as virtual:

public virtual List<UserProfile> Users { get; set; }

//...

public virtual List<Timeline> Timelines { get; set; }

You can then use your original code:

Timeline timeline = db.Timelines.Find(id); // first query
var users = timeline.Users;                // second query

This will run two separate queries. The second is performed as soon as you access the navigation property for the first time.

BTW: Is there a reason why you have two context classes - TimelineContext and UsersContext? "Normally" you need only one context.

Slauma
  • 175,098
  • 59
  • 401
  • 420
  • Thank you. The double context also caused problems, two databases were created for the two contexts and the pure join table was empty in one of them. After correcting these it works fine. – Adam Szabo Jun 30 '13 at 22:26
0

I'm not a fan of messing with the working of the internal userprofile. I would suggest creating your own user class, linking it to the simplemembershipprovider and adding functionality there. At max you'll extend the accountclasses a little to add more fields to register with, but that's about it.

Follow this extremely handy guide to get things working and let me know if you encounter an error.

Community
  • 1
  • 1
Jeroen Vannevel
  • 43,651
  • 22
  • 107
  • 170