c# - EF Core:blazor 服务器应用程序中的 dbcontext 问题
问题描述
我正在 Blazor 服务器中开发应用程序,当我尝试执行某些操作时遇到不同的错误。
我的解决方案分为 3 个项目
- 带有存储库的后端项目 - 服务架构和 MySql DB
- 类库中的模型来存储我的模型
- Blazor 服务器前端。
- 一个 xUnit 测试项目
我收到以下错误
System.InvalidOperationException:无法跟踪实体类型“Provincia”的实例,因为已经在跟踪具有相同键值 {'IdProvincia'} 的另一个实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。考虑使用“DbContextOptionsBuilder.EnableSensitiveDataLogging”来查看冲突的键值
当我尝试Localidad
在我的 dbcontext 中持久化实体时会出现此错误Crear.razor
,我的 dbcontext 的生命周期服务等于Transient
.
另一个问题是,当我更改 DbContext 服务的生命周期时,如果我将其设置为Scoped
(或删除它,这是默认设置)CRUD 操作正在工作,但如果我在页面中进行刷新(按F5)出现以下错误
InvalidOperationException:在前一个操作完成之前在此上下文上启动了第二个操作。这通常是由使用相同 DbContext 实例的不同线程引起的。有关如何避免 DbContext 线程问题的更多信息,请参阅https://go.microsoft.com/fwlink/?linkid=2097913
这是我的代码(省略了导入)
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);
}
}
服务.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 我只复制了 configureServices 方法,文件的其余部分不需要
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 这是我的观点
@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 class="row">
<div class="col-md-8">
<div class="form-group">
<label for="Localidad" class="control-label">@L["localidad"]</label>
<input form="Nombre" class="form-control" @bind="@Localidad.Nombre" />
</div>
</div>
<div class="col-md-8">
<div class="form-group">
<label for="Provincia" class="control-label">@L["provincia"]</label>
<CustomInputSelect @bind-Value="SelectedProvincia" id="Provincia" class="form-control">
@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 class="row">
<div class="col-md-4">
<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");
}
}
表单按钮.razor
@using Microsoft.Extensions.Localization;
@inject IStringLocalizer<App> L;
<div class="form-group">
<input type="button" class="btn btn-primary" @onclick="@ClickActionPrimary" value=@PrimaryActionLabel />
<input type="button" class="btn btn-primary" @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; }
}
我一直在阅读有关终身服务的信息,这就是为什么我在我的 dbcontext 中使用瞬态,它为每个请求创建一个新实例。对于与其他模型没有关系的实体,CRUD 可以正常工作,但对于包含Provincia 实体的Localidad 实体,它会失败。另外我认为解决方案可能与实体状态有关,但我还没有深入挖掘。我正在努力在我的工作 atm 上取得一些进展。关于实体状态,我在其他项目中搭建了一些剃须刀页面,并且正在视图中跟踪实体状态,也许我应该这样做。
对改进此代码的任何帮助都会受到重视,我是网络核心和后端的初学者(我只使用前端技术),我仍在阅读一些书籍,如网络核心在行动中并做一些课程,但是这个超出了我目前的知识。
解决方案
无论如何,绝对需要根据 Panagiotis Kanavos 的评论DbContextFactory
从MS 文档中实施,这将解决大多数此错误的情况。
Update
然而,Steve Py 关于实体在尝试使用具有相关实体的实体时与它们各自的 DbContext 分离的评论适用于更高级的情况。如果这是原因,我发现有两种解决方法是:
- 在调用之前清空相关实体
Update
- 设置
EntityEntry.State
为EntityState.Modified
而不是调用Update
推荐阅读
- php - 无法将序列化的表单数据从 ajax 请求解析到 php 数组
- python-3.x - 从python中的响应输出中删除字段
- android - 如何为此按钮设置点击监听器
- machine-learning - Google Auto ML , 平均精度
- javascript - 在剃须刀代码下检查javascript中的viewbag null
- vert.x - Vertx 和 Swagger 项目
- c++ - 除非明确覆盖,否则如何创建采用默认值的 CMake 变量?
- php - PHP递归函数不向AJAX请求返回任何内容
- java - 在不更改 ValueProperty 的情况下更改 ComboBox 的项目
- javascript - 覆盖 PureComponent 的 shouldComponentUpdate 的问题