ASP.NET Core 5.0 : MVC, Razor Pages, Web API, EF Core, Blazor, Design Patterns, and more. Private online coaching for software developers. Click here for more details.

Perform Master Detail CRUD operations in ASP.NET Core (Part 3)

In the previous part of  this article series you created the TeamsController that performs CRUD operations on the Teams table. In this part you will continue to develop the app by creating TeamMembersController. The TeamMembersController performs CRUD operations on the TeamMembers table.

Begin by adding TeamMembersController class into the Controllers folder. The following figure shows both the controllers in the Controllers folder.

You will need to onject AppDbContext in the TeamMembersController also:

public class TeamMembersController : Controller
{
    private readonly AppDbContext db;

    public TeamMembersController(AppDbContext db)
    {
        this.db = db;
    }
}

When you click on the Manage Members button from a particular master row, the team members are displayed in the detail grid.

Clicking the Manage Members button invokes the List() action of TeamMembersController. The List() action is shown below:

[HttpPost]
public IActionResult List(int teamId)
{
    MasterDetailViewModel model = new MasterDetailViewModel
    {
        Teams = db.Teams.ToList(),
        SelectedTeam = db.Teams.Find(teamId),
        DataEntryTarget = DataEntryTargets.TeamMembers,
        DataDisplayMode = DataDisplayModes.Read
    };

    db.Entry(model.SelectedTeam).Collection
(team => team.Members).Load();

    return View("Main", model);
}

The List() action receives a teamId from the route parameter. Inside, we construct MasterDetailViewModel object as before. This time we set the DataEntryTarget to TeamMembers because we are interested in CRUD on TeamMembers table.

We also need to have all the TeamMember entities for the selected Team. There are multiple ways you can accomplish this task. In this example, we explicitly load the related entities using Load() method. The explicitly loaded TeamMember entities are used while rendering the detail table on the view file.

When you click on the Manage Member button from the detail grid, that TeamMember row is highlighted.

This happens in the Select() action as shown below:

[HttpPost]
public IActionResult Select(int teamId, int memberId)
{
    MasterDetailViewModel model = 
new MasterDetailViewModel
    {
        Teams = db.Teams.ToList(),
        SelectedTeam = db.Teams.Find(teamId),
        SelectedTeamMember = db.TeamMembers.Find(memberId),
        DataEntryTarget = DataEntryTargets.TeamMembers,
        DataDisplayMode = DataDisplayModes.Read
    };

    db.Entry(model.SelectedTeam).Collection
(team => team.Members).Load();

    return View("Main", model);
}

The Select() action receives teamId as well as memberId from route parameters. Inside, we construct a MasterDetailViewModel object and set its SelectedTeamMember property to the selected memberId. Since it is a fresh request to the server, we again need to load the related entities using the Load() method.

When you click on the Insert Member button, a data entry area for adding a new TeamMember is shown below the detail grid like this:

Just like TeamsController, here also the insert operation is handled by two actions - InsertEntry() and InsertSave().

The InsertEntry() action is quite simple and straightforward and is shown below:

[HttpPost]
public IActionResult InsertEntry(int teamId)
{
    MasterDetailViewModel model = new 
MasterDetailViewModel
    {
        Teams = db.Teams.ToList(),
        SelectedTeam = db.Teams.Find(teamId),
        SelectedTeamMember = null,
        DataEntryTarget = DataEntryTargets.TeamMembers,
        DataDisplayMode = DataDisplayModes.Insert
    };
    db.Entry(model.SelectedTeam).Collection
(team => team.Members).Load();
    return View("Main", model);
}

Upon clicking the Save button, InsertSave() action is invoked. The InsertSave() action is shown next.

[HttpPost]
public IActionResult InsertSave(TeamMember member)
{
    db.TeamMembers.Add(member);
    db.SaveChanges();

    MasterDetailViewModel model = new 
MasterDetailViewModel
    {
        Teams = db.Teams.ToList(),
        SelectedTeam = db.Teams.Find(member.TeamID),
        SelectedTeamMember = db.TeamMembers.Find
(member.TeamMemberID),
        DataEntryTarget = DataEntryTargets.TeamMembers,
        DataDisplayMode = DataDisplayModes.Read
    };
    db.Entry(model.SelectedTeam).Collection
(team => team.Members).Load();
    return View("Main", model);
}

Here, we add the newTeamMember to the TeamMembers DbSet and t hen call SaveChanges(). We can do this because we have added two DbSet properties to our AppDbContext - Teams and TeamMembers. However, there is an alternate way to accomplish the same task. Let's see how that can be done.

Team t = db.Teams.Find(member.TeamID);
db.Entry(t).Collection
(team => team.Members).Load();
t.Members.Add(member);
db.SaveChanges();

In this case, we didn't use TeamMembers DbSet at all. We first get the Team whose member is being added. Then we add a new TeamMember to the Members collection (recollect that Members is a navigation property) and then call SaveChanges(). In this case EF Core will take care of setting the TeamID foreign key for the new TeamMember and will add it correctly to the TeamMembers table.

When you click on the Manage Member button in the detail grid, that TeamMember entry is shown for editing:

Clicking on the Edit, Delete, and Cancel buttons will trigger UpdateEntry(), Delete(), and CancelSelection() actions of the TeamMembersController. These actions are discussed below.

The UpdateEntry() action changes the data entry mode to Update and the TeamMember is shown for editing as shown below:

The TeamID and Team Member ID textboxes are marked as readonly because they are keys and can't be edited. The UpdateEntry() action responsible for this display is shown below:

[HttpPost]
public IActionResult UpdateEntry(int teamId, 
int memberId)
{
    MasterDetailViewModel model = 
new MasterDetailViewModel
    {
        Teams = db.Teams.ToList(),
        SelectedTeam = db.Teams.Find(teamId),
        SelectedTeamMember = db.TeamMembers.Find(memberId),
        DataEntryTarget = DataEntryTargets.TeamMembers,
        DataDisplayMode = DataDisplayModes.Update
    };
    db.Entry(model.SelectedTeam).Collection
(team => team.Members).Load();
    return View("Main", model);
}

As you can see, we set the DataDisplayMode to Update.

Clicking on the Save button triggers the UpdateSave() action:

[HttpPost]
public IActionResult UpdateSave(TeamMember member)
{
    db.TeamMembers.Update(member);
    db.SaveChanges();

    MasterDetailViewModel model = 
new MasterDetailViewModel
    {
        Teams = db.Teams.ToList(),
        SelectedTeam = db.Teams.Find(member.TeamID),
        SelectedTeamMember = db.TeamMembers.Find
(member.TeamMemberID),
        DataEntryTarget = DataEntryTargets.TeamMembers,
        DataDisplayMode = DataDisplayModes.Read
    };

    db.Entry(model.SelectedTeam).Collection
(team => team.Members).Load();

    return View("Main", model);
}

Clicking on the Cancel button in the Update mode triggers the CancelEntry() action.

[HttpPost]
public IActionResult CancelEntry(int teamId)
{
    MasterDetailViewModel model = new 
MasterDetailViewModel
    {
        Teams = db.Teams.ToList(),
        SelectedTeam = db.Teams.Find(teamId),
        SelectedTeamMember = null,
        DataEntryTarget = DataEntryTargets.
TeamMembers,
        DataDisplayMode = DataDisplayModes.Read
    };

    db.Entry(model.SelectedTeam).Collection
(team => team.Members).Load();

    return View("Main", model);
}

If you click on the Cancel button when a TeamMember is shown in Read mode, the CancelSelection() action is invoked. The CancelSelection() action removes the selection of the TeamMember row and also hides the TeamMember entry area.

[HttpPost]
public IActionResult CancelSelection(int teamId)
{
    MasterDetailViewModel model = new 
MasterDetailViewModel
    {
        Teams = db.Teams.ToList(),
        SelectedTeam = db.Teams.Find(teamId),
        SelectedTeamMember = null,
        DataEntryTarget = DataEntryTargets.
TeamMembers,
        DataDisplayMode = DataDisplayModes.Read
    };

    db.Entry(model.SelectedTeam).Collection
(team => team.Members).Load();

    return View("Main", model);
}

Finally, clicking the Delete button triggers the Delete() action as shown below:

[HttpPost]
public IActionResult Delete(int teamId, 
int memberId)
{
    TeamMember member = db.TeamMembers.
Find(memberId);
    db.TeamMembers.Remove(member);
    db.SaveChanges();

    MasterDetailViewModel model = new 
MasterDetailViewModel
    {
        Teams = db.Teams.ToList(),
        SelectedTeam = db.Teams.Find(teamId),
        SelectedTeamMember = null,
        DataEntryTarget = DataEntryTargets.
TeamMembers,
        DataDisplayMode = DataDisplayModes.Read
    };

    db.Entry(model.SelectedTeam).Collection
(team => team.Members).Load();

    return View("Main", model);
}

Here, you first fond a TeamMember to be deleted, remove it from the TeamMembers DbSet and then call SaveChanges(). Once a TeamMember is deleted, SelectedTeamMember property is set to null because the record has been deleted.

Just like the insert, you can also use an alternate technique for deleting a TeamMember. You can use the parent Team's Members collection to delete a TeamMember. And the SaveChanges() will do the needful. This alternate way is shown below:

 Team t = db.Teams.Find(teamId);
db.Entry(t).Collection(team => 
team.Members).Load();
TeamMember tm = t.Members.Find
(i => i.TeamMemberID == memberId);
t.Members.Remove(tm);
db.SaveChanges();

In the above code, we find the TeamMember from Members collection and then remove it using Remove() method. Then SaveChanges() removes it from the database.

This completes all the actions of the TeamMembersController. In the next part of this series we will focus on the views and partials that make the UI of the application.

That's it for now! Keep coding!!


Bipin Joshi is an independent software consultant, trainer, author, yoga mentor, and meditation teacher. He has been programming, meditating, and teaching for 24+ years. He conducts instructor-led online training courses in ASP.NET family of technologies for individuals and small groups. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get connected : Facebook  Twitter  LinkedIn  YouTube

Posted On : 22 March 2021


Tags : ASP.NET ASP.NET Core Data Access SQL Server MVC C# Visual Studio


Subscribe to our newsletter

Get monthly email updates about new articles, tutorials, code samples, and how-tos getting added to our knowledge base.

  

Receive Weekly Updates