3

I'm trying to integrate the SimpleMembership tables with the rest of my Object Model - to manage all the entities from a single database and context.

Up to now the best recipe I've found for manually spinning up the SM tables (the entry point to combine SimpleMember with the rest of my Object Model) is found here. But, as cited in the comments section there are a couple mistakes in the code sample provided. The comments attempt to provide corrections but, due to formatted, really hard to follow.

I'm 80% the way there but getting stuck with the Foreign Key generation for the Membership table. Does the code within OnModelCreating block belong in the MyDbContext class? I'm getting a compile error on the .WithMany(u => u.Members) line.

Membership.cs

[Table("webpages_Membership")]
public class Membership
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int UserId { get; set; }
    public DateTime? CreateDate { get; set; }

    [StringLength(128)]
    public string ConfirmationToken { get; set; }
    public bool? IsConfirmed { get; set; }
    public DateTime? LastPasswordFailureDate { get; set; }
    public int PasswordFailuresSinceLastSuccess { get; set; }

    [Required, StringLength(128)]
    public string Password { get; set; }
    public DateTime? PasswordChangedDate { get; set; }

    [Required, StringLength(128)]
    public string PasswordSalt { get; set; }

    [StringLength(128)]
    public string PasswordVerificationToken { get; set; }
    public DateTime? PasswordVerificationTokenExpirationDate { get; set; }

    <strike>public virtual ICollection<Role> Roles { get; set; }</strike>

EDIT: Originally I added the line above to remove a compiler complaint in the extraneous code block below. Removing this attempt to create a FK to Roles will align the rest of this code so that these model classes create a Migration that generates tables for SM.

OAuthMembership.cs

[Table("webpages_OAuthMembership")]
public class OAuthMembership
{
    [Key, Column(Order = 0), StringLength(30)]
    public string Provider { get; set; }
    [Key, Column(Order = 1), StringLength(100)]
    public string ProviderUserId { get; set; }
    public int UserId { get; set; }
}

Role.cs

[Table("webpages_Roles")]
public class Role
{
    [Key]
    public int RoleId { get; set; }
    [StringLength(256)]
    public string RoleName { get; set; }

    public virtual ICollection<UserProfile> UserProfiles { get; set; }
}

UserProfile.cs

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

    public virtual ICollection<Role> Roles { get; set; }
}

MyDbContext.cs

public MyDbContext() : base("DefaultConnection") { }

public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Membership> Membership { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<OAuthMembership> OAuthMembership { get; set; }


protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<UserProfile>()
                .HasMany<Role>(r => r.Roles)
                .WithMany(u => u.UserProfiles)
                .Map(m =>
                         {
                             m.ToTable("webpages_UsersInRoles");
                             m.MapLeftKey("UserId");
                             m.MapRightKey("RoleId");
                         });

EDIT: The block below was included in one of the article's comments but seems not to be needed.

    //modelBuilder.Entity<Membership>()
    //    .HasMany<Role>(r => r.Roles)
    //    .WithMany(u => u.Members)
    //    .Map(m =>
    //    {
    //        m.ToTable("webpages_UsersInRoles");
    //        m.MapLeftKey("UserId");
    //        m.MapRightKey("RoleId");
    //    });

}

}

justSteve
  • 5,444
  • 19
  • 72
  • 137
  • You have changed a lot of things from `SimpleMembership` models, is that on purpose? For example the relationship between `Roles and UserProfile` should be through `Membership` to my understanding – Komengem Mar 20 '13 at 16:26
  • I'm pretty much relying on the code that came from the comments included in the linked article - I'd added the FK that's now struck out. When I compare the result of these models against the db diagram that's generated by a fresh project things match up. Are you seeing something different? thx much – justSteve Mar 20 '13 at 19:49
  • With that article i would go with code-first, if you compare your generated POCO and whats in that article you will see that they differ. do POCO class first then use Migration to update your database. – Komengem Mar 20 '13 at 19:56
  • The commented block of code is the one needed rather than the one you used. Map roles to members not UserProfile – Komengem Mar 20 '13 at 20:21

3 Answers3

3

I followed the instructions in the article, and I also took into account the the comments that suggested the article was wrong in a few ways.

I ended up with the following classes:

UserProfile.cs

[Table("UserProfile")]
public class UserProfile
{
    [Key, ForeignKey("Membership")]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }
    public string UserName { get; set; }
    public ICollection<WebSecurity.Role> Roles { get; set; }
    public WebSecurity.Membership Membership { get; set; }
}

You should notice right away the "ForeignKey" attribute I use on the UserId column. Since the user is first created in the Membership table, my UserProfile table is the dependent table.

Membership.cs

[Table("webpages_Membership")]
public class Membership
{
    //public Membership()
    //{
    //  Roles = new List<Role>();
    //  OAuthMemberships = new List<OAuthMembership>();
    //}

    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int UserId { get; set; }
    public DateTime? CreateDate { get; set; }
    [StringLength(128)]
    public string ConfirmationToken { get; set; }
    public bool? IsConfirmed { get; set; }
    public DateTime? LastPasswordFailureDate { get; set; }
    public int PasswordFailuresSinceLastSuccess { get; set; }
    [Required, StringLength(128)]
    public string Password { get; set; }
    public DateTime? PasswordChangedDate { get; set; }
    [Required, StringLength(128)]
    public string PasswordSalt { get; set; }
    [StringLength(128)]
    public string PasswordVerificationToken { get; set; }
    public DateTime? PasswordVerificationTokenExpirationDate { get; set; }


    public UserProfile UserProfile { get; set; }
}

Per Richard's comments in the article, I commented out the constructor. I also created a reference back to the UserProfile, but not to roles.

OAuthMembership.cs

[Table("webpages_OAuthMembership")]
public class OAuthMembership
{
    [Key, Column(Order = 0), StringLength(30)]
    public string Provider { get; set; }

    [Key, Column(Order = 1), StringLength(100)]
    public string ProviderUserId { get; set; }

    public int UserId { get; set; }

    //[Column("UserId"), InverseProperty("OAuthMemberships")]
    //public Membership User { get; set; }
}

My OAuthMembership class remained basically the same; I commented out only the User attribute, per Richard's comment in the article.

AccountModel.cs+UsersContext

Finally, the UserContext class, where I create the association for the UsersInRoles table.

public class UsersContext : DbContext

{

    public UsersContext()
        : base("DefaultConnection")
    {

    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<InternetApplication.Models.WebSecurity.Role>()
            .HasMany<InternetApplication.Models.UserProfile>(r => r.UserProfiles)
            .WithMany(u => u.Roles)
            .Map(m =>
            {
                m.ToTable("webpages_UsersInRoles");
                m.MapLeftKey("UserId");
                m.MapRightKey("RoleId");
            });
    }

    public DbSet<WebSecurity.Membership> Membership { get; set; }
    public DbSet<WebSecurity.OAuthMembership> OAuthMembership { get; set; }
    public DbSet<WebSecurity.Role> Roles { get; set; }
    public DbSet<UserProfile> UserProfiles { get; set; }
}

In addition to adding the UsersInRoles mapping, I added DbSet entries for each table.

Now that everything has been created, I can use my Add-Migration and Update-Database commands and use the following code snippet that combines the Membership, UserProfile, and Roles tables:

using (var db = new UsersContext())
{
    var memberships = db.Membership
        .Include("UserProfile")
        .Include("UserProfile.Roles")
        .ToList();
    foreach (var member in memberships)
    {
        member.IsConfirmed = true;
    }

    db.SaveChanges();
}

This was a long post, but I hope that helps.

Peder Rice
  • 1,764
  • 3
  • 28
  • 51
1

I used the answer to this question to automatically generate the models from the existing 'webpage_' tables in my database. This ensures that the models are created in the exact same way that SimpleMembership creates them. This resulted in the following code:

Models:

public partial class webpages_Membership
{
   public int UserId { get; set; }
   public Nullable<System.DateTime> CreateDate { get; set; }
   public string ConfirmationToken { get; set; }
   public Nullable<bool> IsConfirmed { get; set; }
   public Nullable<System.DateTime> LastPasswordFailureDate { get; set; }
   public int PasswordFailuresSinceLastSuccess { get; set; }
   public string Password { get; set; }
   public Nullable<System.DateTime> PasswordChangedDate { get; set; }
   public string PasswordSalt { get; set; }
   public string PasswordVerificationToken { get; set; }
   public Nullable<System.DateTime> PasswordVerificationTokenExpirationDate { get; set; }
}

public partial class webpages_Roles
{
   public webpages_Roles()
   {
      this.webpages_UsersInRoles = new HashSet<webpages_UsersInRoles>();
   }

   public int RoleId { get; set; }
   public string RoleName { get; set; }

   public virtual ICollection<webpages_UsersInRoles> webpages_UsersInRoles { get; set; }
}

public partial class webpages_UsersInRoles
{
   public int UserId { get; set; }
   public int RoleId { get; set; }

   public virtual webpages_Roles webpages_Roles { get; set; }
}

Fluent Mappings:

internal partial class MembershipMapping : EntityTypeConfiguration<webpages_Membership>
{
   public MembershipMapping()
   {
      this.HasKey(t => t.UserId);
      this.ToTable("webpages_Membership");
      this.Property(t => t.UserId).HasColumnName("UserId").HasDatabaseGeneratedOption(new Nullable<DatabaseGeneratedOption>(DatabaseGeneratedOption.None));
      this.Property(t => t.CreateDate).HasColumnName("CreateDate");
      this.Property(t => t.ConfirmationToken).HasColumnName("ConfirmationToken").HasMaxLength(128);
      this.Property(t => t.IsConfirmed).HasColumnName("IsConfirmed");
      this.Property(t => t.LastPasswordFailureDate).HasColumnName("LastPasswordFailureDate");
      this.Property(t => t.PasswordFailuresSinceLastSuccess).HasColumnName("PasswordFailuresSinceLastSuccess");
      this.Property(t => t.Password).HasColumnName("Password").IsRequired().HasMaxLength(128);
      this.Property(t => t.PasswordChangedDate).HasColumnName("PasswordChangedDate");
      this.Property(t => t.PasswordSalt).HasColumnName("PasswordSalt").IsRequired().HasMaxLength(128);
      this.Property(t => t.PasswordVerificationToken).HasColumnName("PasswordVerificationToken").HasMaxLength(128);
      this.Property(t => t.PasswordVerificationTokenExpirationDate).HasColumnName("PasswordVerificationTokenExpirationDate");
   }
}

internal partial class RolesMapping : EntityTypeConfiguration<webpages_Roles>
{
   public RolesMapping()
   {
      this.HasKey(t => t.RoleId);
      this.ToTable("webpages_Roles");
      this.Property(t => t.RoleId).HasColumnName("RoleId");
      this.Property(t => t.RoleName).HasColumnName("RoleName").IsRequired().HasMaxLength(256);
   }
}

internal partial class UsersInRolesMapping : EntityTypeConfiguration<webpages_UsersInRoles>
{
   public UsersInRolesMapping()
   {
      this.HasKey(t => new { t.UserId, t.RoleId });
      this.ToTable("webpages_UsersInRoles");
      this.Property(t => t.UserId).HasColumnName("UserId").HasDatabaseGeneratedOption(new Nullable<DatabaseGeneratedOption>(DatabaseGeneratedOption.None));
      this.Property(t => t.RoleId).HasColumnName("RoleId").HasDatabaseGeneratedOption(new Nullable<DatabaseGeneratedOption>(DatabaseGeneratedOption.None));
      this.HasRequired(t => t.webpages_Roles).WithMany(t => t.webpages_UsersInRoles).HasForeignKey(d => d.RoleId);
   }
}

Database Context:

public class MembershipContext : DbContext, IDisposable
{
   public DbSet<webpages_Membership> Membership { get; set; }
   public DbSet<webpages_Roles> Roles { get; set; }
   public DbSet<webpages_UsersInRoles> UsersInRoles { get; set; }

   protected override void OnModelCreating(DbModelBuilder modelBuilder)
   {
      modelBuilder.Configurations.Add(new MembershipMapping());
      modelBuilder.Configurations.Add(new RolesMapping());
      modelBuilder.Configurations.Add(new UsersInRolesMapping());

      base.OnModelCreating(modelBuilder);
   }
}

Note that I have excluded the OAuthMembership table, because I didn't need it for my solution. But if you follow the steps in the link I provided above you can easily include that table as well.

Community
  • 1
  • 1
mark.monteiro
  • 2,609
  • 2
  • 33
  • 38
0

Starting from a blank MVC4 Internet Template I ran the project so as to create the SimpleMembership tables in a fresh db - then used EF's Reverse Engineer tool to create POCOs from those tables. Stepped thru it line by line to find the error and edited the code block in the OP.

With that code in place I used Package Manager to 'Add-Migration' and 'Update-Database'. Initial tests confirm everything works - I suppose I'll have to revisit if I find edge-cases that expose any deeper problems.

justSteve
  • 5,444
  • 19
  • 72
  • 137
  • With that article i would go with code-first, if you compare your generated POCO and whats in that article you will see that they differ. do POCO class first then use Migration to update your database. – Komengem Mar 20 '13 at 19:56
  • 1
    I'm using code-first in the live project I only used the reverse engineer against a scratch project to see how the framework wired things up. – justSteve Mar 21 '13 at 00:36