0

I am trying to create a new record in my Game table using EF Core Code First. It shares one-to-many relationships with Genre, Developer, Publisher and Platform.

I am using a view model called GameCreateViewModel for the Game/Create View which holds a Game property as well as properties for select lists that correspond with each foreign key e.g. List<SelectListItem> Genres.

The problem I am having is when I attempt to create a new Game, it gives me this error:

Microsoft.EntityFrameworkCore.DbUpdateException
  HResult=0x80131500
  Message=An error occurred while updating the entries. See the inner exception for details.
  Source=Microsoft.EntityFrameworkCore.Relational
  StackTrace:
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IList`1 entries)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList`1 entriesToSave)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(DbContext _, Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
   at GameSource.Data.Repositories.GameRepository.Insert(Game game) in E:\Tom\source\repos\My Projects\GameSource\GameSource.Data\Repositories\GameRepository.cs:line 35
   at GameSource.Services.GameService.Insert(Game game) in E:\Tom\source\repos\My Projects\GameSource\GameSource.Services\GameService.cs:line 29
   at GameSource.Controllers.GamesController.Create(GameCreateViewModel viewModel) in E:\Tom\source\repos\My Projects\GameSource\GameSource\Controllers\GamesController.cs:line 88
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()

  This exception was originally thrown at this call stack:
    [External Code]

Inner Exception 1:
SqlException: Cannot insert explicit value for identity column in table 'Developer' when IDENTITY_INSERT is set to OFF.
Cannot insert explicit value for identity column in table 'Genre' when IDENTITY_INSERT is set to OFF.
Cannot insert explicit value for identity column in table 'Platform' when IDENTITY_INSERT is set to OFF.
Cannot insert explicit value for identity column in table 'Publisher' when IDENTITY_INSERT is set to OFF.

Game model class:

    public class Game
    {
        [Key]
        public int ID { get; set; }
        public string Name { get; set; }
        public Genre Genre { get; set; }
        public Developer Developer { get; set; }
        public Publisher Publisher { get; set; }
        public string Description { get; set; }
        public Platform Platform { get; set; }
    }

Genre model class:

    public class Genre
    {
        [Key]
        public int ID { get; set; }
        public string Name { get; set; }
    }

GameCreateViewModel:

    public class GameCreateViewModel
    {
        public Game Game { get; set; }
        public List<SelectListItem> Genres { get; set; }
        public List<SelectListItem> Developers { get; set; }
        public List<SelectListItem> Publishers { get; set; }
        public List<SelectListItem> Platforms { get; set; }
    }

Game/Create View - code of Genre select list only, the same format repeated for the other select lists:

@model GameSource.ViewModels.GameViewModel.GameCreateViewModel

        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Game.ID" />
            <div class="form-group">
                <label asp-for="Game.Name" class="control-label"></label>
                <input asp-for="Game.Name" class="form-control" />
                <span asp-validation-for="Game.Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Game.Genre" class="control-label"></label>
                <select asp-for="Game.Genre.ID" asp-items="@Model.Genres" class="form-control">
                    <option value="">Select a Genre/Genres</option>
                </select>
                <span asp-validation-for="Game.Genre" class="text-danger"></span>
            </div>

Game/Create Controller:

        [HttpGet]
        public IActionResult Create()
        {
            GameCreateViewModel viewModel = new GameCreateViewModel();
            viewModel.Game = new Game();
            viewModel.Genres = genreService.GetAll().Select(x => new SelectListItem()
            {
                Text = x.Name,
                Value = x.ID.ToString()
            }).ToList();
            viewModel.Developers = developerService.GetAll().Select(x => new SelectListItem()
            {
                Text = x.Name,
                Value = x.ID.ToString()
            }).ToList();
            viewModel.Publishers = publisherService.GetAll().Select(x => new SelectListItem()
            {
                Text = x.Name,
                Value = x.ID.ToString()
            }).ToList();
            viewModel.Platforms = platformService.GetAll().Select(x => new SelectListItem()
            {
                Text = x.Name,
                Value = x.ID.ToString()
            }).ToList();

            return View(viewModel);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Create(GameCreateViewModel viewModel)
        {
            Game game = new Game
            {
                ID = viewModel.Game.ID,
                Name = viewModel.Game.Name,
                Genre = viewModel.Game.Genre,
                Developer = viewModel.Game.Developer,
                Publisher = viewModel.Game.Publisher,
                Description = viewModel.Game.Description,
                Platform = viewModel.Game.Platform
            };
            gameService.Insert(game);
            return RedirectToAction("Index");
        }

It seems like it is trying to insert a new entry for the foreign keys as well, even though I am simply wanting to use existing IDs for the new Game entry. E.g. The new Game has a GenreID of 1, so it should be referring to the existing Genre entry with an ID of 1.

Any insight into this is very much appreciated. Also, please let me know if you need to see the service and repo methods too. Thanks for your time.

tomtomdam
  • 67
  • 2
  • 9
  • 1
    The `ID` property in the `Game` object is marked as `Key` so you can't insert the `ID` explicitly its auto incremented by the database. also always name your id properties like: `GameID` , `GenreID` so ef core can setup foreign keys properly when one entity depend on another. – HMZ Jul 12 '20 at 21:14
  • You just try to insert as nested table .kindly disply the table structure of game? – LDS Jul 12 '20 at 21:19
  • @HMZ EF Core has mapped out table correctly as far as I am aware. It shouldn't matter if it has the `Key` attribute or not as EF Core already knows that the ID property is the Primary Key for that class. This is where I found this out: https://www.learnentityframeworkcore.com/conventions#primary-key Here is a screenshot of the Design of the `Game` table: https://i.gyazo.com/ce3e8e52c5bdf7a56c9c12f092cf0964.png – tomtomdam Jul 12 '20 at 21:36
  • @LDS Here is a screenshot of the Design of the `Game` table: https://i.gyazo.com/ce3e8e52c5bdf7a56c9c12f092cf0964.png – tomtomdam Jul 12 '20 at 21:36
  • @tomtomdam even if it's mapped correctly you cant insert values in an identity column notice that you have 4 errors corresponding to each entity try mapping the entites with the view model withot the ids – HMZ Jul 12 '20 at 22:05
  • @tomtomdam if for some reason you want to insert ids excplicitly then see [this](https://stackoverflow.com/a/59716252/6843077) you have to configure the db to be able to do that. but personally i don't see the need for this – HMZ Jul 12 '20 at 22:13

3 Answers3

1

I ended up adding the id properties for the foreign keys in my Game class as well as in my GameCreateViewModel, and re-updated my database. It allowed me to create a new Game properly, for example when assigning a Genre to the new Game in the controller, I was able to return a Genre based on the view model's Game.GenreID property.

Example with Genre - the same idea applies for other foreign keys:

GameCreateViewModel:

    public class GameUpdateViewModel
    {
        public Game Game { get; set; }
        public int GenreID { get; set; }
        public List<SelectListItem> Genres { get; set; }
    

Game/Create controller method:

Game game = new Game 
{    
ID = viewModel.Game.ID, 
Name = viewModel.Game.Name, 
Description = viewModel.Game.Description, 
GenreID = viewModel.Game.GenreID

And in the view - in the select list it's using Game.GenreID instead:

<select asp-for="Game.GenreID" asp-items="@Model.Genres" class="form-control"> 
<option value="">Select a Genre/Genres</option> 
</select> 

The idea is to have the foreign key properties present, otherwise I'm not able to assign an existing foreign key ID to the new Game.

tomtomdam
  • 67
  • 2
  • 9
0

The representtion of your Game entity should be something like this:

public class Game
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public int GenreId { get; set; }
    public int DeveloperId { get; set; }
    public int PublisherId { get; set; }
    public int PlatformId { get; set; }

    public Genre Genre { get; set; }
    public Developer Developer { get; set; }
    public Publisher Publisher { get; set; }        
    public Platform Platform { get; set; }
}

Since EF refers to your One side entity across the foreign key, you should configure it in your context.

        builder.HasKey(g => g.ID);
        builder.Property(g => g.Name);
        builder.Property(g => g.Description);

        builder.Property(g => g.GenreId).HasColumnName("GenreID");

        builder.HasOne(g => g.Genre)
            .WithMany(h => h.Games)
            .HasForeignKey(g => g.GenreId)
            .OnDelete(DeleteBehavior.ClientSetNull)
            .HasConstraintName("FK_Games_Genre");  // Repeat the last two lines for all your one side entity relationships

Now you would put just the foreign key when need to add a Game entity. For instance:

new Game({
   Name = "GameName"
   .
   .
   GenreId = 1
   .
   .
});

Just add game to the context and save. EF will map your entities relationships in the correct way.

Notes: Please considere that your EF could be different.

If you are new to EF Core I suggest to use EF Database-First approach once you did the DB schema and this will bring to the project all the config that the context needs.

-2

You have to change around the code

public class Game
{
        [Key]
        public int ID { get; set; }
        public string Name { get; set; }
        public int GenreID { get; set; }
        public int DeveloperID { get; set; }
        public int PublisherID { get; set; }
        public string Description { get; set; }
        public int PlatformID { get; set; }
}


 <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Game.ID" />
            <div class="form-group">
                <label asp-for="@Model.GameName" class="control-label"></label>
                <input asp-for="@Model.GameName" class="form-control" />
                <span asp-validation-for="@Model.GameName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label>Platform</label>
         <select asp-for="@Model.PlatformID"
            asp-items="@(new SelectList(Model.Platforms,"Id","Name"))">
        <option>Please select one</option>
    </select>

                
  </div>
 <div class="form-group">
                <label>Publisher</label>
         <select asp-for="@Model.PublisherID"
            asp-items="@(new SelectList(Model.Publishers,"Id","Name"))">
        <option>Please select one</option>
    </select>

                
  </div>
<div class="form-group">
                <label>Publisher</label>
         <select asp-for="@Model.PublisherID"
            asp-items="@(new SelectList(Model.Publishers,"Id","Name"))">
        <option>Please select one</option>
    </select>

                
  </div>
 <div class="form-group">
                <label>Developer</label>
         <select asp-for="@Model.DeveloperID"
            asp-items="@(new SelectList(Model.Developers,"Id","Name"))">
        <option>Please select one</option>
    </select>
  </div>

public class GameCreateViewModel
{
    public String GameName { get; set; }
    public int GenreID { get; set; }
    public int DeveloperID { get; set; }
    public int PublisherID { get; set; }
    public int PlatformID { get; set; }

    public Game Game { get; set; }
    public List<Genre> Genres { get; set; }
    public List<Developer> Developers { get; set; }
    public List<Publisher> Publishers { get; set; }
    public List<Platform> Platforms { get; set; }
}

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(GameCreateViewModel viewModel)
{
    Game game = new Game
            {
                Name = viewModel.GameName,
                GenreID = viewModel.GenreID,
                DeveloperID = viewModel.DeveloperID,
                Publisher = viewModel.PublisherID,
                Description = viewModel.Game.Description,
                Platform = viewModel.PlatformID
            };
    gameService.Insert(game);

    return RedirectToAction("Index");
}

public class Developer
{
        [Key]
        public int ID { get; set; }
        public string Name { get; set; }
}

public class Publisher
{
        [Key]
        public int ID { get; set; }
        public string Name { get; set; }
}

public class Platform
{
        [Key]
        public int ID { get; set; }
        public string Name { get; set; }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
LDS
  • 354
  • 3
  • 9