Search code examples
asp.net-mvcasp.net-mvc-5asp.net-identity-2

Identity 2.0 Adding extra Email column


When I try to log in to my development site, I get a weird error:

Invalid column name 'Email1'.

I've made derived classes for a number of different ASP.NET Identity 2.0 classes, namely:

public class MyUser : IdentityUser<int, MyLogin, MyUserRole, MyClaim>//, IUser<int>
{
    #region properties

    public string ActivationToken { get; set; }

    public string PasswordAnswer { get; set; }

    public string PasswordQuestion { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string TaxID { get; set; }

    public bool IsActive { get; set; }

    public bool IsApproved { get; set; }

    #endregion

    #region methods

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<MyUser, int> userManager)
    {
        var userIdentity = await userManager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        return userIdentity;
    }

    #endregion
}

The offending code is in the call to UserManager.FindAsync() in the Login method of the AccountController:

public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            var user = await UserManager.FindAsync(model.Email, model.Password);
            if (user != null)
            {
                await SignInAsync(user, model.RememberMe);
                return RedirectToLocal(returnUrl);
            }
            ModelState.AddModelError("", "Invalid username or password.");
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

I've designed my User table as such:

CREATE TABLE [dbo].[User]
(
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [Password] [nvarchar](50) NOT NULL,
    [FirstName] [nvarchar](50) NULL,
    [LastName] [nvarchar](50) NULL,
    [Email] [nvarchar](50) NOT NULL,
    [TaxID] [nvarchar](50) NULL,
    [PaymentMethodID] [int] NULL,
    [IsActive] [bit] NOT NULL DEFAULT 1,
    [IsApproved] [bit] NOT NULL,
    [IsLocked] [bit] NOT NULL,
    [CreationDate] [datetime] NULL,
    [ApprovalDate] [datetime] NULL DEFAULT NULL,
    [LastLoginDate] [datetime] NULL,
    [PasswordQuestion] [nvarchar](max) NULL,
    [PasswordAnswer] [nvarchar](max) NULL,
    [ActivationToken] [nvarchar](200) NULL,
    [EmailConfirmed] [bit] NOT NULL,
    [SecurityStamp] [nvarchar](max) NULL,
    [PhoneNumber] [nvarchar](50) NULL,
    [PhoneNumberConfirmed] [bit] NOT NULL,
    [TwoFactorEnabled] [bit] NOT NULL,
    [LockoutEndDateUtc] [datetime2](7) NULL,
    [LockoutEnabled] [bit] NOT NULL,
    [AccessFailedCount] [int] NOT NULL,
    CONSTRAINT [User_PK] PRIMARY KEY NONCLUSTERED ([ID]),
    CONSTRAINT [Email_UK] UNIQUE NONCLUSTERED([Email])
)

And I'm binding my MyUser class to that table in the OnModelCreating method of MyDbContext:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            // Map Entities to their tables.
            modelBuilder.Entity<MyUser>().ToTable("User");
            modelBuilder.Entity<MyRole>().ToTable("Role");
            modelBuilder.Entity<MyClaim>().ToTable("UserClaim");
            modelBuilder.Entity<MyLogin>().ToTable("UserLogin");
            modelBuilder.Entity<MyUserRole>().ToTable("UserRole");
            // Set AutoIncrement-Properties
            modelBuilder.Entity<MyUser>().Property(r => r.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            modelBuilder.Entity<MyClaim>().Property(r => r.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            modelBuilder.Entity<MyRole>().Property(r => r.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            // Override some column mappings that do not match our default
            modelBuilder.Entity<MyUser>().Property(r => r.UserName).HasColumnName("Email");
            modelBuilder.Entity<MyUser>().Property(r => r.PasswordHash).HasColumnName("Password");
        }

Somewhere Identity 2.0 is creating another Email column that it shouldn't. Any thoughts?


Solution

  • There's already a column with name Email in the default schema.

    As @enki.dev suggest in his deleted answer if you look at the definition for IdentityUser you have those properties already defined.

    You're doing something quite dangerous here:

    modelBuilder.Entity<MyUser>().Property(r => r.UserName).HasColumnName("Email");
    modelBuilder.Entity<MyUser>().Property(r => r.PasswordHash).HasColumnName("Password");
    

    as you're trying to force one column into another. The property Email has been used extensively in this framework for password retrieval etc etc.

    You can try and rename it

    modelBuilder.Entity<MyUser>().Property(r => r.Email).HasColumnName("Blah");
    

    so you wouldn't have the duplicate; but I guess you're going to face many other problems on the way.

    Why don't you simply store the email in the UserName property ?

    Another suggestion; it's always better to let the framework create the tables for you using the migration so you always know what's happening.

    From Visual Studio choose menu

    • TOOLS - NuGet Packager Manager - Package Manager Console

    select your project in the combo: Default Project.

    Run Enable-Migrations.

    It should create a folder called Migrations with a file called Configuration.cs.

    The constructor should look like this:

    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
    }
    

    change the value AutomaticMigrationsEnabled to true.

    go back to Package Manager Console and run

    Update-Database
    

    or

    Update-Database -Force
    

    to recreate the whole schema for you tables.

    There's a protected method there where you can see your data:

    protected override void Seed(ASPNETIdentity2.Models.MyContext context)
    {
        //  This method will be called after migrating to the latest version.
    
        //  You can use the DbSet<T>.AddOrUpdate() helper extension method 
        //  to avoid creating duplicate seed data. E.g.
        //
        //    context.People.AddOrUpdate(
        //      p => p.FullName,
        //      new Person { FullName = "Andrew Peters" },
        //      new Person { FullName = "Brice Lambson" },
        //      new Person { FullName = "Rowan Miller" }
        //    );
        //
    }