龟叔啊 2024-03-04 12:15 采纳率: 50%
浏览 8
已结题

Blazor server 数据库操作异常,如何解决?(语言-c#)

嗨!大家好,我正在拜读马克 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);
        }
    }
}
  • 写回答

8条回答 默认 最新

  • 专家-赤兔[在线] 全栈领域优质创作者 2024-03-04 12:16
    关注

    引自免费微信小程序:皆我百晓生

    针对您在使用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.” 这个问题源于在一个DbContext实例上并发执行了多个数据库操作,而EF Core默认并不支持跨线程或异步操作中的并发 DbContext 使用。

    在您的代码中,NorthwindService.cs 中的 CreateCustomerAsyncUpdateCustomerAsync 方法存在问题。这两个方法没有正确地等待SaveChangesAsync完成,这就导致在导航回客户列表页之前,上下文可能还在处理另一个操作,从而触发了上述错误。

    为了修复这个问题,请将这两个方法更新为以下形式:

    public async Task<Customer> CreateCustomerAsync(Customer c)
    {
        db.Customers.Add(c);
        await db.SaveChangesAsync(); // 添加await确保保存完成
        return c;
    }
    
    public async Task<Customer> UpdateCustomerAsync(Customer c)
    {
        db.Entry(c).State = EntityState.Modified;
        await db.SaveChangesAsync(); // 添加await确保保存完成
        return c;
    }
    

    这样,当创建或更新客户信息后,在导航回客户列表页面前会先确保数据库保存操作已完成。这应该可以解决您遇到的并发使用DbContext的问题。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论 编辑记录
查看更多回答(7条)

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 3月4日
  • 已采纳回答 3月4日
  • 创建了问题 3月4日

悬赏问题

  • ¥15 基于卷积神经网络的声纹识别
  • ¥15 Python中的request,如何使用ssr节点,通过代理requests网页。本人在泰国,需要用大陆ip才能玩网页游戏,合法合规。
  • ¥100 为什么这个恒流源电路不能恒流?
  • ¥15 有偿求跨组件数据流路径图
  • ¥15 写一个方法checkPerson,入参实体类Person,出参布尔值
  • ¥15 我想咨询一下路面纹理三维点云数据处理的一些问题,上传的坐标文件里是怎么对无序点进行编号的,以及xy坐标在处理的时候是进行整体模型分片处理的吗
  • ¥15 CSAPPattacklab
  • ¥15 一直显示正在等待HID—ISP
  • ¥15 Python turtle 画图
  • ¥15 stm32开发clion时遇到的编译问题