0

In my POST Edit function, I have my viewmodel that contain the game I want to update and list of platformIds that I want to add to the game.

Using this code, I was able to add platforms to my game but can't remove them. I put a breakpoint at the end and definitely see that viewModel.Game.Platforms have only what I selected but it is not updated in my game list.

If I add a few platforms and remove some at the same time. The new platforms get added but none are removed.

public ActionResult Edit(GameViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        List<Platform> platforms = new List<Platform>();
        foreach (var id in viewModel.PostedPlatforms.PlatformIds)
        {
            platforms.Add(db.Platforms.Find(Int32.Parse(id)));
        }
        db.Games.Attach(viewModel.Game);
        viewModel.Game.Platforms = platforms;
        db.Entry(viewModel.Game).State = EntityState.Modified;
        UpdateModel(viewModel.Game);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(viewModel.Game);
}

The model class is

public class Game
{
    public int GameId { get; set; }

    public string Title { get; set; }

    public List<Platform> Platforms { get; set; }
}

public class Platform
{
    public int PlatformId { get; set; }

    public string Name { get; set; }

    public List<Game> Games { get; set; }
}

Using ourmandave's suggestion, I got this code which while does change the platforms selection, it creates a new game entry every time which is inefficient and also increasing the id of the content which mess up bookmarks.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(GameViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        List<Platform> platforms = new List<Platform>();
        if(viewModel.PostedPlatforms != null)
        {
            foreach (var id in viewModel.PostedPlatforms.PlatformIds)
            {
                platforms.Add(db.Platforms.Find(Int32.Parse(id)));
            }
        }
        db.Games.Remove(db.Games.Find(viewModel.Game.PostId));
        db.SaveChanges();
        viewModel.Game.Platforms = platforms;
        db.Games.Add(viewModel.Game);

        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(viewModel.Game);
}
Danson
  • 415
  • 1
  • 7
  • 16
  • Where is your code which removes the items ? – Shyju Jan 23 '16 at 21:50
  • In my edit function, I create a new List from all the platforms that I selected and set the Game.Platforms to that one. The new list doesn't have some of the platforms in Game.Platforms. Shouldn't this mean that those platforms are no longer in Game.Platforms? – Danson Jan 23 '16 at 22:02
  • 1
    I think you have to call Remove or DeleteObject on them to mark them in the context so when you SaveChanges it will generate the Delete sql [per this answer](http://stackoverflow.com/a/17726414/3585500). See also [Entity states and SaveChanges](https://msdn.microsoft.com/en-us/data/jj592676) on this page. – ourmandave Jan 23 '16 at 23:33
  • I don't see a way to call that on just the list of platforms for my game. That seem rather awkward if I have to delete my game and re-add it every time I want to update it. – Danson Jan 25 '16 at 20:12

2 Answers2

1

You could try this...

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(GameViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        List<Platform> selectedPlatforms = viewModel.Select(pl => GetPlatformById(pl.Id)).ToList();
        var game = GetGameById(viewModel.Id);

        UpdateGamePlatforms(game, selectedPlatforms);

        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(viewModel.Game);
    }        



private Platform GetPlatformById(int platformId)
{
    return db.Platforms.First(pl => pl.Id == platformId);
}

private Game GetGameById(int gameId)
{
    return db.Games.First(g => g.Id == gameId);
}

private void UpdateGamePlatforms(Game game, IList<Platform> selectedPlatforms)
{
    var gamePlatforms = game.Platforms.ToList();

    foreach (var gamePlatform in gamePlatforms)
    {
        if (selectedPlatforms.Contains(gamePlatform) == false)
        {
            game.Platforms.Remove(gamePlatform);
        }
        else
        {
            selectedPlatforms.Remove(gamePlatform);
        }
    }

    game.Platforms.AddRange(selectedPlatforms);
}

UpdateGamePlatformswill remove platforms from the game which are no longer selected. It will leave the platforms which are still selected and it will also add new platforms to the game which have been selected.

TomJerrum
  • 584
  • 1
  • 5
  • 20
  • Thank you. That work perfectly. I only change the first part to `selectedPlatforms.AddRange(db.Platforms.Where(item => platformIds.Contains(item.PlatformId)).ToList());` since I have an array of id for selected platforms. – Danson Jan 26 '16 at 15:26
  • Upon further testing, I noticed your code would not take into account any changes to other properties of Game beside platforms. I try to use my viewModel.Game instead of creating a new Game object and with adding `db.Entry(viewModel.Game).State = EntityState.Modified;` I can update the other properties but not the platforms. Is there another way beside manually doing something like `game.Title = viewModel.Game.Title` for every properties? – Danson Jan 27 '16 at 05:10
  • That would be the way that I do it. You could pull it out into a separate method... `private void Map(Game game, GameViewModel viewModel)` Then in this method you could set all of the other properties on the Game from the GameViewModel. – TomJerrum Jan 27 '16 at 09:15
0

Solution: Using TomJerrum's solution, I can now edit the platform list properly. To update the rest of the properties, I have to map all the property of the game object I'm trying to edit to match the property of my viewModel. Thankfully, there is already a function for that so I only have to add db.Entry(game).CurrentValues.SetValues(viewModel.game);.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(GameViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        List<Platform> selectedPlatforms = new List<Platform>();

        if (viewModel.PostedPlatforms != null)
        {
            int[] platformIds = Array.ConvertAll(viewModel.PostedPlatforms.PlatformIds, p => Convert.ToInt32(p));

            selectedPlatforms.AddRange(db.Platforms.Where(item => platformIds.Contains(item.PlatformId)).ToList());
        }

        var game = GetGameById(viewModel.Id);

        UpdateGamePlatforms(game, selectedPlatforms);

        db.Entry(game).CurrentValues.SetValues(viewModel.game);

        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(viewModel.Review);
}

private Game GetGameById(int gameId)
{
    return db.Games.First(g => g.Id == gameId);
}

private void UpdateGamePlatforms(Game game, IList<Platform> selectedPlatforms)
{
    var gamePlatforms = game.Platforms.ToList();

    foreach (var gamePlatform in gamePlatforms)
    {
        if (selectedPlatforms.Contains(gamePlatform) == false)
        {
            game.Platforms.Remove(gamePlatform);
        }
        else
        {
            selectedPlatforms.Remove(gamePlatform);
        }
    }

    game.Platforms.AddRange(selectedPlatforms);
}
Danson
  • 415
  • 1
  • 7
  • 16