What binds ApplicationUser to the aspnetusers table in Database First?
Asked Answered
A

1

10

I have a database-first EDMX model in a separate library (e.g. Common.Feedback.Data), that includes the AspNetUser table and its related Identity Framework tables (pulled from another existing, working, database/application).

I have updated the ApplicationDbContext connection string to point to the new model and new database connection:

using System.Data.Entity;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;

namespace Feedback.MvcApplication.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.

    public class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("FeedbackEntities", throwIfV1Schema: false)
        {
        }

        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
    }
}

The connection string in the web.config includes the full path to the assembly and that reference is fine:

<add name="FeedbackEntities" 
     connectionString="metadata=res://Common.Feedback.Data/FeedbackModel.csdl|res://Common.Feedback.Data/FeedbackModel.ssdl|res://Common.Feedback.Data/FeedbackModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=mydatabase.database.windows.net;initial catalog=Feedback;persist security info=True;user id=LeaveFeedbackuser@mydatabase;password=mypassword;MultipleActiveResultSets=True;App=EntityFramework&quot;" 
     providerName="System.Data.EntityClient" />

At runtime, any access to login results in the following error:

"The entity type ApplicationUser is not part of the model for the current context"

All links I have tried, relating to that error, usually involve ways of updating the migration.

I cannot enable migrations as it is an EDMX first setup, but I assume there is a binding between the ApplicationUser object and the aspnetusers table "somewhere" behind the scenes.

Does anyone here have a clear understanding of how the ApplicationUser class is mapped to/from the aspnetusers table at runtime and explain how to get my code to work with the database?

Repro steps

  • Create a new MVC Web Application using VS 2013
  • Update all NuGet packages to latest
  • Take an existing SQL database with the Identity Framework tables and copy it to a new table (strip out any unrelated tables).
  • Add new tables for the project
  • Create a class library to hold the data models (e.g. Common.Feedback.Data)
  • Add an Edmx data model, to the library, based on the database created previously
  • Change the connection string to fully qualify the assembly (not res://*/)
  • Change the connection string name in IdentityModel.cs to match the connection string name in the config.
  • Copy the connection string from the library's app.config to the web project's web.config
  • Try login and you will hit the error mentioned

Update:

Based on a stray post, I changed my connection string to match the normal SQL connection the Identity Framework is configured to use by default (and using a Sql client):

<add name="FeedbackSql" 
     connectionString="data source=mydatabase.database.windows.net;initial catalog=Feedback;persist security info=True;user id=LeaveFeedbackuser@mydatabase;password=mypassword;MultipleActiveResultSets=True;App=EntityFramework" 
     providerName="System.Data.SqlClient" />

and changed to setup to use this new connection:

   public ApplicationDbContext()
        : base("FeedbackSql", throwIfV1Schema: false)
    {
    }

Bizarrely the error changes to:

The magic number in GZip header is not correct. Make sure you are passing in a GZip stream.

I think the initial change to a SqlClient provider is correct, so this new error may related to using an Azure database over that connection. I am open to suggestions on what to try next.

Updated web.config based on suggestion by @rism and this link (but the GZip error persists):

  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
      <parameters>
        <!--<parameter value="mssqllocaldb" />-->
        <parameter value="data source=mydatabase.database.windows.net;initial catalog=Feedback;persist security info=True;user id=myuserid;password=mypassword;MultipleActiveResultSets=True;App=EntityFramework" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>

Again based on tips from @rism I also tried this version (but the GZip error persists):

  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="v12.0" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>

New update:

I created a brand new Web application with the standard user-security option. I also created an empty database in Azure.

I did nothing but change the default connection string to this:

  <connectionStrings>
    <add name="DefaultConnection" 
         connectionString="data source=mydatabase.database.windows.net;initial catalog=Feedback;persist security info=True;user id=LeaveFeedbackuser;password=mypassword;MultipleActiveResultSets=True;App=EntityFramework"
         providerName="System.Data.SqlClient" />
  </connectionStrings>

and the default connection factory to this:

  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="v12.0" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>

On attempting to login I get the following error:

The magic number in GZip header is not correct. Make sure you are passing in a GZip stream. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.IO.InvalidDataException: The magic number in GZip header is not correct. Make sure you are passing in a GZip stream.

Source Error:

Line 153: { Line 154: var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; Line 155: var result = await UserManager.CreateAsync(user, model.Password); Line 156: if (result.Succeeded) Line 157: {

Aleasealeatory answered 27/1, 2015 at 22:21 Comment(11)
If you're going the route of a DB-first solution theres no reason to stick with the ApplicationDbContext. You could just remove all that junk, have your own DbContext and save yourself a headacheSubdebutante
You've only shown the constructor for ApplicationDbContext() can you show the whole class. Also which version of Identity are you using? Version 1.0 is quite different from Version 2. Mappings are made between classes and tables via subclasses of EntityTypeConfiguration in which you set up aspects of the model like mapping referential integrity, cardinality etc. If you were using Fluent Api in an TspIdentityMap : EntityTypeConfiguration<TspIdentity> you would make a statement like this.ToTable("AspNetUsers"); i.e. you map the code class entity TspIdentity to the db table.Doralin
The reason I want to see your whole ApplicationDbContext is that the message reads to me like you haven't got an DbSet<ApplicationUser> on your context tho I normally that would generate a compiler error... but then you've mixed and matched bits so lets see all the same. Also check you are using the same version of Identity in all projects.Doralin
@rism: I should have said, everything is "out of the box" Visual Studio 2013 scaffolding + MVC5 Razor with latest versions of "everything". This is currently just a bare-bones project until I resolve this issue.Aleasealeatory
In the web.config of a default project <entityFramework> <defaultConnectionFactory will be set to use LocalDb have you updated those to use SqlServer? And can you show us the stack trace from the YSOD?Doralin
@rism: You were correct that it was set to be a local db for the defaultConnectionFactory. I changed it to match the desired SQL connection type but I still get the GZip error mentioned in the updateAleasealeatory
@TrueBlueAussie Typically you'd keep your connection string as is and update that section as follows: <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework"> <parameters> <parameter value="v12.0" /> </parameters> </defaultConnectionFactory> <providers> <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> </providers> </entityFramework>Doralin
@rism: Thanks for the simplification. Still getting the GZip error message. I was thinking that: as a specific connection name is supplied in the code, I doubt the DefaultConnectionFactory is used at all.Aleasealeatory
@TrueBlueAussie No it wouldn't be used in a working app with a supplied connection string, in fact it's really only for code first but since you've explictly mentioned ApplicationDbContext() which is a code first generated context (from AspIdentity nuget package) I wanted to if this made any difference but it seems not. What happens if you delete ApplicationDbContext and then clean up the compile errors by pointing all references to ApplicationDbContext to whatever DbContext was generated by the EDMX. Basically to me it seems you have one foot in code first and the other in DbFirst.Doralin
I'm sure if you downloaded ExpressProfiler from CodePlex you'd see there is no request going to the db at all so mapping wont be the issue at all in terms of code class name vs db table name which is simply using the convention of same name in this case. i.e. no explicit mapping is occurring.Doralin
@rism: I have created a new question for the connection problem as I have separated it from the original problem with a new repro. #28227520 Please continue on that one as I found an answer for the original title problem (below).Aleasealeatory
A
14

The original question was about how does OWIN map the ApplicationUser class to the AspNetUsers table.

I decompiled the Identity Framework dlls with DotPeek and found that the table name "AspNetUsers" is simply hard-wired into the code of the OnModelCreating method. e.g.

 protected virtual void OnModelCreating(DbModelBuilder modelBuilder)
    {
      if (modelBuilder == null)
        throw new ArgumentNullException("modelBuilder");
        EntityTypeConfiguration<TUser> typeConfiguration1 = ((EntityTypeConfiguration<TUser>) modelBuilder.Entity<TUser>())
        .ToTable("AspNetUsers");

That code uses reflection to generate a list of field names from the properties in ApplicationUser. It checks for the existence of all those field names using a simple SQL query (of the system table containing all field names) and confirms they are all present.

The rest is simple mapping of names/values using constructed SQL queries.

As for the other part of the problem, the database connection, I have created a new question as it is a) unrelated to the title of this one and b) driving me crazy! The new question is Why am I getting "The magic number in GZip header is not correct." error using OWIN auth against Azure SQL

Aleasealeatory answered 30/1, 2015 at 0:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.