First of all, i just wanna say i'm a total newbie with asp .net core mvc, and entity framework, soo i don't really know if i'm doing this right or in the best way.
I'm trying to do a simple program where you can register games, every game got some information about them, a console associated with them, and the thing that i can't solve, a association with one or more genres.
But to start simple, i'm only trying to register a game with only one genre (and i don't know how i gonna display multiple genres for selection yet).
The game, and console information have no problem registering in the db, but when i try to register a game with a genre, i get this error:
An unhandled exception occurred while processing the request.
InvalidOperationException: The value of 'GameGameGenre.GameId' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.PrepareToSave()
These are the relevant MODELS for this problem (i think at least)
This is the Game class:
namespace ProjectC01.Models
{
public class Game
{
public int GameId { get; set; }
public string GameName { get; set; }
public string GameDeveloper { get; set; }
public string GamePublisher { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime GameRelease { get; set; }
public string GameCover { get; set; }
public string ImageLocation { get; set; }
public VideoGameConsole GameConsole { get; set; }
public int VideoGameConsoleId { get; set; }
public ICollection<GameGenre> GameGenres { get; set; } = new List<GameGenre>();
public ICollection<GameGameGenre> GameGameGenres { get; set; } = new List<GameGameGenre>();
public Game() { }
public Game(int gameId, string gameName, string gameDeveloper,
string gamePublisher, DateTime gameRelease, string gameCover, VideoGameConsole gameConsole, string imageLocation)
{
GameId = gameId;
GameName = gameName;
GameDeveloper = gameDeveloper;
GamePublisher = gamePublisher;
GameRelease = gameRelease;
GameCover = gameCover;
GameConsole = gameConsole;
ImageLocation = imageLocation;
}
}
}
This is the Game Genre Class:
namespace ProjectC01.Models
{
public class GameGenre
{
[Key]
public int GenreId { get; set; }
public string GenreName { get; set; }
public ICollection<Game> Games { get; set; } = new List<Game>();
public ICollection<GameGameGenre> GameGameGenres { get; set; } = new List<GameGameGenre>();
public GameGenre() { }
public GameGenre(int genreId, string genreName)
{
GenreId = genreId;
GenreName = genreName;
}
}
}
And this is the class that does the join between the games, and the genres
namespace ProjectC01.Models
{
public class GameGameGenre
{
public Game Game { get; set; }
public int GameId { get; set; }
public GameGenre GameGenre { get; set; }
public int GameGenreId { get; set; }
public GameGameGenre() { }
public GameGameGenre(Game game, int gameId, GameGenre gameGenre, int gameGenreId)
{
Game = game;
GameId = gameId;
GameGenre = gameGenre;
GameGenreId = gameGenreId;
}
}
}
This is the the CONTROLLER for the Games
namespace ProjectC01.Controllers
{
public class GameController : Controller
{
private readonly GameService _gameService;
private readonly VideoGameConsoleService _videoGameConsoleService;
private readonly GameGenreService _gameGenreService;
private readonly ProjectC01Context _context;
private readonly IWebHostEnvironment _environment;
public GameController(GameService gameService, VideoGameConsoleService videoGameConsoleService,
GameGenreService gameGenreService, ProjectC01Context context, IWebHostEnvironment environment)
{
_gameService = gameService;
_videoGameConsoleService = videoGameConsoleService;
_gameGenreService = gameGenreService;
_context = context;
_environment = environment;
}
public IActionResult Index()
{
var list = _gameService.FindAll();
return View(list);
}
public async Task<IActionResult> Create()
{
var consoles = await _videoGameConsoleService.FindAllAsync();
var genres = await _gameGenreService.FindAllAsync();
var viewModel = new GameFormViewModel { Consoles = consoles, GameGenres = genres };
return View(viewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(GameFormViewModel model, IFormFile imageUpload)
{
if (imageUpload == null || imageUpload.Length == 0)
{
return Content("Nenhuma imagem selecionada");
}
var cover = Path.Combine(_environment.WebRootPath, "Images\\GameCovers", imageUpload.FileName);
using (FileStream stream = new FileStream(cover, FileMode.Create))
{
await imageUpload.CopyToAsync(stream);
stream.Close();
}
string imgPath = "\\Images\\GameCovers\\" + imageUpload.FileName;
model.Game.GameCover = imageUpload.FileName;
if (model != null)
{
var game = new Game
{
GameName = model.Game.GameName,
GameDeveloper = model.Game.GameDeveloper,
GamePublisher = model.Game.GamePublisher,
GameRelease = model.Game.GameRelease,
GameCover = model.Game.GameCover,
VideoGameConsoleId = model.Game.VideoGameConsoleId,
ImageLocation = imgPath,
};
var gameGameGenre = new GameGameGenre
{
GameId = model.GameGameGenre.GameId,
GameGenreId = model.GameGameGenre.GameGenreId,
};
_context.Add(game);
_context.Add(gameGameGenre);
await _context.SaveChangesAsync(); //The error occurs after this line is executed
}
return RedirectToAction("Index");
}
}
}
This is the CREATE VIEW for the Games
@model ProjectC01.Models.ViewModels.GameFormViewModel
@{
ViewData["Title"] = "Cadastrar";
}
<h1>@ViewData["Title"]</h1>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create" method="post" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Game.GameName" class="control-label"></label>
<input asp-for="Game.GameName" class="form-control" />
<span asp-validation-for="Game.GameName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Game.GameDeveloper" class="control-label"></label>
<input asp-for="Game.GameDeveloper" class="form-control" />
<span asp-validation-for="Game.GameDeveloper" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Game.GamePublisher" class="control-label"></label>
<input asp-for="Game.GamePublisher" class="form-control" />
<span asp-validation-for="Game.GamePublisher" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Game.GameRelease" class="control-label"></label>
<input asp-for="Game.GameRelease" class="form-control" />
<span asp-validation-for="Game.GameRelease" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Game.GameCover" class="control-label"></label>
<input asp-for="Game.GameCover"
type="file" name="imageUpload" accept="image/*" class="form-control" />
<span asp-validation-for="Game.GameCover" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Game.VideoGameConsoleId" class="control-label"></label>
<select asp-for="Game.VideoGameConsoleId" class="form-select"
asp-items="@(new SelectList(Model.Consoles,"ConsoleId","ConsoleName"))"></select>
</div>
<div class="form-group">
<label asp-for="GameGameGenre.GameGenreId" class="control-label"></label>
<select asp-for="GameGameGenre.GameGenreId" class="form-select"
asp-items="@(new SelectList(Model.GameGenres,"GenreId","GenreName"))"></select>
</div>
<hr />
<div class="form-group">
<input type="submit" value="Cadastrar" class="btn btn-primary" /> |
<a asp-action="Index" class="btn btn-secondary">Retornar a Lista</a>
</div>
</form>
</div>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
This is the VIEWMODEL used on the CREATE VIEW above
namespace ProjectC01.Models.ViewModels
{
public class GameFormViewModel
{
public Game Game { get; set; }
public GameGenre GameGenre { get; set; }
public GameGameGenre GameGameGenre { get; set; }
public IFormFile ImageUpload { get; set; }
public ICollection<VideoGameConsole> Consoles { get; set; }
public ICollection<GameGenre> GameGenres { get; set; }
}
}
And this is the CONTEXT for the application
namespace ProjectC01.Data
{
public class ProjectC01Context : DbContext
{
public ProjectC01Context(DbContextOptions<ProjectC01Context> options)
: base(options) { }
public DbSet<VideoGameConsole>? VideoGameConsole { get; set; }
public DbSet<GameGenre>? GameGenre { get; set; }
public DbSet<Game>? Game { get; set; }
public DbSet<GameGameGenre>? GameGameGenre { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Game>()
.HasMany(e => e.GameGenres)
.WithMany(e => e.Games)
.UsingEntity<GameGameGenre>(
r => r.HasOne<GameGenre>(e => e.GameGenre).WithMany(e => e.GameGameGenres),
l => l.HasOne<Game>(e => e.Game).WithMany(e => e.GameGameGenres));
modelBuilder.Entity<GameGenre>()
.HasMany(e => e.Games)
.WithMany(e => e.GameGenres)
.UsingEntity<GameGameGenre>(
r => r.HasOne<Game>(e => e.Game).WithMany(e => e.GameGameGenres),
l => l.HasOne<GameGenre>(e => e.GameGenre).WithMany(e => e.GameGameGenres));
//I don't really know if it was necessary to do this for both classes
}
}
}
And i think that's all the things connected in some way to the problem, probably i did something wrong in the CREATE view or in the CREATE POST action on the CONTROLLER, at least that's the conclusion i got to.
I searched a lot for some post that maybe had the same problem, but i really couldn't find any, sorry if maybe i'm not clear enough, but as i said, i'm just getting started on this asp .net MVC/EF thing.
Thanks to this issue on GitHub i found out the solution for this problem.
Turns out the problem was because the code never sets a id value for the GameId, since its a auto increment PK on the database, because of this when the controller tried to add a GameId to the GameGameGenre table, it was aways 0, causing the error above.
I solved it by first including the game on the database (as i was already doing), and them doing a LINQ search on the db for the game that just got registered, them getting its id, and using that for the GameGameGenre.GameId insertion.
public async Task CreateNewGGS(GameFormViewModel model)
{
if (model != null)
{
var gameId = _context.Game.SingleOrDefault(m =>
m.GameName == model.Game.GameName &&
m.GameDeveloper == model.Game.GameDeveloper &&
m.GamePublisher == model.Game.GamePublisher &&
m.GameRelease == model.Game.GameRelease &&
m.GameCover == model.Game.GameCover &&
m.VideoGameConsoleId == model.Game.VideoGameConsoleId
).GameId;
var ggs = new GameGameGenre
{
GameId = gameId,
GameGenreId = model.GameGameGenre.GameGenreId,
};
await InsertAsync(ggs);
}
}