'Why is Entity Framework trying to update my child entity?
I have a Blazor server project where we have a Game
and it has related child entities such as Venue
, Season
and GameType
.
public Game()
{
Results = new HashSet<Result>();
}
public int GameId { get; set; }
public int SeasonId { get; set; }
public int VenueId { get; set; }
public int GameTypeId { get; set; }
[Required]
public DateTime GameDate { get; set; } = DateTime.Today;
public int BuyIn { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
public virtual Season Season { get; set; }
public virtual Venue Venue { get; set; }
public virtual GameType GameType { get; set; }
public virtual ICollection<Result> Results { get; set; }
I have a repo that is injected into my page to handle the update:
...
public async Task UpdateGame(Game game)
{
using (var context = _suttonPokerDbContext.CreateDbContext())
{
context.Games.Update(game);
await context.SaveChangesAsync();
}
}
...
@page "/Settings/Games/EditGame/{GameId:int}"
@using SuttonPokerBlazor.Components
@using SuttonPokerBlazor.Models
@using SuttonPokerBlazor.Repositories.Interfaces
@inject IGameRepository gamesRepository
@inject NavigationManager NavigationManager
@inject ISeasonRepository seasonsRepository
@inject IVenueRepository venueRepository
@inject IGameTypeRepository gameTypeRepository
@if (Game != null)
{
<h3>Add new game</h3>
<EditForm Model="@Game" OnValidSubmit="Save">
<DataAnnotationsValidator />
<div class="mb-3">
<label for="Season" class="form-label">Season</label>
<div class="col-md-4">
<InputSelect @bind-Value="@Game.SeasonId" class="form-control">
@foreach (Season season in seasons)
{
<option value="@season.SeasonId">@season.SeasonDescription</option>
}
</InputSelect>
</div>
<ValidationMessage For="@(() => Game.SeasonId)" />
</div>
<div class="mb-3">
<label for="Season" class="form-label">Venue</label>
<div class="col-md-4">
<InputSelect @bind-Value="@Game.VenueId" class="form-control">
@foreach (Venue venue in venues)
{
<option value="@venue.VenueId">@venue.VenueName</option>
}
</InputSelect>
</div>
<ValidationMessage For="@(() => Game.VenueId)" />
</div>
<div class="mb-3">
<label for="Season" class="form-label">Game Type</label>
<div class="col-md-4">
<InputSelect @bind-Value="@Game.GameTypeId" class="form-control">
@foreach (GameType gameType in gameTypes)
{
<option value="@gameType.GameTypeId">@gameType.GameTypeDescription</option>
}
</InputSelect>
</div>
<ValidationMessage For="@(() => Game.GameTypeId)" />
</div>
<div class="mb-3">
<label for="GameDate" class="form-label">Game Date</label>
<div class="col-md-4">
<InputDate class="form-control" @bind-Value="Game.GameDate" />
</div>
<ValidationMessage For="@(() => Game.GameDate)" />
</div>
<div class="mb-3">
<label for="BuyIn" class="form-label">Buy In</label>
<div class="col-md-4">
<InputNumber class="form-control" @bind-Value="Game.BuyIn" />
</div>
<ValidationMessage For="@(() => Game.BuyIn)" />
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">Back</button>
<button type="button" class="btn btn-danger" @onclick="Delete">Delete</button>
</div>
</EditForm>
<Confirm @ref="DeleteConfirmation" ConfirmationChanged="ConfirmDelete"
ConfirmationMessage=@($"Are you sure you want to delete {Game.GameDate} - {Game.GameType.GameTypeDescription}?")>
</Confirm>
}
else
{
<h3>Not found</h3>
}
@code {
[Parameter]
public int GameId { get; set; }
public Game Game { get; set; }
ConfirmBase DeleteConfirmation { get; set; }
List<Season> seasons { get; set; }
List<Venue> venues { get; set; }
List<GameType> gameTypes { get; set; }
protected override async Task OnInitializedAsync()
{
Game = await gamesRepository.GetGame(GameId);
seasons = await seasonsRepository.GetSeasons();
venues = await venueRepository.GetVenues();
gameTypes = await gameTypeRepository.GetGameTypes();
}
private async Task Save()
{
await gamesRepository.UpdateGame(Game);
}
private void Cancel()
{
NavigationManager.NavigateTo("/Settings/Games/");
}
private void Delete()
{
DeleteConfirmation.Show();
}
private async void ConfirmDelete(bool deleteConfirmed)
{
if (deleteConfirmed)
{
await gamesRepository.DeleteGame(Game);
NavigationManager.NavigateTo("/Settings/Games/");
}
}
}
However, when the Game
entity is updated it sets the Id of Season
, GameType
and/or Venue
back to what they were before the update occurred.
For example:
Pre SaveChangesAsync()
:
Post SaveChangesAsync()
:
SQL produced:
In the SQL above, my assumption was that I would only see a update request to my Game entity. Why is it making updates to the other related tables and then why is it reverting anything that was changed back to what it was pre Save?
Any other changes to things like dates or strings is persisted as expected. It just seems that where I've used a drop down <InputSelect>
this effect is taking place.
Update:
This is an updated version of my repo that seems to work but Caius Jard was asking why I was doing what had fixed my issue. I'm happy to correct something if what I've done is incorrect for some reason:
public class GameRepository : IGameRepository
{
private readonly IDbContextFactory<SuttonPokerDbContext> _suttonPokerDbContext;
public GameRepository(IDbContextFactory<SuttonPokerDbContext> suttonPokerDbContext)
{
_suttonPokerDbContext = suttonPokerDbContext;
}
public async Task<Game> GetGame(int GameId)
{
using (var context = _suttonPokerDbContext.CreateDbContext())
{
return await context.Games.Include(q => q.Results).Include(q => q.GameType).Include(q => q.Venue).Include(q => q.Season).FirstOrDefaultAsync(q => q.GameId == GameId); ;
}
}
public async Task<List<Game>> GetGames()
{
using (var context = _suttonPokerDbContext.CreateDbContext())
{
return await context.Games.Include(q => q.GameType).Include(q => q.Venue).Include(q => q.Season).OrderByDescending(q => q.GameDate).ToListAsync();
}
}
public async Task<Game> AddGame(Game game)
{
using (var context = _suttonPokerDbContext.CreateDbContext())
{
context.Games.Add(game);
await context.SaveChangesAsync();
return game;
}
}
public async Task<Game> UpdateGame(Game game)
{
using (var context = _suttonPokerDbContext.CreateDbContext())
{
// First we need to get the game from the database as we need to see if it's been modified already
Game dbGame = await context.Games.Include(q => q.Results).Include(q => q.GameType).Include(q => q.Venue).Include(q => q.Season).FirstOrDefaultAsync(q => q.GameId == game.GameId);
// Compare the byte arrays
if (!dbGame.RowVersion.SequenceEqual(game.RowVersion))
{
game = dbGame;
return game;
}
else
{
// We have to detach the dbGame version otherwise we get a conflict of tracked games.
context.Entry(dbGame).State = EntityState.Detached;
context.Entry(game).State = EntityState.Modified;
await context.SaveChangesAsync();
return game;
}
}
}
public async Task DeleteGame(Game game)
{
using (var context = _suttonPokerDbContext.CreateDbContext())
{
context.Remove(game);
await context.SaveChangesAsync();
}
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|