ASP.NET Core 3.1 - Rename Related Entities
This article will demonstrate a process for renaming related EF Core entities and database objects without dropping data. I will assume you have downloaded the ASP.NET Core 3.1 - Users Without Identity Project or created a new ASP.NET Core 3.1 Razor Pages project. See Tutorial: Get started with Razor Pages in ASP.NET Core. You should review the earlier articles of the Users Without Identity Project series.
Users Without Identity Project and Article Series
Creation Series
- ASP.NET Core 3.1 - Users Without Identity
- ASP.NET Core 3.1 - User Entity
- ASP.NET Core 3.1 - Password Hasher
- ASP.NET Core 3.1 - User Management
- ASP.NET Core 3.1 - Admin Role
- ASP.NET Core 3.1 - Cookie Validator
- ASP.NET Core 3.1 - Concurrency Conflicts
- ASP.NET Core 3.1 - Must Change Password
- ASP.NET Core 3.1 - User Database Service
- ASP.NET Core 3.1 - Rename Related Entities
2FA Series
- ASP.NET Core 3.1 - 2FA Without Identity
- ASP.NET Core 3.1 - 2FA User Tokens
- ASP.NET Core 3.1 - 2FA Cookie Schemes
- ASP.NET Core 3.1 - 2FA Authenticating
- ASP.NET Core 3.1 - 2FA Sign In Service
- ASP.NET Core 3.1 - 2FA QR Code Generator
- ASP.NET Core 3.1 - Admin 2FA Requirement
- ASP.NET Core 3.1 - 2FA Admin Override
Enhanced User Series
- ASP.NET Core 3.1 - Enhanced User Without Identity
- ASP.NET Core 3.1 - 2FA Recovery Codes
- ASP.NET Core 3.1 - Login Lockout
- ASP.NET Core 3.1 - Created And Last Login Date
- ASP.NET Core 3.1 - Security Stamp
- ASP.NET Core 3.1 - Token Service
- ASP.NET Core 3.1 - Confirmed Email Address
- ASP.NET Core 3.1 - Password Recovery
I prototyped an application for a prospect with a simple user management system which I published to demo.kenhaggerty.com. I named the user entity User. When I created the Users Without Identity Project (UWIP) I named the user entity AppUser. When I implemented 2FA, the demo site used a UserToken entity and UWIP used AppUserToken.
I recently updated almost every page in the Account and Account / Mange folders of the UWIP. To implement all the updates to the demo site, I decided to copy the folders and rename the user and user token entities to AppUser and AppUserToken.
Demo site - User.cs:
public class User { [Key] public int Id { get; set; } [Required] [Display(Name = "Login Name")] [StringLength(32, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] public string LoginName { get; set; } [Required] [StringLength(32)] public string LoginNameUppercase { get; set; } [Required] [MaxLength(84, ErrorMessage = "The {0} is max {1} characters long.")] public string PasswordHash { get; set; } [Display(Name = "MCP")] public bool MustChangePassword { get; set; } [Display(Name = "2FA")] public bool TwoFactorEnabled { get; set; } [Display(Name = "Admin")] public bool IsAdmin { get; set; } [Timestamp] public byte[] RowVersion { get; set; } public virtual ICollection<UserToken> UserTokens { get; set; } }
Demo site - UserToken.cs:
public class UserToken { [Required] public int UserId { get; set; } [Required] [StringLength(128)] public string Name { get; set; } public string Value { get; set; } public virtual User User { get; set; } }
Notice the virtual UserTokens navigational property on User and the virtual User navigational property on UserToken. I implemented a unique constraint on LoginNameUppercase for the User and a non-clustered composite primary key for the UserToken in the ApplicationDbContext > OnModelCreating method.
Demo site - ApplicationDbContext.cs:
protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); // Set a unique constraint for LoginNameUppercase builder.Entity<User>().HasIndex(u => u.LoginNameUppercase).IsUnique(); // Composite primary key consisting of the UserId and Name builder.Entity<UserToken>().HasKey(t => new { t.UserId, t.Name }).IsClustered(false); }
After renaming the entities, navigational properties, and references to AppUser and AppUserToken along with the AppUserToken.AppUserId property, the Package Manager Console add migration command results in dropping the tables with existing data and creating new empty tables.
AppUserRename.cs:
public partial class AppUserRename : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( name: "UserTokens"); migrationBuilder.DropTable( name: "Users"); migrationBuilder.CreateTable( name: "AppUsers", columns: table => new { Id = table.Column<int>(nullable: false) .Annotation("SqlServer:Identity", "1, 1"), LoginName = table.Column<string>(maxLength: 32, nullable: false), LoginNameUppercase = table.Column<string>(maxLength: 32, nullable: false), PasswordHash = table.Column<string>(maxLength: 128, nullable: false), IsAdmin = table.Column<bool>(nullable: false), MustChangePassword = table.Column<bool>(nullable: false), TwoFactorEnabled = table.Column<bool>(nullable: false), IsDisabled = table.Column<bool>(nullable: false), RowVersion = table.Column<byte[]>(rowVersion: true, nullable: true) }, constraints: table => { table.PrimaryKey("PK_AppUsers", x => x.Id); }); migrationBuilder.CreateTable( name: "AppUserTokens", columns: table => new { AppUserId = table.Column<int>(nullable: false), Name = table.Column<string>(maxLength: 128, nullable: false), Value = table.Column<string>(nullable: true) }, constraints: table => { table.PrimaryKey("PK_AppUserTokens", x => new { x.AppUserId, x.Name }) .Annotation("SqlServer:Clustered", false); table.ForeignKey( name: "FK_AppUserTokens_AppUsers_AppUserId", column: x => x.AppUserId, principalTable: "AppUsers", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateIndex( name: "IX_AppUsers_LoginNameUppercase", table: "AppUsers", column: "LoginNameUppercase", unique: true); } }
I had a similar issue when I refactored KenHaggerty.Com's FrameworkEnum. See ASP.NET Core 5.0 - Framework Enum. My research found Stack Overflow - Entity Framework Migrations renaming tables and columns which I used to rename the TargetFrameworkEnum to FrameworkEnum. Renaming the user and user token entities is a little more complicated. The solution drops all the foreign and primary keys along with the unique constraint before renaming the tables and column then adding back the keys and constraint.
public partial class AppUserRename : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.DropForeignKey(name: "FK_UserTokens_Users_UserId", table: "UserTokens"); migrationBuilder.DropUniqueConstraint(name: "IX_Users_LoginNameUppercase", table: "Users"); migrationBuilder.DropPrimaryKey(name: "PK_Users", table: "Users"); migrationBuilder.DropPrimaryKey(name: "PK_UserTokens", table: "UserTokens"); migrationBuilder.RenameTable(name: "Users", newName: "AppUsers"); migrationBuilder.AddPrimaryKey(name: "PK_AppUsers", table: "AppUsers", column: "Id"); migrationBuilder.AddUniqueConstraint(name: "IX_AppUsers_LoginNameUppercase", table: "AppUsers", column: "LoginNameUppercase"); migrationBuilder.RenameTable(name: "UserTokens", newName: "AppUserTokens"); migrationBuilder.RenameColumn(name: "UserId", table: "AppUserTokens", newName: "AppUserId"); migrationBuilder.AddPrimaryKey(name: "PK_AppUserTokens", table: "AppUserTokens", columns: new string[] { "AppUserId", "Name" }) .Annotation("SqlServer:Clustered", false); migrationBuilder.AddForeignKey(name: "FK_AppUserTokens_AppUsers_AppUserId", table: "AppUserTokens", column: "AppUserId", principalTable: "AppUsers", principalColumn: "Id", onDelete: ReferentialAction.Cascade); } }
Update 02/23/2021
I added the Enhanced User Series' article links.
Comments(0)