I can't seem to successfully update a many-to-many graph in MVC2 using EF4. I figured the simplest thing to do would be to Clear() the entire graph, call SaveChanges(), then rebuild the graph calling SaveChanges() again at the end, but it's not working. All my other properties ARE working, however. First, my Action Methods:
public ActionResult EditReview(int id)
{
var game = _gameRepository.GetGame(id);
var genres = _gameRepository.AllGenres();
var platforms = _gameRepository.AllPlatforms();
var model = new AdminGameViewModel { GameData = game, AllGenres = genres, AllPlatforms = platforms };
return View(model);
}
//
// POST: /Admin/EditReview/5
[HttpPost]
public ActionResult EditReview([Bind(Prefix="GameData")]Game existingGame, int[] PlatformIDs)
{
try
{
_gameRepository.ValidateGame(existingGame, PlatformIDs);
}
catch(RulesException ex)
{
ex.CopyTo(ModelState);
ex.CopyTo(ModelState, "GameData");
}
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
else
{
var genres = _gameRepository.AllGenres();
var platforms = _gameRepository.AllPlatforms();
var model = new AdminGameViewModel { GameData = existingGame, AllGenres = genres, AllPlatforms = platforms };
return View(model);
}
}
The repo itself (ValidateGame and SaveGame are the relevant methods):
namespace HandiGamer.Domain.Concrete
{
public class HGGameRepository : IGameRepository
{
private HGEntities _siteDB = new HGEntities();
public List<Game> Games
{
get { return _siteDB.Games.ToList(); }
}
public void ValidateGame(Game game, int[] PlatformIDs)
{
var errors = new RulesException<Game>();
if (string.IsNullOrEmpty(game.GameTitle))
{
errors.ErrorFor(x => x.GameTitle, "A game must have a title");
}
if (string.IsNullOrEmpty(game.ReviewText))
{
errors.ErrorFor(x => x.ReviewText, "A review must be written");
}
if (game.ReviewScore <= 0 || game.ReviewScore > 5)
{
errors.ErrorFor(x 开发者_StackOverflow中文版=> x.ReviewScore, "A game must have a review score, and the score must be between 1 and 5");
}
if (string.IsNullOrEmpty(game.Pros))
{
errors.ErrorFor(x => x.Pros, "Each game review must have a list of pros");
}
if (string.IsNullOrEmpty(game.Cons))
{
errors.ErrorFor(x => x.Cons, "Each game review must have a list of cons");
}
if (PlatformIDs == null || PlatformIDs.Length == 0)
{
errors.ErrorForModel("A game must belong to at least one platform");
}
if (game.GenreID == 0)
{
errors.ErrorFor(x => x.GenreID, "A game must be associated with a genre");
}
if (errors.Errors.Any())
{
throw errors;
}
else
{
SaveGame(game, PlatformIDs);
}
}
public void SaveGame(Game game, int[] PlatformIDs)
{
_siteDB.Games.Attach(game);
if (game.GameID > 0)
{
_siteDB.ObjectStateManager.ChangeObjectState(game, System.Data.EntityState.Modified);
game.Platforms.Clear();
}
else
{
_siteDB.ObjectStateManager.ChangeObjectState(game, System.Data.EntityState.Added);
}
foreach (int id in PlatformIDs)
{
Platform plat = _siteDB.Platforms.Single(pl => pl.PlatformID == id);
game.Platforms.Add(plat);
}
game.LastModified = DateTime.Now;
_siteDB.SaveChanges();
}
public Game GetGame(int id)
{
return _siteDB.Games.Include("Genre").Include("Platforms").SingleOrDefault(g => g.GameID == id);
}
public IEnumerable<Game> GetGame(string title)
{
return _siteDB.Games.Include("Genre").Include("Platforms").Where(g => g.GameTitle.StartsWith(title)).AsEnumerable<Game>();
}
public List<Game> GetGamesByGenre(int id)
{
return _siteDB.Games.Where(g => g.GenreID == id).ToList();
}
public List<Game> GetGamesByGenre(string genre)
{
return _siteDB.Games.Where(g => g.Genre.Name == genre).ToList();
}
public List<Game> GetGamesByPlatform(int id)
{
return _siteDB.Games.Where(g => g.Platforms.Any(p => p.PlatformID == id)).ToList();
}
public List<Game> GetGamesByPlatform(string platform)
{
return _siteDB.Games.Where(g => g.Platforms.Any(p => p.Name == platform)).ToList();
}
public List<Genre> AllGenres()
{
return _siteDB.Genres.OrderBy(g => g.Name).ToList();
}
public List<Platform> AllPlatforms()
{
return _siteDB.Platforms.OrderBy(p => p.PlatformID).ToList();
}
}
}
I'm stumped.
Kevin, oh this is a little complex and forces you back to EFv1 patterns because with M:M you don't have foreign keys to lean on and you are stuck with having objects.
WHen you add a game, you do want the relationship (i.e. a row in the join table) to be added but you don't want the platform to be added since it's just a reference.
I haven't actually done this but I think it would be easier if you could break it apart and then rebuild the platforms collection once the game is attached and marked added. Otherwise if you add the whole graph, everything gets marked added.
Problem with EF is that if you attach game you will get the related stuff attached too. There might be a cleaner pattern but my thought is to detach the platforms from the game, attach the game to the context, mark it as added. Then I would attach the platforms to the context. They will be "unchanged". Then add them to the games.platform collection. The platforms will still be unchanged but the : will be understood.
You may have tried that. I'd have to do it myself and watch the entity state of everything as I go along to see for sure what's happening. The key is that EF needs to track that a relationship has been added and that's new (and will result in a row in the join table being added) but understand that Platforms are not new.
hth julie
精彩评论