3

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.

Community
  • 1
  • 1
Tsar Bomba
  • 1,047
  • 6
  • 29
  • 52
  • Have you tried to manually specify the relationship in `OnModelCreating`? – adricadar Apr 13 '15 at 20:37
  • 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 Apr 13 '15 at 20:42
  • @adricadar I haven't, like I said I'm pretty green with Identity. How would I do that for this scenario? – Tsar Bomba Apr 13 '15 at 20:43
  • @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 Apr 13 '15 at 20:45
  • @adricadar It looks like you had an answer up here but it disappeared. No good? – Tsar Bomba Apr 13 '15 at 21:22
  • Ok, Great.. You created a MyPortfolio class and added a property in ApplicationUser. But EF doesn't know anything about, so you have to add the DbSet, just like you would in Entity Framework, because IdentityDbContext is based on Entity Framework. However, it seems like really don't know what you're asking, because a 1:1 relationship in EF is not done this way... You can only do a 1:1 relationship by sharing the same primary key. You can create a 1:Many relationship that you treat as a 1:1, but you can't just add a single reference in both objects, it won't work. – Erik Funkenbusch Apr 13 '15 at 21:34
  • @ErikFunkenbusch It's pretty clear what I have doesn't work. I'd welcome any examples you might have, which would be very helpful. Particularly the part "you have to add the DbSet". Could you clarify, please? – Tsar Bomba Apr 13 '15 at 21:42
  • 1
    I undeleted, because i done what @ErikFunkenbusch said, take a look, it might help you :) – adricadar Apr 14 '15 at 06:23
  • You never mentioned anything about multiple DbContexts before, that changes everything and makes your solution much more complex. This is just something you really don't need to do. Just have a column in your regular DbContext that you store the UserId and then just run your queries against that, there is no need to link to the Identity tables if you want them in separate contexts. – Erik Funkenbusch Apr 15 '15 at 05:04
  • @ErikFunkenbusch Sorry, I thought I had made that clear. I had copied the IdentityModel.cs file into my own class library project where I had defined a model "MyDataModel", which I had made reference to a few times. – Tsar Bomba Apr 15 '15 at 10:20

1 Answers1

2

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
adricadar
  • 9,971
  • 5
  • 33
  • 46
  • seems I can't reply to you, it's broken or...something? I did what you described here and it caused a bunch of other complications and ultimately did not work. I'll describe it by updating my OP. However, I tried to back out by deleting the migrations folder and commenting out the relationships and code that was added to facilitate what you describe, and now when I run the app, the Seed method is never called! This always worked before. I'm still calling the same initializer in Application_Start(). I'm still calling Seed and doing DropCreateDatabaseAlways in the initializer. ??? – Tsar Bomba Apr 14 '15 at 14:41
  • Thanks. I had deleted all the Migrations stuff and all of the tables, it just wasn't running. Just did it again for a third time, now it's finally calling Seed again, mysteriously and without explanation. Now I'll re-run through what you've outlined for me and update my original post w/ what I find. – Tsar Bomba Apr 14 '15 at 15:06
  • OK, posted my findings. I've been reading up on code-first migrations and doing tutorials, while attempting your solution, and not getting the same outcome from my code - probably due to using more than one context (one for Identity and another for my own entities)? Please let me know your thoughts. Huge learning curve on this! – Tsar Bomba Apr 14 '15 at 15:58
  • @TsarBomba I like your enthusiasm and desire to learn. To solve the last problem remove the lines with `CreateTable( "dbo.MyPortfolio",..` – adricadar Apr 14 '15 at 16:04
  • Thanks...don't want to be one of those quick-fix posters - I actually want to master this. Not sure I follow you though. If I do that, the Up method will be empty. It created 7 CreateTable calls in Up, and nothing else. Shouldn't I have expected some AddColumn and DropColumn calls in this class, instead? – Tsar Bomba Apr 14 '15 at 16:16
  • @TsarBomba To master this you have to do it `clean` from the start, that means if you deleted your DB and did migration, all should work with no problem :). The problem you face is that `MyPortofolio` appears in 2 DbContext, you have 2 migrations that creates `MyPortofolio`, to solve this you have to delete 1 create, it's not a quick fix, it's okay to do it :). Migration don't resolve this type of problems, if it did, now you didn't see any `Up` or `Down` method. But you see it, you can manipulate it. Why? To solve your problems. – adricadar Apr 14 '15 at 17:02
  • Alright, so I will need to use Migration from now on, instead of my DbInitializer? I've been using "public class MyDbInitializer : DropCreateDatabaseIfModelChanges" and calling it in Global.asax.cs using "Database.SetInitializer(new MyDataModel.MyDbInitializer());" I guess I'm confused as to whether these two approaches can or cannot be used together. Would this be the proper method, once using Migrations? http://stackoverflow.com/questions/10848746/using-entity-framework-code-first-migrations-in-production Thanks again, appreciate the help! – Tsar Bomba Apr 14 '15 at 19:26
  • @TsarBomba that is for production, now you have to check something and migration is done automaticaly on deploy. These 3 comands are what you need to know :) and the guide you followed :). – adricadar Apr 14 '15 at 19:44
  • I've added "Update 2" to the original post. Closest I've gotten so far, but really no closer.Thoughts? – Tsar Bomba Apr 15 '15 at 03:27
  • @TsarBomba a quick question, do you really need 2x DbContext? Usually is done 1xDbContext per database. – adricadar Apr 15 '15 at 07:04
  • No! Not at all. Could I get away with putting my entire model in my IdentityDbContext? I'll try that. – Tsar Bomba Apr 15 '15 at 10:14
  • @TsarBomba Yes, it will be much easier. Keep me updated with your progress, i'm curious to know how you doing. – adricadar Apr 15 '15 at 12:32
  • Worked like a charm! I merged everything from my second context, including the model and initializer, into the Identity context, and it all works just fine. Thanks so much for your help, and ErikFunkenbusch as well. Make sure to post that as an answer so I can give you credit. – Tsar Bomba Apr 15 '15 at 19:02
  • @TsarBomba Very happy to hear this, i updated my answer with the comment i mentioned. – adricadar Apr 15 '15 at 20:28