Introduction
This is Part IV of a multi-part article which demonstrates the mapping of C# enum
values to string
values in database tables via EntityFramework Core 2.1 (EF). It addresses the mapping of enum
values in one-to-many and many-to-many relationships with application entities. It does this in the context of an ASP.NET Core Razor Page application.
EF is an Object-Relational Mapper (ORM). In an application such as this sample, there are two "worlds". One is the object world that exists as an object model in C#. The other is the relational world that exists in a relational database, like Microsoft SQL Server. These two worlds are not consistent with each other. The function of an ORM, like EntityFramework, is to bridge these two worlds and facilitate the transfer of data between them.
Part I. Setup the Entity Framework data context and the initial Customers Razor Pages
Part II. Complete CRUD functions for Customers
Part III. Create Project and ProjectState entities and implement a one-to-many relationship between ProjectState and Projects
In Part IV: Add Skill entities (Skill enum
, SkillTitle
and ProjectSkill
) and implement a many-to-many relationship between Projects and Skills as follows:
- Add entities
Skill
, SkillTitle
and ProjectSkill
and configure into QuantumDbContext
. - Add migration, Add-Project-Skill-Entities, and Update-Database.
- Scaffold Skill related Razor pages, CustomerProjectSkills.cshtml, CustomerProjectSkillAssign.cshtml and CustomerProjectSkillDelete.cshtml. Run tests to confirm functions.
Background
The sample application implemented in this series is for a fictitious engineering and technology company, Quantum Engineering Technologies. Quantum primarily serves the oil, gas and chemical industries. To this point, we have discussed the basis of the object model and created a number of ASP.NET Razor Pages to handle Customers
, Projects
and Project
states. In this final part, we will discuss listing of the skills required to execute the work. This effort is built around a Skill enum
and the many-to-many relationship between Skills and Projects. There can be many Projects requiring a certain Skill
. There can, also, be many Skills needed to execute a Project. Hence, there exists a many-to-many relationship between the Project and Skill entities.
In relational databases, many-to-many relationships are implemented by creating a join
table for the two entities as shown below.
Many-to-many Relationship via Join Table
There is a many-to-many relationship between TABLE A and TABLE B. In a relational database, one creates a join table, JOIN-TABLE A-B, with a one-to-many relationship with both tables.
In previous versions of Entity Framework (EF), the join
table could be configured by convention without the need for an entity or class in the object model. However, this feature has not been provided in Entity Framework Core as of this date. (See this link.) Therefore, we need to create and configure a class in our object model to achieve this end. The following discussion shows how this is done along with the associated Razor Pages to manage things in the UI.
Using the Code
The working object model is shown in Part I and repeated here for Skills.
Working Object Model for Skills
Skill.cs
namespace QuantumWeb.Model
{
/// <summary>
/// Skill Enumeration
/// </summary>
public enum Skill
{
Programmer, // Programmer
ME, // Mechanical Engineer
ChE, // Chemical Engineer
CtrlE, // Control Engineer
SWE, // Software Engineer
SWArch, // Software Architect
WebDes, // Web Designer
DataSci, // Data Scientist
ProjMgr, // Project Manager
BusAnal, // Business Analyst
QA // Quality Assurance Tester
} // end public enum Skill
} // end namespace QuantumWeb.Model
We provide a SkillTitle
class so we can provide associated titles for enum
values in the UI.
SkillTitle.cs
using System.Collections.Generic;
namespace QuantumWeb.Model
{
/// <summary>
/// SkillTitle Class
/// </summary>
public class SkillTitle
{
/// <summary>
/// Skill Code
/// </summary>
public Skill SkillCode { get; set; }
/// <summary>
/// Skill Title
/// </summary>
public string Title { get; set; }
#region Navigation Properties
/// <summary>
/// List of ProjectSkill Instances
/// </summary>
public List<ProjectSkill> ProjectSkills { get; set; }
#endregion // Navigation Properties
} // end public class SkillTitle
} // end namespace QuantumWeb.Model
This class references the class, ProjectSkill
, which will be configured to the join
table in the database.
ProjectSkill.cs
namespace QuantumWeb.Model
{
/// <summary>
/// ProjectSkill Class, a join entity
/// </summary>
/// <remarks>
/// Note: The database table will have a non-unique index on ProjectId and SkillCode
/// </remarks>
public class ProjectSkill
{
/// <summary>
/// ProjectSkill Identifier, primary key
/// </summary>
public int ProjectSkillId { get; set; }
#region Navigation Properties
/// <summary>
/// Project Identifier
/// </summary>
public int ProjectId { get; set; }
/// <summary>
/// Project Reference
/// </summary>
public Project Project { get; set; }
/// <summary>
/// Skill Code
/// </summary>
public Skill SkillCode { get; set; }
/// <summary>
/// SkillTitle Reference
/// </summary>
public SkillTitle SkillTitle { get; set; }
#endregion // Navigation Properties
} // end public class ProjectSkill
} // end namespace QuantumWeb.Model
Notice the navigation properties. There is a reference to a single Project
instance and a single SkillTitle
instance. The ProjectId
and SkillCode
properties will map to foreign keys in the ProjectSkills
table in the database. Also, notice that the SkillTitle
class has a collection property, ProjectSkills
(a List<ProjectSkill>
), that indicates it can be related to zero or more ProjectSkill
instances. We now must modify the Project
class.
Modified Project.cs
using System.Collections.Generic;
namespace QuantumWeb.Model
{
/// <summary>
/// Project Class
/// </summary>
public class Project
{
/// <summary>
/// Project Identifier, primary key
/// </summary>
public int ProjectId { get; set; }
/// <summary>
/// Project Name
/// </summary>
public string ProjectName { get; set; }
#region Navigation Properties
/// <summary>
/// Customer Identifier
/// </summary>
public int CustomerId { get; set; }
/// <summary>
/// Customer
/// </summary>
/// <remarks>
/// Every Project has a Customer
/// </remarks>
public Customer Customer { get; set; }
/// <summary>
/// Project Status Code
/// </summary>
public ProjectState ProjectStateCode { get; set; }
/// <summary>
/// ProjectStateDescription Reference
/// </summary>
public ProjectStateDescription ProjectStateDescription { get; set; }
/// <summary>
/// List of ProjectSkill Instances
/// </summary>
public List<ProjectSkill> ProjectSkills { get; set; }
#endregion // Navigation Properties
} // end public class Project
} // end namespace QuantumApp.Model
Here, we added a navigation property, ProjectSkills
(a List<ProjectSkill>
), which allows a Project
to be related to multiple ProjectSkill
instances.
Now we must configure these classes in the QuantumDbContext
class to create the database mappings. First, we create the SkillTitleConfiguration
class.
SkillTitleConfiguration.cs
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using QuantumWeb.Model;
namespace QuantumWeb.Data
{
public class SkillTitleConfiguration : IEntityTypeConfiguration<SkillTitle>
{
public void Configure(EntityTypeBuilder<SkillTitle> builder)
{
builder.ToTable("SkillTitles");
builder.HasKey(st => st.SkillCode);
builder.Property(st => st.SkillCode)
.HasColumnType("nvarchar(20)")
.HasConversion(
st => st.ToString(),
st => (Skill)Enum.Parse(typeof(Skill), st));
builder.Property(st => st.Title)
.IsRequired()
.HasColumnType("nvarchar(50)")
.HasMaxLength(50);
} // end public void Configure(EntityTypeBuilder<SkillTitle> builder)
} // end public class SkillTitleConfiguration : IEntityTypeConfiguration<SkillTitle>
} // end namespace QuantumWeb.Data
This maps the SkillTitle
class to the SkillTitles
database table. It also configures the conversion between the Skill enum
values and the SkillCode string
column values in the SkillTitles
table. This is similar to the mapping of the ProjectState enum
values to the ProjectStateCode
values discussed in Part III.
We can now configure the mapping of the ProjectSkill
class to the join
table, ProjectSkills
.
ProjectSkillConfiguration.cs
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using QuantumWeb.Model;
namespace QuantumWeb.Data
{
public class ProjectSkillConfiguration : IEntityTypeConfiguration<ProjectSkill>
{
public void Configure(EntityTypeBuilder<ProjectSkill> builder)
{
builder.ToTable("ProjectSkills");
builder.HasKey(ps => ps.ProjectSkillId);
builder.Property(ps => ps.ProjectSkillId)
.HasColumnType("int");
builder.Property(ps => ps.ProjectId)
.HasColumnType("int");
builder.Property(ps => ps.SkillCode)
.HasColumnType("nvarchar(20)")
.HasConversion(
ps => ps.ToString(),
ps => (Skill)Enum.Parse(typeof(Skill), ps));
builder.HasIndex(ps => new { ps.ProjectId, ps.SkillCode })
.IsUnique(false);
builder.HasOne<Project>(ps => ps.Project)
.WithMany(p => p.ProjectSkills)
.HasForeignKey(ps => ps.ProjectId);
builder.HasOne<SkillTitle>(ps => ps.SkillTitle)
.WithMany(p => p.ProjectSkills)
.HasForeignKey(ps => ps.SkillCode);
} // end public void Configure(EntityTypeBuilder<ProjectSkill> builder)
} // end public class ProjectSkillConfiguration : IEntityTypeConfiguration<ProjectSkill>
} // end namespace QuantumWeb.Data
We now modify the QuantumDbContext
class to reflect these changes.
Modified QuantumDbContext.cs
using Microsoft.EntityFrameworkCore;
using QuantumWeb.Model;
namespace QuantumWeb.Data
{
public class QuantumDbContext : DbContext
{
public QuantumDbContext (DbContextOptions<QuantumDbContext> options)
: base(options)
{
}
#region DbSets
public DbSet<Customer> Customers { get; set; }
public DbSet<Project> Projects { get; set; }
public DbSet<ProjectStateDescription> ProjectStateDescriptions { get; set; }
public DbSet<SkillTitle> SkillTitles { get; set; }
public DbSet<ProjectSkill> ProjectSkills { get; set; }
#endregion // DbSets
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new CustomerConfiguration());
modelBuilder.ApplyConfiguration(new ProjectConfiguration());
modelBuilder.ApplyConfiguration(new ProjectStateDescriptionConfiguration());
modelBuilder.ApplyConfiguration(new SkillTitleConfiguration());
modelBuilder.ApplyConfiguration(new ProjectSkillConfiguration());
}
}
}
Here we define the DbSets
, SkillTitles
and ProjectSkills
, and add the SkillTitleConfiguration
and ProjectSkillConfiguration
classes for processing in the OnModelCreating
method. Now we create a migration and update the database in the Package Manager Console.
Add-Migration Add-Project-Skill-Entities
Generated 20181025222456_Add-Project-Skill-Entities.cs
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace QuantumWeb.Migrations
{
public partial class AddProjectSkillEntities : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "SkillTitles",
columns: table => new
{
SkillCode = table.Column<string>(type: "nvarchar(20)", nullable: false),
Title = table.Column<string>(type: "nvarchar(50)",
maxLength: 50, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SkillTitles", x => x.SkillCode);
});
migrationBuilder.CreateTable(
name: "ProjectSkills",
columns: table => new
{
ProjectSkillId = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn),
ProjectId = table.Column<int>(type: "int", nullable: false),
SkillCode = table.Column<string>(type: "nvarchar(20)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ProjectSkills", x => x.ProjectSkillId);
table.ForeignKey(
name: "FK_ProjectSkills_Projects_ProjectId",
column: x => x.ProjectId,
principalTable: "Projects",
principalColumn: "ProjectId",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ProjectSkills_SkillTitles_SkillCode",
column: x => x.SkillCode,
principalTable: "SkillTitles",
principalColumn: "SkillCode",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ProjectSkills_SkillCode",
table: "ProjectSkills",
column: "SkillCode");
migrationBuilder.CreateIndex(
name: "IX_ProjectSkills_ProjectId_SkillCode",
table: "ProjectSkills",
columns: new[] { "ProjectId", "SkillCode" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ProjectSkills");
migrationBuilder.DropTable(
name: "SkillTitles");
}
}
}
There are some key points to consider.
- The
SkillTitles
table has a primary key, SkillCode
, which maps to a value in the Skill enum
. - The
ProjectSkills
table has an integer primary key, ProjectSkillId
, and two foreign keys, ProjectId
, to link to the Project
record and, SkillCode
, to link to the SkillTitle
record. Some writers recommend setting a multi-valued primary key, ProjectId
and SkillCode
. That would setup some issues with a Project
needing multiple skills of the same type. We do set up a non-unique index, IX_ProjectSkills_ProjectId_SkillCode
, however.
We will see some of these features in action later. Now to move the migration to the database.
Update-Database
The diagram below generated from SQL Server Management Studio (SSMS) shows the relevant database tables.
Customers-Projects-Skills Database Diagram
Now we include these entities in the UI. First, we create a link to ProjectSkills
in the CustomerProjects Index
page.
Modified CustomerProject.cshtml
@page "{id:int?}"
@model QuantumWeb.Pages.Customers.CustomerProjectsModel
@{
ViewData["Title"] = "Customer Projects";
}
<h2>Customer Projects</h2>
<div>
<h4>Customer</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Customer.CustomerId)
</dt>
<dd>
@Html.DisplayFor(model => model.Customer.CustomerId)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Customer.CustomerName)
</dt>
<dd>
@Html.DisplayFor(model => model.Customer.CustomerName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Customer.Projects)
</dt>
<dd>
<table class="table">
<tr>
<th>Project ID</th>
<th>Project Name</th>
<th>Project State</th>
<th></th>
</tr>
@foreach (var item in Model.Customer.Projects)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.ProjectId)
</td>
<td>
@Html.DisplayFor(modelItem => item.ProjectName)
</td>
<td>
@Html.DisplayFor(modelItem => item.ProjectStateCode)
</td>
<td>
<a asp-page="./CustomerProjectSkills"
asp-route-id="@item.ProjectId">
ProjectSkills
</a> |
<a asp-page="./CustomerProjectEdit"
asp-route-id="@item.ProjectId">Edit</a> |
<a asp-page="./CustomerProjectDelete"
asp-route-id="@item.ProjectId">Delete</a>
</td>
</tr>
}
</table>
</dd>
</dl>
</div>
<div>
<a asp-page="CustomerProjectCreate" asp-route-id="@Model.Customer.CustomerId">
Create New Project
</a> |
<a asp-page="./Index">Back to List</a>
</div>
The link to CustomerProjectSkills
requires that we scaffold a new Razor Page.
Scaffold Customers/CustomerProjectSkills Razor Page
Initial ~Pages\Customers\CustomerProjectSkills.cshtml
@page "{id:int?}"
@model QuantumWeb.Pages.Customers.CustomerProjectSkillsModel
@{
ViewData["Title"] = "Customer Project Skills";
}
<h2>Customer Project Skills</h2>
<p>
<a asp-page="./CustomerProjects"
asp-route-id="@Model.Customer.CustomerId">Back to List</a> |
<a asp-page="CustomerProjectSkillAssign"
asp-route-id="@Model.Project.ProjectId">Assign Skill</a>
</p>
<div>
<h4>Customer-Project</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Customer.CustomerId)
</dt>
<dd>
@Html.DisplayFor(model => model.Customer.CustomerId)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Customer.CustomerName)
</dt>
<dd>
@Html.DisplayFor(model => model.Customer.CustomerName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Project.ProjectId)
</dt>
<dd>
@Html.DisplayFor(model => model.Project.ProjectId)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Project.ProjectName)
</dt>
<dd>
@Html.DisplayFor(model => model.Project.ProjectName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Project.ProjectSkills)
</dt>
<dd>
<table class="table">
<tr>
<th>Project Skill Id</th>
<th>Skill Code</th>
<th>Skill Title</th>
<th></th>
</tr>
@foreach (var item in Model.Project.ProjectSkills)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.ProjectSkillId)
</td>
<td>
@Html.DisplayFor(modelItem => item.SkillCode)
</td>
<td>
@Html.DisplayFor(modelItem => item.SkillTitle.Title)
</td>
<td>
<a asp-page="./CustomerProjectSkillDelete"
asp-route-id="@item.ProjectSkillId">
Delete
</a>
</td>
</tr>
}
</table>
</dd>
</dl>
</div>
This configures the page to use a nullable parameter, id
, which will allow the ProjectId
of the target Project
to be passed to the OnGet
handler. There is a link to a CustomerProjectSkillAssign
page which will create a ProjectSkill
record effectively assigning a Skill
to the Project. There is another link to the CustomerProjectSkillDelete
page which will delete a ProjectSkill
record. We will show code for these two pages later.
Initial ~Pages\Customers\CustomerProjectSkills.cshtml.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using QuantumWeb.Data;
using QuantumWeb.Model;
namespace QuantumWeb.Pages.Customers
{
public class CustomerProjectSkillsModel : PageModel
{
private readonly QuantumDbContext _context;
public CustomerProjectSkillsModel(QuantumDbContext context)
{
_context = context;
}
public Project Project { get; set; }
public Customer Customer { get; set; }
public async Task<IActionResult> OnGet(int? id)
{
if (id == null)
{
return NotFound();
}
Project = await _context.Projects
.Include(p => p.Customer)
.Include(p => p.ProjectSkills)
.ThenInclude(ps => ps.SkillTitle)
.FirstOrDefaultAsync(p => p.ProjectId == id);
if (Project == null)
{
return NotFound();
}
Customer = Project.Customer;
return Page();
}
}
}
Please take note of the example of Entity Framework(EF) eager loading.
Project = await _context.Projects
.Include(p => p.Customer)
.Include(p => p.ProjectSkills)
.ThenInclude(ps => ps.SkillTitle)
.FirstOrDefaultAsync(p => p.ProjectId == id);
In EF, eager loading related entities are retrieved in a single query using the Include()
and ThenInclude
extension methods. Here, we retrieve the related Customer
and ProjectSkills
entities for the Project
whose ProjectId
has the value of the input parameter, id
. The query goes one step further using the ThenInclude()
method to retrieve the SkillTitles
associated with the retrieved ProjectSkills
.
The next few screenshots show the sequence to navigate to this page.
QuantumWeb Application Home Page: https//localhost: 44306/
QuantumWeb Application Customer Index Page: https//localhost: 44306/Customers
Customer Projects Page with 2 Projects for Mirarex Oil & Gas (ProjectSkills link added)
Customer Project Skills Page for Mirarex Oil & Gas, Zolar Pipeline (No Skills)
We now show the scaffolding of the CustomerProjectSkillAssign
Razor Page.
Scaffold Customers/CustomerProjectSkillAssign Razor Page
Initial ~Pages\Customers\CustomerProjectSkillAssign.cshtml.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using QuantumWeb.Data;
using QuantumWeb.Model;
namespace QuantumWeb.Pages.Customers
{
public class CustomerProjectSkillAssignModel : PageModel
{
private readonly QuantumDbContext _context;
public CustomerProjectSkillAssignModel(QuantumDbContext context)
{
_context = context;
} // end public CustomerProjectSkillAssignModel(QuantumDbContext context)
[BindProperty]
public Project Project { get; set; }
[BindProperty]
public Customer Customer { get; set; }
[BindProperty]
public SkillTitle SkillTitle { get; set; }
public async Task<IActionResult> OnGet(int? id)
{
if (id == null)
{
return NotFound();
} // endif (id == null)
Project = await _context.Projects
.Include(p => p.Customer)
.Include(p => p.ProjectSkills)
.ThenInclude(ps => ps.SkillTitle)
.FirstOrDefaultAsync(p => p.ProjectId == id);
if (Project == null)
{
return NotFound();
} // endif (Project == null)
Customer = Project.Customer;
ViewData["SkillCode"] = new SelectList(_context.SkillTitles, "SkillCode", "Title");
return Page();
}// end public async Task<IActionResult> OnGet(int? id)
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
} // endif (!ModelState.IsValid)
ProjectSkill projectSkill = new ProjectSkill()
{
ProjectId = Project.ProjectId,
SkillCode = SkillTitle.SkillCode
};
_context.ProjectSkills.Add(projectSkill);
await _context.SaveChangesAsync();
return RedirectToPage("./CustomerProjectSkills", new { id = Project.ProjectId });
} // end public async Task<IActionResult> OnPostAsync()
} // end public class CustomerProjectSkillAssignModel : PageModel
} // end namespace QuantumWeb.Pages.Customers
Here again, we have a ViewData
to setup a select list as in Part III.
ViewData["SkillCode"] = new SelectList(_context.SkillTitles, "SkillCode", "Title");
This time, it handles the SkillTitles
for selecting Skills to associate with a Project
.
Initial ~Pages\Customers\CustomerProjectSkillAssign.cshtml
@page "{id:int?}"
@model QuantumWeb.Pages.Customers.CustomerProjectSkillAssignModel
@{
ViewData["Title"] = "Customer Project Skill Assignment";
}
<h2>Customer Project Skill Assignment</h2>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Project.ProjectId)
</dt>
<dd>
@Html.DisplayFor(model => model.Project.ProjectId)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Project.ProjectName)
</dt>
<dd>
@Html.DisplayFor(model => model.Project.ProjectName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Customer.CustomerId)
</dt>
<dd>
@Html.DisplayFor(model => model.Customer.CustomerId)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Customer.CustomerName)
</dt>
<dd>
@Html.DisplayFor(model => model.Customer.CustomerName)
</dd>
</dl>
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly"
class="text-danger"></div>
<input type="hidden" asp-for="Project.ProjectId" />
<div class="form-group">
<label asp-for="SkillTitle.SkillCode"
class="control-label"></label>
<select asp-for="SkillTitle.SkillCode"
class="form-control" asp-items="ViewBag.SkillCode"></select>
</div>
<div class="form-group">
<a asp-page="./CustomerProjectSkills"
asp-route-id="Project.ProjectId">Project Skills</a> |
<input type="submit" value="Assign" class="btn btn-default" />
</div>
</form>
</div>
</div>
Customer Project Skill Assignment Page for Zolar Pipeline (Skill select open)
Customer Project Skill Assignment Page for Zolar Pipeline (Assign a programmer)
Customer Project Skills Page for Mirarex Oil & Gas, Zolar Pipeline (Programmer assigned)
After assigning several more skills, this page looks like:
Customer Project Skills Page for Mirarex Oil & Gas, Zolar Pipeline (Several Skills)
Management decides there are too many programmers assigned, so we need to delete one. We now will scaffold the CustomerProjectSkillDelete
page.
Scaffold Customers/CustomerProjectSkillDelete Razor Page
Initial ~Pages\Customers\CustomerProjectSkillDelete.cshtml.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using QuantumWeb.Data;
using QuantumWeb.Model;
namespace QuantumWeb.Pages.Customers
{
public class CustomerProjectSkillDeleteModel : PageModel
{
private readonly QuantumDbContext _context;
public CustomerProjectSkillDeleteModel(QuantumDbContext context)
{
_context = context;
}
[BindProperty]
public Customer Customer { get; set; }
[BindProperty]
public Project Project { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Project = await _context.Projects
.Include(p => p.Customer)
.FirstOrDefaultAsync(p => p.ProjectId == id);
if (Project == null)
{
return NotFound();
}
Customer = Project.Customer;
return Page();
}
public async Task<IActionResult> OnPostAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Project = await _context.Projects
.Include(p => p.Customer)
.FirstOrDefaultAsync(p => p.ProjectId == id);
if (Project != null)
{
_context.Projects.Remove(Project);
await _context.SaveChangesAsync();
}
return RedirectToPage("./CustomerProjectSkills",
new { id = Project.Customer.CustomerId });
}
}
}
Initial ~Pages\Customers\CustomerProjectSkillDelete.cshtml
@page "{id:int?}"
@model QuantumWeb.Pages.Customers.CustomerProjectDeleteModel
@{
ViewData["Title"] = "Delete Customer Project";
}
<h2>Delete Customer Project</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Customer.CustomerName)
</dt>
<dd>
@Html.DisplayFor(model => model.Customer.CustomerName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Project.ProjectId)
</dt>
<dd>
@Html.DisplayFor(model => model.Project.ProjectId)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Project.ProjectName)
</dt>
<dd>
@Html.DisplayFor(model => model.Project.ProjectName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Project.ProjectStateCode)
</dt>
<dd>
@Html.DisplayFor(model => model.Project.ProjectStateCode)
</dd>
</dl>
<form method="post">
<input type="hidden" asp-for="Project.ProjectId" />
<a asp-page="CustomerProjects" asp-route-id="
@Model.Customer.CustomerId">Back to Customer Projects</a> |
<input type="submit" value="Delete" class="btn btn-default" />
</form>
</div>
Customer Project Skill Delete Page for Mirarex Oil & Gas, Zolar Pipeline
Customer Project Skills Page for Mirarex Oil & Gas, Zolar Pipeline (Record deleted)
Summary
We have implemented the Skill to Project many-to-many relationship and the join entity, ProjectSkill
, to implement the feature in Entity Framework Core. We also created ASP.NET Razor Pages to manage the entities.