我正在使用Entity Framework Core
开发ASP.Net Core 2.0项目<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0"/>
在我的一个列表方法中,我收到了这个错误:
InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
这是我的方法:
[HttpGet("{currentPage}/{pageSize}/")]
[HttpGet("{currentPage}/{pageSize}/{search}")]
public ListResponseVM<ClientVM> GetClients([FromRoute] int currentPage, int pageSize, string search)
{
var resp = new ListResponseVM<ClientVM>();
var items = _context.Clients
.Include(i => i.Contacts)
.Include(i => i.Addresses)
.Include("ClientObjectives.Objective")
.Include(i => i.Urls)
.Include(i => i.Users)
.Where(p => string.IsNullOrEmpty(search) || p.CompanyName.Contains(search))
.OrderBy(p => p.CompanyName)
.ToPagedList(pageSize, currentPage);
resp.NumberOfPages = items.TotalPage;
foreach (var item in items)
{
var client = _mapper.Map<ClientVM>(item);
client.Addresses = new List<AddressVM>();
foreach (var addr in item.Addresses)
{
var address = _mapper.Map<AddressVM>(addr);
address.CountryCode = addr.CountryId;
client.Addresses.Add(address);
}
client.Contacts = item.Contacts.Select(p => _mapper.Map<ContactVM>(p)).ToList();
client.Urls = item.Urls.Select(p => _mapper.Map<ClientUrlVM>(p)).ToList();
client.Objectives = item.Objectives.Select(p => _mapper.Map<ObjectiveVM>(p)).ToList();
resp.Items.Add(client);
}
return resp;
}
我有点迷失,特别是因为它在我本地运行它时有效,但当我部署到我的登台服务器(IIS 8.5)时,它会出现这个错误并且它正常工作。在我增加其中一个模型的最大长度后,错误开始出现。我还更新了相应视图模型的最大长度。还有许多其他列表方法非常相似,它们正在发挥作用。
我运行了一个Hangfire作业,但是这个作业不使用同一个实体。这就是我能想到的所有相关内容。有什么可能导致这个问题的想法?
答案 0 :(得分:31)
我不确定您是否正在使用IoC和依赖注入来解析您可能使用的DbContext。如果您这样做并且您正在使用来自.NET Core(或任何其他IoC-Container)的本机IoC,并且您收到此错误,请确保将DbContext注册为Transient。做
services.AddTransient<MyContext>();
OR
services.AddDbContext<MyContext>(ServiceLifetime.Transient);
而不是
services.AddDbContext<MyContext>();
AddDbContext将上下文添加为作用域,这可能会在处理多个线程时造成麻烦。
使用异步lambda表达式时,async / await operations也会导致此行为。
将其添加为瞬态也有其缺点。您将无法在使用上下文的多个类上对某个实体进行更改,因为每个类都将获得自己的DbContext实例。
答案 1 :(得分:20)
异常意味着两个线程同时使用_context
;同一个请求中的两个线程,或两个请求。
你的_context
可能是静态的吗?它不应该是。
或者您是否在代码中其他位置的同一请求中多次调用GetClients
?
您可能已经这样做了,但理想情况下,您将DbContext
用于private readonly MyDbContext _context; //not static
public MyController(MyDbContext context) {
_context = context;
}
,这意味着您将在Startup.cs中使用dependency injection,并且你的控制器构造函数看起来像这样:
{{1}}
如果您的代码不是这样,请告诉我们,也许我们可以提供更多帮助。
答案 2 :(得分:11)
使用Startup.cs文件中的这一行代码解决我的问题。
添加临时服务意味着每次请求服务时,在工作时都会创建一个新实例
与
依赖注入
services.AddDbContext<Context>(options =>
options.UseSqlServer(_configuration.GetConnectionString("ContextConn")),
ServiceLifetime.Transient);
答案 3 :(得分:10)
我遇到了同样的问题,结果证明父母服务是单身汉。因此,上下文也自动成为singelton。即使在DI中被声明为“ Per Life Time Scoped”。
将具有不同生命周期的服务注入另一个
切勿将范围和瞬态服务注入Singleton服务。 ( 这可以有效地将临时服务或范围服务转换为 单身人士。 )
切勿将瞬态服务注入作用域服务(这会转换 过渡服务进入范围。 )
答案 4 :(得分:8)
在某些情况下,此错误是由这种情况引起的:您调用 async 方法,但在调用该方法之前未使用 await 。我的问题通过在该方法之前添加等待来解决。但是答案可能与提到的问题无关,但可以帮助解决类似的错误。
答案 5 :(得分:2)
我遇到了同样的问题,但原因并非以上所列。我创建了一个任务,在任务内部创建了一个作用域,并要求容器获得服务。效果很好,但是随后我在任务中使用了第二个服务,但我忘了也将其要求到新的范围。因此,第二项服务使用的是已处置的DbContext。
Task task = Task.Run(() =>
{
using (var scope = serviceScopeFactory.CreateScope())
{
var otherOfferService = scope.ServiceProvider.GetService<IOfferService>();
// everything was ok here. then I did:
productService.DoSomething(); // (from the main scope) and this failed because the db context associated to that service was already disposed.
...
}
}
我应该这样做:
var otherProductService = scope.ServiceProvider.GetService<IProductService>();
otherProductService.DoSomething();
答案 6 :(得分:2)
Entity Framework Core不支持在同一DbContext
实例上运行的多个并行操作。这包括async
查询的并行执行以及来自多个线程的任何显式并发使用。因此,始终await async
会立即调用,或者对并行执行的操作使用单独的DbContext
实例。
答案 7 :(得分:2)
我有同样的错误。发生这种情况是因为我调用了一个构造为public async void ...
而不是public async Task ...
的方法。
答案 8 :(得分:1)
如果您创建了迁移 (Add-Migration) 但忘记实际更新数据库 (Update-Database),也可能会出现此错误。
答案 9 :(得分:1)
我的情况有所不同: 我试图为30个用户(属于特定角色)播种数据库,所以我正在运行以下代码:
for (var i = 1; i <= 30; i++)
{
CreateUserWithRole("Analyst", $"analyst{i}", UserManager);
}
这是一个同步功能。 在其中,我有3个呼叫:
UserManager.FindByNameAsync(username).Result
UserManager.CreateAsync(user, pass).Result
UserManager.AddToRoleAsync(user, roleName).Result
当我将.Result
替换为.GetAwaiter().GetResult()
时,此错误消失了。
答案 10 :(得分:1)
首先,支持(至少)alsami的答案。那使我走上了正确的道路。
但是对于那些进行IoC的人来说,这是更深层次的潜水。
我的错误(与其他错误相同)
发生一个或多个错误。 (第二个操作由此开始 上一个操作完成之前的上下文。这通常是由于 通过不同的线程使用相同的DbContext实例。欲了解更多 有关如何避免DbContext出现线程问题的信息,请参见 https://go.microsoft.com/fwlink/?linkid=2097913。)
我的代码设置。 “只是基础” ...
public class MyCoolDbContext: DbContext{
public DbSet <MySpecialObject> MySpecialObjects { get; set; }
}
和
public interface IMySpecialObjectDomainData{}
和(注意正在注入MyCoolDbContext)
public class MySpecialObjectEntityFrameworkDomainDataLayer: IMySpecialObjectDomainData{
public MySpecialObjectEntityFrameworkDomainDataLayer(MyCoolDbContext context) {
/* HERE IS WHERE TO SET THE BREAK POINT, HOW MANY TIMES IS THIS RUNNING??? */
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
}
}
和
public interface IMySpecialObjectManager{}
和
public class MySpecialObjectManager: IMySpecialObjectManager
{
public const string ErrorMessageIMySpecialObjectDomainDataIsNull = "IMySpecialObjectDomainData is null";
private readonly IMySpecialObjectDomainData mySpecialObjectDomainData;
public MySpecialObjectManager(IMySpecialObjectDomainData mySpecialObjectDomainData) {
this.mySpecialObjectDomainData = mySpecialObjectDomainData ?? throw new ArgumentNullException(ErrorMessageIMySpecialObjectDomainDataIsNull, (Exception)null);
}
}
最后,从控制台应用程序(命令行界面应用程序)调用我的多线程类
public interface IMySpecialObjectThatSpawnsThreads{}
和
public class MySpecialObjectThatSpawnsThreads: IMySpecialObjectThatSpawnsThreads
{
public const string ErrorMessageIMySpecialObjectManagerIsNull = "IMySpecialObjectManager is null";
private readonly IMySpecialObjectManager mySpecialObjectManager;
public MySpecialObjectThatSpawnsThreads(IMySpecialObjectManager mySpecialObjectManager) {
this.mySpecialObjectManager = mySpecialObjectManager ?? throw new ArgumentNullException(ErrorMessageIMySpecialObjectManagerIsNull, (Exception)null);
}
}
和DI积累。 (同样,这是针对控制台应用程序(命令行界面)的。。。它的行为与Web应用程序略有不同)
private static IServiceProvider BuildDi(IConfiguration configuration) {
/* this is being called early inside my command line application ("console application") */
string defaultConnectionStringValue = string.Empty; /* get this value from configuration */
////setup our DI
IServiceCollection servColl = new ServiceCollection()
////.AddLogging(loggingBuilder => loggingBuilder.AddConsole())
/* THE BELOW TWO ARE THE ONES THAT TRIPPED ME UP. */
.AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>()
.AddTransient<IMySpecialObjectManager, MySpecialObjectManager>()
/* so the "ServiceLifetime.Transient" below................is what you will find most commonly on the internet search results */
# if (MY_ORACLE)
.AddDbContext<ProvisioningDbContext>(options => options.UseOracle(defaultConnectionStringValue), ServiceLifetime.Transient);
# endif
# if (MY_SQL_SERVER)
.AddDbContext<ProvisioningDbContext>(options => options.UseSqlServer(defaultConnectionStringValue), ServiceLifetime.Transient);
# endif
servColl.AddSingleton <IMySpecialObjectThatSpawnsThreads, MySpecialObjectThatSpawnsThreads>();
ServiceProvider servProv = servColl.BuildServiceProvider();
return servProv;
}
让我感到惊讶的是瞬变的(变化)
.AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>()
.AddTransient<IMySpecialObjectManager, MySpecialObjectManager>()
请注意,我认为是因为将IMySpecialObjectManager注入“ MySpecialObjectThatSpawnsThreads”中,所以这些注入的对象需要是Transient的才能完成链。
关键是......不仅仅是需要(My)DbContext .Transient ...而是更大的DI图块。
调试提示:
此行:
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
在此处放置调试器断点。如果您的MySpecialObjectThatSpawnsThreads正在创建N个线程(例如,说10个线程)……而该行仅被命中一次……那是您的问题。您的DbContext正在穿越线程。
奖金:
我建议您在下面的url / article(既是老书又是老书)中阅读有关Web应用程序和控制台应用程序之间差异的内容
https://mehdi.me/ambient-dbcontext-in-ef6/
如果链接发生更改,这是文章的标题。
使用实体框架6正确地管理DBCONTEXT:深入了解 指南Mehdi El Gueddari
我通过WorkFlowCore https://github.com/danielgerlag/workflow-core遇到了这个问题
<ItemGroup>
<PackageReference Include="WorkflowCore" Version="3.1.5" />
</ItemGroup>
下面的示例代码..帮助将来的互联网搜索者
namespace MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Workflows
{
using System;
using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Constants;
using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Glue;
using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.WorkflowSteps;
using WorkflowCore.Interface;
using WorkflowCore.Models;
public class MySpecialObjectInterviewDefaultWorkflow : IWorkflow<MySpecialObjectInterviewPassThroughData>
{
public const string WorkFlowId = "MySpecialObjectInterviewWorkflowId";
public const int WorkFlowVersion = 1;
public string Id => WorkFlowId;
public int Version => WorkFlowVersion;
public void Build(IWorkflowBuilder<MySpecialObjectInterviewPassThroughData> builder)
{
builder
.StartWith(context =>
{
Console.WriteLine("Starting workflow...");
return ExecutionResult.Next();
})
/* bunch of other Steps here that were using IMySpecialObjectManager.. here is where my DbContext was getting cross-threaded */
.Then(lastContext =>
{
Console.WriteLine();
bool wroteConcreteMsg = false;
if (null != lastContext && null != lastContext.Workflow && null != lastContext.Workflow.Data)
{
MySpecialObjectInterviewPassThroughData castItem = lastContext.Workflow.Data as MySpecialObjectInterviewPassThroughData;
if (null != castItem)
{
Console.WriteLine("MySpecialObjectInterviewDefaultWorkflow complete :) {0} -> {1}", castItem.PropertyOne, castItem.PropertyTwo);
wroteConcreteMsg = true;
}
}
if (!wroteConcreteMsg)
{
Console.WriteLine("MySpecialObjectInterviewDefaultWorkflow complete (.Data did not cast)");
}
return ExecutionResult.Next();
}))
.OnError(WorkflowCore.Models.WorkflowErrorHandling.Retry, TimeSpan.FromSeconds(60));
}
}
}
和
ICollection<string> workFlowGeneratedIds = new List<string>();
for (int i = 0; i < 10; i++)
{
MySpecialObjectInterviewPassThroughData currentMySpecialObjectInterviewPassThroughData = new MySpecialObjectInterviewPassThroughData();
currentMySpecialObjectInterviewPassThroughData.MySpecialObjectInterviewPassThroughDataSurrogateKey = i;
//// private readonly IWorkflowHost workflowHost;
string wfid = await this.workflowHost.StartWorkflow(MySpecialObjectInterviewDefaultWorkflow.WorkFlowId, MySpecialObjectInterviewDefaultWorkflow.WorkFlowVersion, currentMySpecialObjectInterviewPassThroughData);
workFlowGeneratedIds.Add(wfid);
}
答案 11 :(得分:0)
异步方法将向您发出警告消息,提示代码块将在没有等待的情况下同步运行(如果不存在)。删除等待的Task.Yield()后,我收到此错误。我没有使用Task.Run(async()=> await ...)模式。如前所述,同步线程现在同时被异步线程和同步线程引用了dbContext,这导致单元测试中“在上一个操作完成之前,在此上下文上启动了第二个操作”
答案 12 :(得分:0)
另一种可能的情况:如果您直接使用连接,请不要忘记关闭。我需要执行任意SQL查询,并读取结果。这是一个快速解决方案,我不想定义数据类,也不想设置“普通” SQL连接。因此,我简单地将EFC的数据库连接重用为FragmentContainerView
。在执行var connection = Context.Database.GetDbConnection() as SqlConnection
之前,请确保先致电connection.Close()
。
答案 13 :(得分:0)
在我的情况下,我使用的锁不允许您使用await,并且当您不等待异步时也不会创建编译器警告。
问题:
lock (someLockObject) {
// do stuff
context.SaveChangesAsync();
}
// some other code somewhere else doing await context.SaveChangesAsync() shortly after the lock gets the concurrency error
解决方法: 通过使用.Wait()使其阻塞来等待锁内的异步
lock (someLockObject) {
// do stuff
context.SaveChangesAsync().Wait();
}
答案 14 :(得分:0)
我知道这个问题已经在两年前问过了,但是我刚刚遇到了这个问题,我使用的修复程序确实起到了帮助。
如果您要使用相同的上下文进行两个查询-您可能需要删除AsNoTracking
。如果您确实使用AsNoTracking
,则将为每次读取创建一个新的数据读取器。两个数据读取器无法读取相同的数据。
答案 15 :(得分:0)
就我而言,我在Blazor中使用了模板组件。
<BTable ID="Table1" TotalRows="MyList.Count()">
问题是在组件标头中调用方法(Count)。 为了解决此问题,我将其更改为:
int total = MyList.Count();
及更高版本:
<BTable ID="Table1" TotalRows="total">
答案 16 :(得分:0)
我设法通过将IQueryable
传递到一个方法中来解决该错误,该方法随后将该IQueryable'list'用作对同一上下文的另一个查询的一部分。
public void FirstMethod()
{
// This is returning an IQueryable
var stockItems = _dbContext.StockItems
.Where(st => st.IsSomething);
SecondMethod(stockItems);
}
public void SecondMethod(IEnumerable<Stock> stockItems)
{
var grnTrans = _dbContext.InvoiceLines
.Where(il => stockItems.Contains(il.StockItem))
.ToList();
}
为阻止这种情况的发生,我通过将对SecondMethod
的调用更改为SecondMethod(stockItems.ToList()
答案 17 :(得分:0)
当我尝试在下面的代码的async方法中使用FirstOrDefaultAsync()
时,我遇到了同样的问题。当我修复FirstOrDefault()
时-问题解决了!
_context.Issues.Add(issue);
await _context.SaveChangesAsync();
int userId = _context.Users
.Where(u => u.UserName == Options.UserName)
.FirstOrDefaultAsync()
.Id;
...
答案 18 :(得分:0)
我有一个后台服务,该服务为表中的每个条目执行操作。问题是,如果我遍历并修改DbContext的同一实例上的所有数据,则会发生此错误。
如该线程中所述,一种解决方案是通过像这样定义DbContext的生存期以将其更改为瞬态
services.AddDbContext<DbContext>(ServiceLifetime.Transient);
但是因为我在多个不同的服务中进行了更改,并使用SaveChanges()
方法一次提交了它们,所以该解决方案在我的情况下不起作用。
因为我的代码在服务中运行,所以我正在做类似的事情
using (var scope = Services.CreateScope())
{
var entities = scope.ServiceProvider.GetRequiredService<IReadService>().GetEntities();
var writeService = scope.ServiceProvider.GetRequiredService<IWriteService>();
foreach (Entity entity in entities)
{
writeService.DoSomething(entity);
}
}
能够像使用简单请求一样使用该服务。因此,要解决此问题,我只将单个作用域分为两个,一个用于查询,另一个用于写操作,如下所示:
using (var readScope = Services.CreateScope())
using (var writeScope = Services.CreateScope())
{
var entities = readScope.ServiceProvider.GetRequiredService<IReadService>().GetEntities();
var writeService = writeScope.ServiceProvider.GetRequiredService<IWriteService>();
foreach (Entity entity in entities)
{
writeService.DoSomething(entity);
}
}
实际上,实际上使用了两个不同的DbContext实例。
另一种可能的解决方案是确保在开始迭代之前读取操作已终止。就我而言,这不是很实际,因为可能需要将很多结果全部都加载到内存中才能执行该操作,而我首先尝试使用Queryable来避免这种情况。
答案 19 :(得分:0)
我认为这个答案仍然可以帮助一些人并节省很多时间。我通过将IQueryable
更改为List
(或更改为数组,集合...)解决了类似的问题。
例如:
var list=_context.table1.where(...);
到
var list=_context.table1.where(...).ToList(); //or ToArray()...
答案 20 :(得分:0)
我收到了同样的消息。但这对我来说毫无意义。我的问题是我错误地使用了“ NotMapped”属性。 在某些情况下,它可能仅意味着Linq语法或模型类错误。该错误消息似乎具有误导性。此消息的原始含义是,您不能在同一请求中多次在同一dbcontext上调用异步。
[NotMapped]
public int PostId { get; set; }
public virtual Post Post { get; set; }
您可以查看此链接以获取详细信息, https://www.softwareblogs.com/Posts/Details/5/error-a-second-operation-started-on-this-context-before-a-previous-operation-completed
答案 21 :(得分:-1)
我有一个类似的问题。
我通过从使用foreach
循环更改为使用for
循环来解决我的问题。
答案 22 :(得分:-1)
您可以使用 SemaphoreSlim 来阻止将尝试执行该 EF 调用的下一个线程。
import React, { Component } from "react";
import propTypes from "prop-types";
import Button from "elements/Button";
import { InputNumber, InputDate } from "elements/Form";
export default class BookingForm extends Component {
constructor(props) {`enter code here`
super(props);
this.state = {
data: {
duration: 1,
date: {
startDate: new Date(),
endDate: new Date(),
key: "selection",
},
},
};
}
updateData = (e) => {
this.setState({
...this.state,
data: {
...this.state.data,
[e.target.name]: e.target.value,
},
});
};
componentDidUpdate(prevProps, prevState) {
const { data } = this.state;
if (prevState.data.date !== data.date) {
const startDate = new Date(data.date.startDate);
const endDate = new Date(data.date.endDate);
const countDuration = new Date(endDate - startDate).getDate();
this.setState({
data: {
...this.state.data,
duration: countDuration,
},
});
}
if (prevState.data.duration !== data.duration) {
const starDate = new Date(data.date.startDate);
const endDate = new Date(
starDate.setDate(starDate.getDate() + +data.duration - 1)
);
this.setState({
...this.state,
data: {
...this.state.data,
date: {
...this.state.data.date,
endDate: endDate,
},
},
});
}
}
render() {
const { data } = this.state;
const { itemDetails, startBooking } = this.props;
return (
<div className="card bordered" style={{ padding: "60px 80px" }}>
<h4 className="mb-3">Start Booking</h4>
<h5 className="h2 text-teal mb-4">
${itemDetails.price}{" "}
<span className="text-gray-500 font-weight-light">
per {itemDetails.unit}
</span>
</h5>
<label htmlFor="duration">How long you will stay?</label>
<InputNumber
max={30}
suffix={" night"}
isSuffixPlural
onChange={this.updateData}
name="duration"
value={data.duration}
/>
<label htmlFor="date">Pick a date</label>
<InputDate onChange={this.updateData} name="date" value={data.date} />
<h6
className="text-gray-500 font-weight-light"
style={{ marginBottom: 40 }}
>
You will pay{" "}
<span className="text-gray-900">
${itemDetails.price * data.duration} USD
</span>{" "}
<span className="text-gray-900">
{data.duration} {itemDetails.unit}
</span>
</h6>
<Button
className="btn"
hasShadow
isPrimary
isBlock
onClik={startBooking}
>
Continue to Book
</Button>
</div>
);
}
}
BookingForm.propTypes = {
itemDetails: propTypes.object,
startBooking: propTypes.func,
};
答案 23 :(得分:-2)
如果您的方法返回了某些内容,则可以通过以下方法解决此错误:
.Result
至工作结束,
.Wait()
(如果没有返回任何内容)。
答案 24 :(得分:-3)
我只是设法让它再次运作。它没有多大意义,但它起作用了:
我稍后会进一步调查,但是我用hangfire调用的方法会收到一个DBContext,这是可能的原因。