Sankara
Sankara

Reputation: 1479

AspNetIdentity framework Code first Approach

I am new to Code first Approach on EF6. So please excuse

I am trying to implement AspNetIdentityframework for my local project.

I have all the tables which comes out of the box in the database.I kept as is same with the model class structure.I have no roles just 1 role.

Here is my DbContext class for this.

 public class AuthDbContext : IdentityDbContext<IdentityUser>
    {
        public AuthDbContext()
            : base("AuthDbContext")
        {
            Database.SetInitializer<AuthDbContext>(null);
        }
        public virtual DbSet<AspNetRole> AspNetRoles { get; set; }
        public virtual DbSet<AspNetUserClaim> AspNetUserClaims { get; set; }
        public virtual DbSet<AspNetUserLogin> AspNetUserLogins { get; set; }
        public virtual DbSet<AspNetUser> AspNetUsers { get; set; }


        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(t => t.UserId);
            modelBuilder.Entity<IdentityRole>().HasKey<string>(t => t.Id);
            modelBuilder.Entity<IdentityUser>().HasKey<string>(t => t.Id);
            modelBuilder.Entity<IdentityUserRole>().HasKey(t => new { t.RoleId,t.UserId});

            modelBuilder.Ignore<IdentityUserRole>();
            modelBuilder.Ignore<AspNetRole>();
}

I have 2 users in the table. I am trying to find one . I am getting error Invalid Column Name 'Discriminator' What is that I am doing wrong in generating the model ? I tried lot of options I could not solve this one

Here is the sql generated for userManager.FindByName Method

 exec sp_executesql N'SELECT 
        [Limit1].[C1] AS [C1], 
        [Limit1].[Id] AS [Id], 
        [Limit1].[Email] AS [Email], 
        [Limit1].[EmailConfirmed] AS [EmailConfirmed], 
        [Limit1].[PasswordHash] AS [PasswordHash], 
        [Limit1].[SecurityStamp] AS [SecurityStamp], 
        [Limit1].[PhoneNumber] AS [PhoneNumber], 
        [Limit1].[PhoneNumberConfirmed] AS [PhoneNumberConfirmed], 
        [Limit1].[TwoFactorEnabled] AS [TwoFactorEnabled], 
        [Limit1].[LockoutEndDateUtc] AS [LockoutEndDateUtc], 
        [Limit1].[LockoutEnabled] AS [LockoutEnabled], 
        [Limit1].[AccessFailedCount] AS [AccessFailedCount], 
        [Limit1].[UserName] AS [UserName]
        FROM ( SELECT TOP (1) 
            [Extent1].[Id] AS [Id], 
            [Extent1].[Email] AS [Email], 
            [Extent1].[EmailConfirmed] AS [EmailConfirmed], 
            [Extent1].[PasswordHash] AS [PasswordHash], 
            [Extent1].[SecurityStamp] AS [SecurityStamp], 
            [Extent1].[PhoneNumber] AS [PhoneNumber], 
            [Extent1].[PhoneNumberConfirmed] AS [PhoneNumberConfirmed], 
            [Extent1].[TwoFactorEnabled] AS [TwoFactorEnabled], 
            [Extent1].[LockoutEndDateUtc] AS [LockoutEndDateUtc], 
            [Extent1].[LockoutEnabled] AS [LockoutEnabled], 
            [Extent1].[AccessFailedCount] AS [AccessFailedCount], 
            [Extent1].[UserName] AS [UserName], 
            ''0X0X'' AS [C1]
            FROM [dbo].[AspNetUsers] AS [Extent1]
 WHERE ([Extent1].[Discriminator] = N''User'') //Error here ?????????
AND (((UPPER([Extent1].[UserName])) = (UPPER(@p__linq__0))) OR ((UPPER([Extent1].[UserName]) IS NULL) AND (UPPER(@p__linq__0) IS NULL)))
        )  AS [Limit1]',N'@p__linq__0 nvarchar(4000)',@p__linq__0=N'testUser'

Upvotes: 0

Views: 675

Answers (1)

Maximo Dominguez
Maximo Dominguez

Reputation: 2085

As you can see here in the sources of Asp.NET Identity 2.0, IdentityDbContext<T> already defines the DbModelBuilder configuration in OnModelCreating and the DbSet<AspNetRole>, DbSet<AspNetUserClaim>, DbSet<AspNetUserLogin>, DbSet<AspNetUser>, so you don't have to define all those properties, nor override the OnModelCreating.

Your Context should look something like this:

public class AuthDbContext : IdentityDbContext<IdentityUser>
{
    public AuthDbContext() : base("AuthDbContext")
    {
        Database.SetInitializer<AuthDbContext>(null);
    }
}

You are defining a DbSet<AspNetUser> and your Context inherits from Identity<IdentityUser> so there are 2 DbSets, one where T is IdentityUser and another where T is AspNetUser. IdentityUser inherits from AspNetUser, so EntityFramework manages inheritance by adding a column called Discriminator.

Edit:

Here is the implementation of the class you're inheriting from.

/// <summary>
/// DbContext which uses a custom user entity with a string primary key
/// </summary>
/// <typeparam name="TUser"></typeparam>
public class IdentityDbContext<TUser> 
        : IdentityDbContext<
            TUser, 
            IdentityRole, 
            string, 
            IdentityUserLogin,
            IdentityUserRole,
            IdentityUserClaim> 
        where TUser : IdentityUser 
{
    /// <summary>
    /// Default constructor which uses the DefaultConnection
    /// </summary>
    public IdentityDbContext()
        : this("DefaultConnection") 
    { }

    /// <summary>
    /// Constructor which takes the connection string to use
    /// </summary>
    /// <param name="nameOrConnectionString"></param>
    public IdentityDbContext(string nameOrConnectionString)
        : base(nameOrConnectionString) 
    { }
}

As you can see, the class IdentityDbContext<TUser> inherits from IdentityDbContext<TUser, TRole, TKey, TUserLogin, TUserRole, TUserClaim>. The latter already defines properties of type IDbSet<TRole> and IDbSet<TUser>, and already has an implementation of OnModelCreating that in my opinion satisfies your needs.

/// <summary>
/// Maps table names, and sets up relationships between the various user entities
/// </summary>
/// <param name="modelBuilder"></param>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    if (modelBuilder == null)
    {
        throw new ArgumentNullException("modelBuilder");
    }

    // Needed to ensure subclasses share the same table
    var user = modelBuilder.Entity<TUser>().ToTable("AspNetUsers");
    user.HasMany(u => u.Roles).WithRequired().HasForeignKey(ur => ur.UserId);
    user.HasMany(u => u.Claims).WithRequired().HasForeignKey(uc => uc.UserId);
    user.HasMany(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId);
    user.Property(u => u.UserName).IsRequired();

    // CONSIDER: u.Email is Required if set on options?

    // Look into IValidatable...

    // Try this (Configure Join table name HasMany.WithMany.Map(To match 1.0 tables)
    modelBuilder.Entity<TUserRole>()
        .HasKey(r => new { r.UserId, r.RoleId })
        .ToTable("AspNetUserRoles");

    modelBuilder.Entity<TUserLogin>()
        .HasKey(l => new { l.LoginProvider, l.ProviderKey, l.UserId })
        .ToTable("AspNetUserLogins");

    modelBuilder.Entity<TUserClaim>()
        .ToTable("AspNetUserClaims");

    var role = modelBuilder.Entity<TRole>().ToTable("AspNetRoles");
    role.Property(r => r.Name).IsRequired();
    role.HasMany(r => r.Users).WithRequired().HasForeignKey(ur => ur.RoleId);
}

If you want to use your own AspNetRole, AspNetUserClaim, and AspNetUserLogin, you should inherit from IdentityDbContext<TUser, TRole, TKey, TUserLogin, TUserRole, TUserClaim> and pass your custom entities, or if you want to really have control of your database you should simply inherit from DbContext and do all the setup yourself (thing you're already doing).

I hope I was clear, if you have any doubt don't hesitate in posting a comment.

Upvotes: 2

Related Questions