简体   繁体   中英

Avoiding excessive control flow working with Id arrays in MVC

Multi-select lists in MVC don't seem to bind to complex models. Instead, they return an array of selected Id numbers.

I have such a control on a page, and I'm annoyed by the amount of conditional logic I've had to deploy to get it to work. The objects in question are a Staff object who can have TeamMember membership of none or more teams.

My objects are from entity framework. I added this to the Staff object:

public int[] SelectedTeamMembers
{
    get; set;
} 

I can now bind to this property in my View, and users can edit the multiselect list. On posting back the edit form, I have to do this (comments added for clarity):

//user.TeamMembers not bound, so get existing memberships
IEnumerable<TeamMember> existingTeamMembers = rep.TeamMembers_Get().Where(t => t.UserId == user.UserID);

//if array is empty, remove all team memberships & avoid null checks in else
if(user.SelectedTeamMembers == null)
{
    foreach(TeamMember tm in existingTeamMembers)
    {
        rep.TeamMembers_Remove(tm);
    }
}
else
{
    // if team members have been deleted, delete them
    foreach (TeamMember tm in existingTeamMembers)
    {
        if (!user.SelectedTeamMembers.Contains(tm.TeamId))
        {
            rep.TeamMembers_Remove(tm);
        }
    }

    // if there are new team memberships, add them
    foreach (int i in user.SelectedTeamMembers)
    {
        if (!existingTeamMembers.Select(t => t.TeamId).Contains(i))
        {
            TeamMember tm = new TeamMember { TeamId = i, UserId = user.UserID };
            rep.TeamMembers_Change(tm);
        }
    }
}

I can tidy this up a bit by farming out each bit to a function, of course, but it still feels like a sledgehammer to crack a nut.

Is there a neater way of achieving this?

You should evaluate the possibility of combining your for and foreach loops into a single loop as the first step of simplifying this code.

Also, you know how to use LINQ (as evidenced by you initial Where() statement) so simplify the null conditional action as well, using LINQ and some of its helper extensions:

//user.TeamMembers not bound, so get existing memberships
IEnumerable<TeamMember> existingTeamMembers = rep.TeamMembers_Get().Where(t => t.UserId == user.UserID);

//if array is empty, remove all team memberships & avoid null checks in else
if(user.SelectedTeamMembers == null)
{
    existingTeamMembers.ToList().ForEach(tm => rep.TeamMembers_Remove(tm));
}
else
{
    // if team members have been deleted, delete them
    existingTeamMembers.Where(tm => !user.SelectedTeamMembers.Contains(tm.TeamId)).ToList().ForEach(tm => rep.TeamMembers_Remove(tm));

    // if there are new team memberships, add them    
    user.SelectedTeamMembers.Except(existingTeamMembers.Select(t=> t.TeamId)).ToList().ForEach(i =>
    {
        TeamMember tm = new TeamMember { TeamId = i, UserId = user.UserID };
        rep.TeamMembers_Change(tm);
    });

}

While this has not decreased the conditional complexity (as in all the conditionals are still there) the syntax is a lot more readable.

You could do it this way...It relies on using the RemoveRange method.

Entity - I'm using my own for demo purposes

public class User
{

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid Id { get; set; }
    public String Name { get; set; }

}

Action

public ActionResult Action(Guid[] selectedTeamMembers)
{



    using (var ctx = new DatabaseContext())
    {

        //
        // Start by targeting all users!
        //
        var usersToRemove = ctx.Users.AsQueryable();

        //
        // if we have specified a selection then select the inverse.
        //
        if (selectedTeamMembers != null)
        {
            usersToRemove = usersToRemove.Where(x => !selectedTeamMembers.Contains(x.Id));
        }


        //
        // Use the Set Generic as this gives us access to the Remove Range method
        //
        ctx.Set<User>().RemoveRange(usersToRemove);


        ctx.SaveChanges();

    }



    return View();
}

Hope this helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM