3
votes

Can't seem to find an answer to this one, even though what I'm doing seems like it would be common and important to most developers. In most systems with user accounts, the user table is tied to other tables in the database. That's all I want. I'm using MSSQL Express 2012 and VS 2013.

I have a class library where I'm using the code-first approach to generate tables. I moved the IdentityModel class from the MVC project to this class library as well. Everything works separately - my tables are generated and work fine, and the Identity tables are generated when I register a new user.

However, now I need one of my entities/tables tied to the ApplicationUser in a 1-1 relationship, but adding the field like so prevents the Identity tables from being generated:

public class ApplicationUser : IdentityUser
{
    //***custom field
    public MyPortfolio Portfolio { get; set; }

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
}

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

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

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

        base.OnModelCreating(modelBuilder);

        //sql output log
        Database.Log = s => Debug.Write(s);
    }
}

..."MyPortfolio" is just plain entity:

    public class MyPortfolio
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }

        [StringLength(45, MinimumLength = 3)]
        public string Name { get; set; }

        public Boolean IsMaster { get; set; }

        //public ApplicationUser User { get; set; } //threw exception!
    }

I don't know too much about Identity, but have read that Migrations might be the answer. I'd rather avoid any further complexity if possible. Will that really be necessary? I am in early development and will be dropping/re-creating the tables many more times.

UPDATE 1:

OK, I added everything like adricadar described below. Here's what happened...

When adding migrations I had to select my class library from the "Default project" dropdown in the Package Manager Console. When doing Enable-Migrations, I got the following error:

More than one context type was found in the assembly 'MyProject.Data'. To enable migrations for 'MyProject.Models.ApplicationDbContext', use Enable-Migrations -ContextTypeName MyProject.Models.ApplicationDbContext. To enable migrations for 'MyProject.Data.MyDataModel', use Enable-Migrations -ContextTypeName MyProject.Data.MyDataModel.

...so I did the following:

Enable-Migrations -ContextTypeName MyProject.Models.ApplicationDbContext

...which as expected, created the Configuration class and an "InitialCreate" class for the AspNetUser* tables.

I then ran "Add-Migration UserPortofolioRelation", which generated the DbMigration class with Up() and Down(). Up and Down both define all of the tables I've defined in MyDataModel. I now see the relationship between MyPortfolio and AspNetUsers in Up():

        CreateTable(
                "dbo.MyPortfolio",
                c => new
                        {
                            Id = c.Int(nullable: false, identity: true),
                            Name = c.String(maxLength: 45),
                            IsMaster = c.Boolean(nullable: false),
                            UserId = c.String(nullable: false, maxLength: 128),
                        })
                .PrimaryKey(t => t.Id)
                .ForeignKey("dbo.AspNetUsers", t => t.UserId)
                .Index(t => t.UserId);

When I run Update-Database, I get the following error:

Applying explicit migrations: [201504141316068_UserPortofolioRelation]. Applying explicit migration: 201504141316068_UserPortofolioRelation. System.Data.SqlClient.SqlException (0x80131904): There is already an object named 'MyPortfolio' in the database.

The extent of my knowledge of Migrations is this basic tutorial:

https://msdn.microsoft.com/en-us/data/jj591621.aspx

This worked for me, and only the new fields were defined in the generated migrations code, not commands to drop and create all the tables.

UPDATE 2:

I followed this tutorial, which seemed to explain things a little more clearly when trying to work with migrations on multiple data contexts:

http://www.dotnet-tricks.com/Tutorial/entityframework/2VOa140214-Entity-Framework-6-Code-First-Migrations-with-Multiple-Data-Contexts.html

I ran this command:

Enable-Migrations -ContextTypeName MyProject.Models.ApplicationDbContext

The following Configuration was created:

internal sealed class Configuration : DbMigrationsConfiguration<MyProject.Models.ApplicationDbContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
    }

    protected override void Seed(MyProject.Models.ApplicationDbContext context)
    {
    }
}

...looking good. Then I ran this:

Add-Migration -Configuration MyProject.Data.Migrations.Configuration MigrationIdentity

...which generated this file:

namespace MyProject.Data.Migrations
{
    using System;
    using System.Data.Entity.Migrations;

    public partial class MigrationIdentity : DbMigration
    {
        public override void Up()
        {
            CreateTable(
                    "dbo.MyPortfolio",
                    c => new
                            {
                                Id = c.Int(nullable: false, identity: true),
                                Name = c.String(maxLength: 45),
                                IsMaster = c.Boolean(nullable: false),
                                UserId = c.String(nullable: false, maxLength: 128),
                            })
                    .PrimaryKey(t => t.Id)
                    .ForeignKey("dbo.AspNetUsers", t => t.UserId)
                    .Index(t => t.UserId);

            ...my other non-identity entities...

            CreateTable(
                    "dbo.AspNetUsers",
                    c => new
                            {
                                Id = c.String(nullable: false, maxLength: 128),
                                Email = c.String(maxLength: 256),
                                EmailConfirmed = c.Boolean(nullable: false),
                                PasswordHash = c.String(),
                                SecurityStamp = c.String(),
                                PhoneNumber = c.String(),
                                PhoneNumberConfirmed = c.Boolean(nullable: false),
                                TwoFactorEnabled = c.Boolean(nullable: false),
                                LockoutEndDateUtc = c.DateTime(),
                                LockoutEnabled = c.Boolean(nullable: false),
                                AccessFailedCount = c.Int(nullable: false),
                                UserName = c.String(nullable: false, maxLength: 256),
                            })
                    .PrimaryKey(t => t.Id)
                    .Index(t => t.UserName, unique: true, name: "UserNameIndex");

            ...other Identity entities/tables...

        }

        public override void Down()
        {
            ...everything you'd expect...
        }
    }
}

Awesome! All tables/entities in one file! So I ran it:

Update-Database -Configuration MyProject.Data.Migrations.Configuration -Verbose

...and bam! It generated all the tables with the UserId FK on the MyPortfolio table. All seems to be great with the world. Nothing can stop me now! Then I ran it and got this exception:

One or more validation errors were detected during model generation:

System.Data.Entity.ModelConfiguration.ModelValidationException

MyProject.Data.IdentityUserLogin: : EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType. MyProject.Data.IdentityUserRole: : EntityType 'IdentityUserRole' has no key defined. Define the key for this EntityType. IdentityUserLogins: EntityType: EntitySet 'IdentityUserLogins' is based on type 'IdentityUserLogin' that has no keys defined. IdentityUserRoles: EntityType: EntitySet 'IdentityUserRoles' is based on type 'IdentityUserRole' that has no keys defined.

A quick Google brought me back to Stack Exchange, naturally: EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType

The accepted answer outlines a whole slew of new possible angles to play, to try and get this to work right. This brings me back to my original question. Can I do this without migrations. Is that possible, in any way? With the level of complexity this comes with, I'm seriously debating "rolling my own" authentication, if not. I've already spent an exorbitant amount of time trying to simply tie a code-first entity to the Identity user. Each new door presents two more to go through. It just doesn't seem worth it...but maybe they'll clean this up a bit in the next release.

1
Have you tried to manually specify the relationship in OnModelCreating?adricadar
You said you pulled the ApplicatonUser out of the project; And given that the reference in MyPortfolio throws an exception, I'm thinking you have circular reference (which EF Identity is poor at n-tier anyways). But, being the case, have you added the Microsot.AspNet.Identity.EntityFramework package to both projects? Also, I assume ApplicationUser and MyPortfolio reside in the same project now?Brad Christie
@adricadar I haven't, like I said I'm pretty green with Identity. How would I do that for this scenario?Tsar Bomba
@BradChristie Yes, both projects have EF (done through NuGet.) And yes, both ApplicationUser and MyPortfolio are in the same class lib project. MyPortfolio is defined in a separate DbContext-derived class, where the rest of my model resides.Tsar Bomba
I undeleted, because i done what @ErikFunkenbusch said, take a look, it might help you :)adricadar

1 Answers

2
votes

You can specify the relationgship in OnModelCreating.

Try to use one DbContext per database. Usually you make different DbContexts for different databases, not for same.

Move all your entities in ApplicationDbContext and follow the instruction below.

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

    public DbSet<MyPortfolio> Portfolios { get; set; }
    // The rest of the entities
    // goes here

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

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

        modelBuilder.Entity<MyPortfolio>()
            .HasRequired(m => m.User  )
            .WithOptional(m => m.Portfolio )
            .Map(m => { m.MapKey("UserId"); });

        base.OnModelCreating(modelBuilder);

        //sql output log
        Database.Log = s => Debug.Write(s);
    }

}

Than you have to update your database, with migration, it's very easy. Open Package Manager Console in Visual Studio and enter this commands in order.

Enable-Migration
Add-Migration UserPortofolioRelation`
Update-Database