嗨!大家好,我正在拜读马克 J 普莱斯的《c#10和.NET 6入门与跨平台开发(第6版)》这本书,跟着书中的代码练习,在第17章关于Blazor server第四小节关于客户信息的增、删、改功能时,当点击创建或修改客户信息并提交返回客户信息列表页后就会遇到的错误提示:“A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext”,但如果点击的是删除,就没出现报错。我搜索了该问题的解决方案,检查了依赖注入方式和异步操作的关键字await都没发现需要纠正的错误,并与书中和github上代码进行了比较也没有发现不一样的地方,请问我到底错过了什么?该如何解决这个问题?希望得到解答,请不要用AI回答我的问题,谢谢!
gitbug代码连接:https://github.com/markjprice/cs10dotnet6/tree/main/vs4win/PracticalApps/Northwind.BlazorServer
我的代码如下:
CustomerDetail.razor
<EditForm Model="@Customer" OnValidSubmit="@OnValidSubmit">
<DataAnnotationsValidator />
<div class="form-group">
<div>
<label>Customer Id</label>
<div>
<InputText @bind-Value="@Customer.CustomerId" />
<ValidationMessage For="@(()=>Customer.CustomerId)"></ValidationMessage>
</div>
</div>
</div>
<div class="form-group">
<div>
<label>Company Name</label>
<div>
<InputText @bind-Value="@Customer.CompanyName" />
<ValidationMessage For="@(()=>Customer.CompanyName)"></ValidationMessage>
</div>
</div>
</div>
<div class="form-group">
<div>
<label>Address</label>
<div>
<InputText @bind-Value="@Customer.Address" />
<ValidationMessage For="@(()=>Customer.Address)"></ValidationMessage>
</div>
</div>
</div>
<div class="form-group">
<div>
<label>Country</label>
<div>
<InputText @bind-Value="@Customer.Country" />
<ValidationMessage For="@(()=>Customer.Country)"></ValidationMessage>
</div>
</div>
</div>
<button type="submit" class="btn btn-@ButtonStyle">@ButtonText</button>
</EditForm>
@code {
[Parameter]
public Customer Customer { get; set; } = null!;
[Parameter]
public string ButtonText { get; set; } = "Save Changes";
[Parameter]
public string ButtonStyle { get; set; } = "info";
[Parameter]
public EventCallback OnValidSubmit { get; set; }
}
CreateCustomer.razor
@page "/createcustomer"
@using Northwind.BlazorServer.Data
@inject INorthwindService service
@inject NavigationManager navigation
<h3>Create Customer</h3>
<CustomerDetail ButtonText="Create Customer" Customer="@customer" OnValidSubmit="@Create" />
@code {
private Customer customer = new();
private async Task Create()
{
await service.CreateCustomerAsync(customer);
navigation.NavigateTo("customers");
}
}
DeleteCustomer.razor
@page "/deletecustomer/{customerid}"
@using Northwind.BlazorServer.Data
@inject INorthwindService service
@inject NavigationManager navigation
<h3>Delete Customer</h3>
<div class="alert alert-danger">
Warning! This action cannot be undone!
</div>
<CustomerDetail ButtonText="Delete Customer" ButtonStyle="danger" Customer="@customer" OnValidSubmit="@Delete" />
@code {
[Parameter]
public string CustomerId { get; set; }
private Customer? customer = new();
protected async override Task OnParametersSetAsync()
{
customer = await service.GetCustomerAsync(CustomerId);
}
private async Task Delete()
{
if (customer is not null)
{
await service.DeleteCustomerAsync(CustomerId);
navigation.NavigateTo("customers");
}
}
}
EditCustomer.razor
@page "/editcustomer/{customerid}"
@using Northwind.BlazorServer.Data
@inject INorthwindService service
@inject NavigationManager navigation
<h3>Edit Customer</h3>
<CustomerDetail ButtonText="Edit Customer" Customer="@customer" OnValidSubmit="@Update" />
@code {
[Parameter]
public string CustomerId { get; set; }
private Customer? customer=new();
protected async override Task OnParametersSetAsync()
{
customer = await service.GetCustomerAsync(CustomerId);
}
private async Task Update()
{
if (customer is not null)
{
await service.UpdateCustomerAsync(customer);
}
navigation.NavigateTo("Customers");
}
}
Customers.razor
@using Microsoft.EntityFrameworkCore @* ToListAsync extension method *@
@using Northwind.BlazorServer.Data
@page "/Customers/{country?}"
@inject INorthwindService service
<h3>Customers@(string.IsNullOrWhiteSpace(Country)?" Worldwide":"in "+Country)</h3>
<div class="form-group">
<a class="btn btn-info" href="createcustomer">
<i class="oi oi-plus"></i> Create New
</a>
</div>
@if (customers==null)
{
<p><em>Loading...</em></p>
}
else
{
<table>
<thead>
<tr>
<th>Id</th>
<th>Company Name</th>
<th>Address</th>
<th>Phone</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach(Customer c in customers)
{
<tr>
<td>@c.CustomerId</td>
<td>@c.CompanyName</td>
<td>
@c.Address<br />
@c.City<br/>
@c.PostalCode<br />
@c.Country
</td>
<td>@c.Phone</td>
<td>
<a class="btn btn-info" href="editcustomer/@c.CustomerId">
<i class="oi oi-pencil"></i></a>
<a class="btn btn-danger" href="deletecustomer/@c.CustomerId">
<i class="oi oi-trash"></i></a>
</td>
</tr>
}
</tbody>
</table>
}
@code {
[Parameter]
public string? Country { get; set; }
private IEnumerable<Customer>? customers;
protected override async Task OnParametersSetAsync()
{
if (string.IsNullOrWhiteSpace(Country))
{
customers = await service.GetCustomersAsync();
}
else
{
customers = await service.GetCustomersAsync(Country);
}
}
}
Program.cs
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Northwind.BlazorServer.Data;
using Northwind.Common.DataContext.SqlServer;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddNorthwindContext();
builder.Services.AddTransient<INorthwindService, NorthwindService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
数据库操作相关:
NorthwindContext.cs
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
namespace Packt.Shared
{
public partial class NorthwindContext : DbContext
{
public NorthwindContext()
{
}
public NorthwindContext(DbContextOptions<NorthwindContext> options)
: base(options)
{
}
public virtual DbSet<AlphabeticalListOfProduct> AlphabeticalListOfProducts { get; set; } = null!;
public virtual DbSet<Category> Categories { get; set; } = null!;
public virtual DbSet<CategorySalesFor1997> CategorySalesFor1997s { get; set; } = null!;
public virtual DbSet<CurrentProductList> CurrentProductLists { get; set; } = null!;
public virtual DbSet<Customer> Customers { get; set; } = null!;
public virtual DbSet<CustomerAndSuppliersByCity> CustomerAndSuppliersByCities { get; set; } = null!;
public virtual DbSet<CustomerDemographic> CustomerDemographics { get; set; } = null!;
public virtual DbSet<Employee> Employees { get; set; } = null!;
public virtual DbSet<Invoice> Invoices { get; set; } = null!;
public virtual DbSet<Order> Orders { get; set; } = null!;
public virtual DbSet<OrderDetail> OrderDetails { get; set; } = null!;
public virtual DbSet<OrderDetailsExtended> OrderDetailsExtendeds { get; set; } = null!;
public virtual DbSet<OrderSubtotal> OrderSubtotals { get; set; } = null!;
public virtual DbSet<OrdersQry> OrdersQries { get; set; } = null!;
public virtual DbSet<Product> Products { get; set; } = null!;
public virtual DbSet<ProductSalesFor1997> ProductSalesFor1997s { get; set; } = null!;
public virtual DbSet<ProductsAboveAveragePrice> ProductsAboveAveragePrices { get; set; } = null!;
public virtual DbSet<ProductsByCategory> ProductsByCategories { get; set; } = null!;
public virtual DbSet<QuarterlyOrder> QuarterlyOrders { get; set; } = null!;
public virtual DbSet<Region> Regions { get; set; } = null!;
public virtual DbSet<SalesByCategory> SalesByCategories { get; set; } = null!;
public virtual DbSet<SalesTotalsByAmount> SalesTotalsByAmounts { get; set; } = null!;
public virtual DbSet<Shipper> Shippers { get; set; } = null!;
public virtual DbSet<SummaryOfSalesByQuarter> SummaryOfSalesByQuarters { get; set; } = null!;
public virtual DbSet<SummaryOfSalesByYear> SummaryOfSalesByYears { get; set; } = null!;
public virtual DbSet<Supplier> Suppliers { get; set; } = null!;
public virtual DbSet<Territory> Territories { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("data source=.; Initial Catalog=Northwind; Integrated Security=true;");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AlphabeticalListOfProduct>(entity =>
{
entity.ToView("Alphabetical list of products");
});
modelBuilder.Entity<CategorySalesFor1997>(entity =>
{
entity.ToView("Category Sales for 1997");
});
modelBuilder.Entity<CurrentProductList>(entity =>
{
entity.ToView("Current Product List");
entity.Property(e => e.ProductId).ValueGeneratedOnAdd();
});
modelBuilder.Entity<Customer>(entity =>
{
entity.Property(e => e.CustomerId).IsFixedLength();
entity.HasMany(d => d.CustomerTypes)
.WithMany(p => p.Customers)
.UsingEntity<Dictionary<string, object>>(
"CustomerCustomerDemo",
l => l.HasOne<CustomerDemographic>().WithMany().HasForeignKey("CustomerTypeId").OnDelete(DeleteBehavior.ClientSetNull).HasConstraintName("FK_CustomerCustomerDemo"),
r => r.HasOne<Customer>().WithMany().HasForeignKey("CustomerId").OnDelete(DeleteBehavior.ClientSetNull).HasConstraintName("FK_CustomerCustomerDemo_Customers"),
j =>
{
j.HasKey("CustomerId", "CustomerTypeId").IsClustered(false);
j.ToTable("CustomerCustomerDemo");
j.IndexerProperty<string>("CustomerId").HasMaxLength(5).HasColumnName("CustomerID").IsFixedLength();
j.IndexerProperty<string>("CustomerTypeId").HasMaxLength(10).HasColumnName("CustomerTypeID").IsFixedLength();
});
});
modelBuilder.Entity<CustomerAndSuppliersByCity>(entity =>
{
entity.ToView("Customer and Suppliers by City");
});
modelBuilder.Entity<CustomerDemographic>(entity =>
{
entity.HasKey(e => e.CustomerTypeId)
.IsClustered(false);
entity.Property(e => e.CustomerTypeId).IsFixedLength();
});
modelBuilder.Entity<Employee>(entity =>
{
entity.HasOne(d => d.ReportsToNavigation)
.WithMany(p => p.InverseReportsToNavigation)
.HasForeignKey(d => d.ReportsTo)
.HasConstraintName("FK_Employees_Employees");
entity.HasMany(d => d.Territories)
.WithMany(p => p.Employees)
.UsingEntity<Dictionary<string, object>>(
"EmployeeTerritory",
l => l.HasOne<Territory>().WithMany().HasForeignKey("TerritoryId").OnDelete(DeleteBehavior.ClientSetNull).HasConstraintName("FK_EmployeeTerritories_Territories"),
r => r.HasOne<Employee>().WithMany().HasForeignKey("EmployeeId").OnDelete(DeleteBehavior.ClientSetNull).HasConstraintName("FK_EmployeeTerritories_Employees"),
j =>
{
j.HasKey("EmployeeId", "TerritoryId").IsClustered(false);
j.ToTable("EmployeeTerritories");
j.IndexerProperty<int>("EmployeeId").HasColumnName("EmployeeID");
j.IndexerProperty<string>("TerritoryId").HasMaxLength(20).HasColumnName("TerritoryID");
});
});
modelBuilder.Entity<Invoice>(entity =>
{
entity.ToView("Invoices");
entity.Property(e => e.CustomerId).IsFixedLength();
});
modelBuilder.Entity<Order>(entity =>
{
entity.Property(e => e.CustomerId).IsFixedLength();
entity.Property(e => e.Freight).HasDefaultValueSql("((0))");
entity.HasOne(d => d.Customer)
.WithMany(p => p.Orders)
.HasForeignKey(d => d.CustomerId)
.HasConstraintName("FK_Orders_Customers");
entity.HasOne(d => d.Employee)
.WithMany(p => p.Orders)
.HasForeignKey(d => d.EmployeeId)
.HasConstraintName("FK_Orders_Employees");
entity.HasOne(d => d.ShipViaNavigation)
.WithMany(p => p.Orders)
.HasForeignKey(d => d.ShipVia)
.HasConstraintName("FK_Orders_Shippers");
});
modelBuilder.Entity<OrderDetail>(entity =>
{
entity.HasKey(e => new { e.OrderId, e.ProductId })
.HasName("PK_Order_Details");
entity.Property(e => e.Quantity).HasDefaultValueSql("((1))");
entity.HasOne(d => d.Order)
.WithMany(p => p.OrderDetails)
.HasForeignKey(d => d.OrderId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Order_Details_Orders");
entity.HasOne(d => d.Product)
.WithMany(p => p.OrderDetails)
.HasForeignKey(d => d.ProductId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Order_Details_Products");
});
modelBuilder.Entity<OrderDetailsExtended>(entity =>
{
entity.ToView("Order Details Extended");
});
modelBuilder.Entity<OrderSubtotal>(entity =>
{
entity.ToView("Order Subtotals");
});
modelBuilder.Entity<OrdersQry>(entity =>
{
entity.ToView("Orders Qry");
entity.Property(e => e.CustomerId).IsFixedLength();
});
modelBuilder.Entity<Product>(entity =>
{
entity.Property(e => e.ReorderLevel).HasDefaultValueSql("((0))");
entity.Property(e => e.UnitPrice).HasDefaultValueSql("((0))");
entity.Property(e => e.UnitsInStock).HasDefaultValueSql("((0))");
entity.Property(e => e.UnitsOnOrder).HasDefaultValueSql("((0))");
entity.HasOne(d => d.Category)
.WithMany(p => p.Products)
.HasForeignKey(d => d.CategoryId)
.HasConstraintName("FK_Products_Categories");
entity.HasOne(d => d.Supplier)
.WithMany(p => p.Products)
.HasForeignKey(d => d.SupplierId)
.HasConstraintName("FK_Products_Suppliers");
});
modelBuilder.Entity<ProductSalesFor1997>(entity =>
{
entity.ToView("Product Sales for 1997");
});
modelBuilder.Entity<ProductsAboveAveragePrice>(entity =>
{
entity.ToView("Products Above Average Price");
});
modelBuilder.Entity<ProductsByCategory>(entity =>
{
entity.ToView("Products by Category");
});
modelBuilder.Entity<QuarterlyOrder>(entity =>
{
entity.ToView("Quarterly Orders");
entity.Property(e => e.CustomerId).IsFixedLength();
});
modelBuilder.Entity<Region>(entity =>
{
entity.HasKey(e => e.RegionId)
.IsClustered(false);
entity.Property(e => e.RegionId).ValueGeneratedNever();
entity.Property(e => e.RegionDescription).IsFixedLength();
});
modelBuilder.Entity<SalesByCategory>(entity =>
{
entity.ToView("Sales by Category");
});
modelBuilder.Entity<SalesTotalsByAmount>(entity =>
{
entity.ToView("Sales Totals by Amount");
});
modelBuilder.Entity<SummaryOfSalesByQuarter>(entity =>
{
entity.ToView("Summary of Sales by Quarter");
});
modelBuilder.Entity<SummaryOfSalesByYear>(entity =>
{
entity.ToView("Summary of Sales by Year");
});
modelBuilder.Entity<Territory>(entity =>
{
entity.HasKey(e => e.TerritoryId)
.IsClustered(false);
entity.Property(e => e.TerritoryDescription).IsFixedLength();
entity.HasOne(d => d.Region)
.WithMany(p => p.Territories)
.HasForeignKey(d => d.RegionId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Territories_Region");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
}
NorthwindContextExtensions.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Packt.Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Northwind.Common.DataContext.SqlServer
{
public static class NorthwindContextExtensions
{
public static IServiceCollection AddNorthwindContext(
this IServiceCollection services, string connectionString =
"data source=.; Initial Catalog=Northwind; " +
"Integrated Security=true;MultipleActiveResultsets=true;"
)
{
services.AddDbContext<NorthwindContext>(options => options.UseSqlServer(connectionString));
return services;
}
}
}
INorthwindService.cs
using Packt.Shared;
namespace Northwind.BlazorServer.Data
{
public interface INorthwindService
{
Task<List<Customer>> GetCustomersAsync();
Task<List<Customer>> GetCustomersAsync(string country);
Task<Customer?> GetCustomerAsync(string id);
Task<Customer> CreateCustomerAsync(Customer c);
Task<Customer> UpdateCustomerAsync(Customer c);
Task DeleteCustomerAsync(string id);
}
}
NorthwindService.cs
using Microsoft.EntityFrameworkCore;
using Packt.Shared;
namespace Northwind.BlazorServer.Data
{
public class NorthwindService : INorthwindService
{
private readonly NorthwindContext db;
public NorthwindService(NorthwindContext db)
{
this.db = db;
}
public Task DeleteCustomerAsync(string id)
{
Customer? customer = db.Customers.FirstOrDefaultAsync
(c => c.CustomerId == id).Result;
if (customer == null)
{
return Task.CompletedTask;
}
else
{
db.Customers.Remove(customer);
return db.SaveChangesAsync();
}
}
public Task<Customer?> GetCustomerAsync(string id)
{
return db.Customers.FirstOrDefaultAsync(c => c.CustomerId == id);
}
public Task<Customer> CreateCustomerAsync(Customer c)
{
db.Customers.Add(c);
db.SaveChangesAsync();
return Task.FromResult(c);
}
public Task<List<Customer>> GetCustomersAsync()
{
return db.Customers.ToListAsync();
}
public Task<List<Customer>> GetCustomersAsync(string country)
{
return db.Customers.Where(c=>c.Country==country).ToListAsync();
}
public Task<Customer> UpdateCustomerAsync(Customer c)
{
db.Entry(c).State= EntityState.Modified;
db.SaveChangesAsync();
return Task.FromResult(c);
}
}
}