EF Core: dbcontext issues in blazor server app

I'm developing an app in Blazor server and I'm getting different errors when I try to perform some operations.

I have my solution structured in 3 projects

  • A backend Project with Repository - Service architecture and MySql DB
  • A Models in a class library to store my models
  • A Blazor Server frontend.
  • A xUnit test Project

I get the following error

System.InvalidOperationException: The instance of entity type 'Provincia' cannot be tracked because another instance with the same key value for {'IdProvincia'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values

This error appears when I try to persist Localidad entity in my Crear.razor, my dbcontext here has a lifetime service equal to Transient.

Other issue, is when I change the lifetime of the service for the DbContext, if I set it to Scoped (or remove it, which is the default) the CRUD operations are working, but If I do a refresh in the page (press F5) the following error shows up

InvalidOperationException: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see

Here's my code (the imports are omitted)

Localidad.cs

namespace CapacitacionModelos.Modelos { [Table("Localidad")] public class Localidad { [Column("IdLocalidad")] public int LocalidadId { get; set; } [Column("Descripcion")] [Required] public string Nombre { get; set; } [ForeignKey("IdProvincia")] public int IdProvincia { get; set; } public virtual Provincia Provincia { get; set; } [StringLength(50)] public string Abreviatura { get; set; } [Column("IdMPF")] public byte? IdMpf { get; set; } public Localidad() { } public Localidad(string Nombre, Provincia Provincia) { this.Nombre = Nombre; this.Provincia = Provincia; } } } 

Provincia.cs

namespace CapacitacionModelos.Modelos { [Table("Provincia")] public class Provincia { [Key] public int IdProvincia { get; set; } [Column("Descripcion")] [StringLength(80)] public string Nombre { get; set; } public Provincia() { } public Provincia(string Nombre) { this.Nombre = Nombre; } } } 

LocalidadService.cs

namespace CapacitacionesBE.Data { public interface ILocalidadService : IRepository<Localidad> { public Task<List<Localidad>> GetLocalidadesByProvincia(int idProvincia); public Task<List<Localidad>> GetLocalidadesWithProvincias(); } public class LocalidadService : Service<Localidad>, ILocalidadService { public LocalidadService(LengaContext context) : base(context) { } public async Task<List<Localidad>> GetLocalidadesByProvincia(int idProvincia) { return await _context .Localidades .Where(localidad => localidad.Provincia.IdProvincia == idProvincia) .ToListAsync(); } public async Task<List<Localidad>> GetLocalidadesWithProvincias() { return await _context .Localidades .Include(localidad => localidad.Provincia) .ToListAsync(); } } } 

IRepository.cs

namespace CapacitacionesBE.Data { public interface IRepository<T> : IDisposable { public Task<List<T>> GetAll(); public Task<T> GetById(int Id); public Task<T> Insert(T Element); public Task<T> Update(T Element); public Task Delete(int Id); } } 

Service.cs

namespace CapacitacionesBE.Data { public class Service<T> : IRepository<T> where T : class { protected readonly LengaContext _context; public Service(LengaContext context) { _context = context; } public async Task Delete(int Id) { var ItemToDelete = _context.Set<T>().Find(Id); if (ItemToDelete != null) { _context.Set<T>().Remove(ItemToDelete); try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { throw; } } } public void Dispose() { _context.Dispose(); } public async Task<List<T>> GetAll() { try { var Items = await _context.Set<T>().ToListAsync(); return Items; } catch (Exception ex) { throw new Exception($"Error no se pueden traer las entidades: {ex.Message}"); } } public async Task<T> GetById(int Id) { var Item = await _context.Set<T>().FindAsync(Id); return Item; } public async Task<T> Insert(T Element) { if (Element != null) { _context.Set<T>().Add(Element); try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { throw; } return Element; } else { return null; } } public async Task<T> Update(T Element) { _context.Set<T>().Update(Element); try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { throw; } return Element; } } } 

Startup.cs I only copied the configureServices method, there's no need for the rest of the file

 public void ConfigureServices(IServiceCollection services){ services.AddRazorPages(); services.AddServerSideBlazor(); services.AddLocalization(opts => { opts.ResourcesPath = "Resources"; }); services.AddDbContext<LengaContext>(opt => opt.UseSqlServer( Configuration.GetConnectionString("MyDB")), ServiceLifetime.Transient); services.AddScoped<IProvinciaService, ProvinciaService>(); services.AddScoped<ILocalidadService, LocalidadService>(); services.AddTransient<IAuthorizationHandler, IsOperadorHandler>(); services.AddScoped<IStringLocalizer<App>, StringLocalizer<App>>(); } 

Crear.razor this is my view

@page "/Localidades/Crear" @using CapacitacionModelos.Modelos @using Microsoft.Extensions.Localization; @using LengaFE.Shared.Components; @using Microsoft.Extensions.Logging @inject CapacitacionesBE.Data.ILocalidadService _serviceLocalidad @inject CapacitacionesBE.Data.IProvinciaService _serviceProvincia @inject NavigationManager NavigationManager @inject ILogger<Crear> Logger @inject IStringLocalizer<App> L; <h3>Crear</h3> <EditForm Model="@Localidad" OnValidSubmit="@HandleSubmit"> <div> <div> <div> <label for="Localidad">@L["localidad"]</label> <input form="Nombre" @bind="@Localidad.Nombre" /> </div> </div> <div> <div> <label for="Provincia">@L["provincia"]</label> <CustomInputSelect @bind-Value="SelectedProvincia"> @if (Provincias is null) { <option>Cargando...</option> } else { @foreach (var provincia in Provincias) { <option value="@provincia.IdProvincia">@provincia.Nombre</option> } } </CustomInputSelect> </div> </div> </div> <div> <div> <FormButtons PrimaryActionLabel=@L["crear"] SecondaryActionLabel=@L["cancelar"] ClickActionPrimary=@CrearLocalidad ClickActionSecondary=@Cancel /> </div> </div> </EditForm> @code { List<Provincia> Provincias; public Localidad Localidad = new Localidad(); private void HandleSubmit() { Logger.LogInformation("Handling submit"); CrearLocalidad(); } public int SelectedProvincia { get; set; } protected async void CrearLocalidad() { var provincia = await _serviceProvincia.GetById(SelectedProvincia); //Localidad.IdProvincia = SelectedProvincia; Localidad.Provincia = provincia; await _serviceLocalidad.Insert(Localidad); NavigationManager.NavigateTo("Localidades"); } protected override async Task OnInitializedAsync() { Provincias = await Task.Run(() => _serviceProvincia.GetAll()); var PrimerProvincia = Provincias.FirstOrDefault(); if (PrimerProvincia != null) { SelectedProvincia = PrimerProvincia.IdProvincia; } } void Cancel() { NavigationManager.NavigateTo("Localidades"); } } 

FormButtons.razor

@using Microsoft.Extensions.Localization; @inject IStringLocalizer<App> L; <div> <input type="button" @onclick="@ClickActionPrimary" value=@PrimaryActionLabel /> <input type="button" @onclick="@ClickActionSecondary" value=@SecondaryActionLabel /> </div> @code { [Parameter] public string PrimaryActionLabel { get; set; } [Parameter] public string SecondaryActionLabel { get; set; } [Parameter] public EventCallback ClickActionPrimary { get; set; } [Parameter] public EventCallback ClickActionSecondary { get; set; } } 

I've been reading about lifetime services, that's why I use transient in my dbcontext, it is creating a new instance per request. For entities which don't have relationship with other models the CRUD is working properly, but with Localidad Entity which contains a Provincia Entity it is failing. Also I think maybe the solution is related to Entity State,but I haven't dug deep enough. I'm trying to make some progress in my job atm. Regarding entity state, I scaffolded some razor pages in other projects and the entity state is being tracked in the views, maybe I should go that way.

Any help to improve this code will be appreaciated, I'm a beginner with net core and backend in general (I worked with frontend technologies only), and I'm still reading some books like net core in action and doing some courses but this exceeds my current knowledge.

7

1 Answer

Implementing the DbContextFactory from the MS documentation as per the comment by Panagiotis Kanavos is definitely needed regardless and will solve most cases of this error.

However the comment from Steve Py about entities becoming detached from their respective DbContext when trying to Update an entity with related entities is for more advanced cases. Two ways I found to solve it if this is the cause are:

  1. null out the related entities before calling Update
  2. set EntityEntry.State to EntityState.Modified instead of calling Update

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

You Might Also Like