简体   繁体   中英

EF Core - Unable to insert new entry that has a foreign key

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 eg 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. Eg 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.

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.

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.

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; }
}

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