justSteve
justSteve

Reputation: 5524

Integrating SimpleMembership with Entity Framework

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");
    //    });

}

}

Upvotes: 3

Views: 6438

Answers (3)

mark.monteiro
mark.monteiro

Reputation: 2941

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.

Upvotes: 1

Peder Rice
Peder Rice

Reputation: 1794

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.

Upvotes: 3

justSteve
justSteve

Reputation: 5524

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.

Upvotes: 0

Related Questions