brockallen / BrockAllen.MembershipReboot

MembershipReboot is a user identity management and authentication library.
Other
742 stars 238 forks source link

Custom Context - Second UserAccount Table Created #651

Closed DavidRogersDev closed 8 years ago

DavidRogersDev commented 8 years ago

Hi Brock,

I am trying to use a custom DbContext which effectively combines your MR context with the DbContext of our domain. I feel that is necessary for transactions. I've checked out your samples.

I seemed to be winning this battle right up to the point where the database is created and a second UserAccounts table was being created called UserAccounts1. Note, the 2nd table does not have a Key column as its PK. It uses the ID (Guid) column as its PK (??weird??).

I have a domain class called BqaUser which represents one of our domain users and the idea is that there will be a 1:1 mapping between BqaUsers and UserAccounts. The second UserAccounts table only gets created when I include some kind of FK relationship between them in the mapping file. To clarify that point (b/c it is important), if I do not have any code in the BqaUserMap class setting up the FK relationship between the BqaUser and the UserAccount, then the 2nd, redundant table does not get created.

I'm going to drop some code here in the spirit of helpfulness, but if you are too busy to go through it, that's OK. I'll be happy with just a nudge in the right direction (if possible).

BqaUser:

public class BqaUser : ITrackable, IMergeable
    {
        public BqaUser()
        {

        }

        public int Id { get; set; }

        public string FirstName { get; set; }
        public string LastName { get; set; }

        public UserAccount UserAccount { get; set; }
        public Guid UserAccountId { get; set; }

        public TrackingState TrackingState { get; set; }
        public ICollection<string> ModifiedProperties { get; set; }
        public Guid EntityIdentifier { get; set; }
    }

BqaUserMap:

    public class BqaUserMap : EntityTypeConfiguration<BqaUser>
    {
        public BqaUserMap()
        {
            // Primary Key
            HasKey(t => t.Id);

            // Properties
            Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            Property(t => t.FirstName)
                .IsRequired()
                .HasMaxLength(250);
            Property(t => t.LastName)
                .IsRequired()
                .HasMaxLength(250);
            Property(t => t.UserAccountId)
                .IsRequired();

            // Table & Column Mappings
            ToTable("BqaUsers");

            HasRequired(p => p.UserAccount).WithRequiredDependent();

            // Tracking Properties - not to be stored as columns in the database.
            Ignore(t => t.TrackingState);
            Ignore(t => t.ModifiedProperties);
            Ignore(t => t.EntityIdentifier);
        }
    }

BqaContext with helper extensions class:

    public class BqaContext : MembershipRebootDbContext<RelationalUserAccount>
    {
        static BqaContext()
        {

        }

        public BqaContext()
            : base("BQADbConnection")
        {
            Database.SetInitializer<BqaContext>(new DropCreateDatabaseIfModelChanges<BqaContext>());

            Configuration.ProxyCreationEnabled = false;
        }

        public DbSet<BqaUser> BqaUsers { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new BqaUserMap());
            modelBuilder.ConfigureMembershipRebootUserAccounts<RelationalUserAccount>();

            base.OnModelCreating(modelBuilder);
        }
    }

    public static class BqaEfConfigureExtensions
    {
        public static void ConfigureMembershipRebootUserAccounts<TAccount>(this DbModelBuilder modelBuilder)
            where TAccount : RelationalUserAccount
        {
            modelBuilder.ConfigureMembershipRebootUserAccounts<int, TAccount, RelationalUserClaim, RelationalLinkedAccount, RelationalLinkedAccountClaim, RelationalPasswordResetSecret, RelationalTwoFactorAuthToken, RelationalUserCertificate>(null);
        }

        public static void ConfigureMembershipRebootUserAccounts<TKey, TAccount, TUserClaim, TLinkedAccount, TLinkedAccountClaim, TPasswordResetSecret, TTwoFactorAuthToken, TUserCertificate>(this DbModelBuilder modelBuilder, string schemaName)
            where TAccount : RelationalUserAccount<TKey, TUserClaim, TLinkedAccount, TLinkedAccountClaim, TPasswordResetSecret, TTwoFactorAuthToken, TUserCertificate>
            where TUserClaim : RelationalUserClaim<TKey>, new()
            where TLinkedAccount : RelationalLinkedAccount<TKey>, new()
            where TLinkedAccountClaim : RelationalLinkedAccountClaim<TKey>, new()
            where TPasswordResetSecret : RelationalPasswordResetSecret<TKey>, new()
            where TTwoFactorAuthToken : RelationalTwoFactorAuthToken<TKey>, new()
            where TUserCertificate : RelationalUserCertificate<TKey>, new()
        {
            modelBuilder.Entity<TAccount>()
                .HasKey(x => x.Key).ToTable("UserAccounts", schemaName);

            modelBuilder.Entity<TAccount>().HasMany(x => x.PasswordResetSecretCollection)
                .WithRequired().HasForeignKey(x => x.ParentKey).WillCascadeOnDelete();
            modelBuilder.Entity<TPasswordResetSecret>()
                .HasKey(x => x.Key).ToTable("PasswordResetSecrets", schemaName);

            modelBuilder.Entity<TAccount>().HasMany(x => x.TwoFactorAuthTokenCollection)
                .WithRequired().HasForeignKey(x => x.ParentKey).WillCascadeOnDelete();
            modelBuilder.Entity<TTwoFactorAuthToken>()
                .HasKey(x => x.Key).ToTable("TwoFactorAuthTokens", schemaName);

            modelBuilder.Entity<TAccount>().HasMany(x => x.UserCertificateCollection)
                .WithRequired().HasForeignKey(x => x.ParentKey).WillCascadeOnDelete();
            modelBuilder.Entity<TUserCertificate>()
                .HasKey(x => x.Key).ToTable("UserCertificates", schemaName);

            modelBuilder.Entity<TAccount>().HasMany(x => x.ClaimCollection)
                .WithRequired().HasForeignKey(x => x.ParentKey).WillCascadeOnDelete();
            modelBuilder.Entity<TUserClaim>()
                .HasKey(x => x.Key).ToTable("UserClaims", schemaName);

            modelBuilder.Entity<TAccount>().HasMany(x => x.LinkedAccountCollection)
                .WithRequired().HasForeignKey(x => x.ParentKey).WillCascadeOnDelete();
            modelBuilder.Entity<TLinkedAccount>()
                .HasKey(x => x.Key).ToTable("LinkedAccounts", schemaName);

            modelBuilder.Entity<TAccount>().HasMany(x => x.LinkedAccountClaimCollection)
                .WithRequired().HasForeignKey(x => x.ParentKey).WillCascadeOnDelete();
            modelBuilder.Entity<TLinkedAccountClaim>()
                .HasKey(x => x.Key).ToTable("LinkedAccountClaims", schemaName);
        }
    }

MembershipRebootDbContext:

public class MembershipRebootDbContext<TUserAccount, TGroup> : DbContext
        where TUserAccount : RelationalUserAccount
        where TGroup : RelationalGroup
    {
        public MembershipRebootDbContext()
            : this("BQADbConnection", null)
        {
        }

        public MembershipRebootDbContext(string nameOrConnectionString)
            : this(nameOrConnectionString, null)
        {
        }

        public MembershipRebootDbContext(string nameOrConnectionString, string schemaName)
            : base(nameOrConnectionString)
        {
            this.SchemaName = schemaName;
            this.RegisterUserAccountChildTablesForDelete<TUserAccount>();
            this.RegisterGroupChildTablesForDelete<TGroup>();
        }

        public string SchemaName { get; private set; }

        public DbSet<TUserAccount> Users { get; set; }
        public DbSet<TGroup> Groups { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            //modelBuilder.ConfigureMembershipRebootUserAccounts<TUserAccount>(this.SchemaName);
            modelBuilder.ConfigureMembershipRebootGroups<TGroup>(this.SchemaName);
        }
    }

    public class MembershipRebootDbContext<TUserAccount> : MembershipRebootDbContext<TUserAccount, RelationalGroup>
        where TUserAccount : RelationalUserAccount
    {
        public MembershipRebootDbContext()
            : base()
        {
        }

        public MembershipRebootDbContext(string nameOrConnectionString)
            : base(nameOrConnectionString)
        {
        }

        public MembershipRebootDbContext(string nameOrConnectionString, string schemaName)
            : base(nameOrConnectionString, schemaName)
        {
        }
    }   

DefaultUserAccountRepository:

    public class DefaultUserAccountRepository
           : DbContextUserAccountRepository<BqaContext, RelationalUserAccount>,
             IUserAccountRepository
    {
        public DefaultUserAccountRepository(BqaContext ctx)
            : base(ctx)
        {
        }

        IUserAccountRepository<RelationalUserAccount> This { get { return (IUserAccountRepository<RelationalUserAccount>)this; } }

        public new UserAccount Create()
        {
            return This.Create();
        }

        public void Add(UserAccount item)
        {
            This.Add((RelationalUserAccount)item);
        }

        public void Remove(UserAccount item)
        {
            This.Remove((RelationalUserAccount)item);
        }

        public void Update(UserAccount item)
        {
            This.Update((RelationalUserAccount)item);
        }

        public new UserAccount GetByID(Guid id)
        {
            return This.GetByID(id);
        }

        public new UserAccount GetByUsername(string username)
        {
            return This.GetByUsername(username);
        }

        UserAccount IUserAccountRepository<UserAccount>.GetByUsername(string tenant, string username)
        {
            return This.GetByUsername(tenant, username);
        }

        public new UserAccount GetByEmail(string tenant, string email)
        {
            return This.GetByEmail(tenant, email);
        }

        public new UserAccount GetByMobilePhone(string tenant, string phone)
        {
            return This.GetByMobilePhone(tenant, phone);
        }

        public new UserAccount GetByVerificationKey(string key)
        {
            return This.GetByVerificationKey(key);
        }

        public new UserAccount GetByLinkedAccount(string tenant, string provider, string id)
        {
            return This.GetByLinkedAccount(tenant, provider, id);
        }

        public new UserAccount GetByCertificate(string tenant, string thumbprint)
        {
            return This.GetByCertificate(tenant, thumbprint);
        }

        /// <summary>
        /// Overriding this was instrumental as MembershipReboot has proxies enabled out of the box. 
        /// </summary>
        protected override IQueryable<RelationalUserAccount> Queryable
        {
            get
            {
                return base.Queryable.Include(a => a.ClaimCollection).Include(a => a.UserCertificateCollection);
            }
        }
    }

The other curious thing is that it creates a __MigrationHistory table, which I thought was only created if you used Migrations. As far as I can tell, I am not using EF Migrations. I am not using the MigrateDatabaseToLatestVersion class anwhere as an initializer.

Thanks.

brockallen commented 8 years ago

Yea, you're now fighting in the world of EF. I don't have a good answer for you other than perhaps us ea different DbCtx for your other data.

DavidRogersDev commented 8 years ago

Thanks Brock. As great as it is, EF can be a hard mistress.

DavidRogersDev commented 8 years ago

Oh, and as an FYI, I think I just discovered why there is a __MigrationHistory table, even though I am not using Migrations (I much prefer Paul Stovell's DbUp). It is by design and a new thing in EF6.