[英]HostedService: The instance of entity type cannot be tracked
我正在开发一个带有 asp.net core 2.2 和 ef core 2.2.1 的 web api。 api 除了处理 angular 应用程序发出的 restful 请求外,还负责处理一些 xml 文件,这些文件用作与其他软件的接口。 文件位于应用程序服务器本地,并通过FileWatcher
检测。
我在测试期间注意到,当我多次重新处理一个 xml 测试文件时,从第二次重新处理文件开始,我获得了异常:
System.InvalidOperationException:无法跟踪实体类型“QualityLot”的实例,因为已跟踪具有键值“{QualityLotID: ...}”的另一个实例。 附加现有实体时,请确保仅附加一个具有给定键值的实体实例。
当我调用方法DbContext.QualityLot.Update(qualityLot);
“处理文件”服务及其使用的服务配置到Startup.cs
文件中,如下所示:
services.AddHostedService<InterfaceDownloadService>();
services.AddTransient<IQLDwnldService, QLDwnldService>();
db 上下文配置如下:
services.AddDbContext<MyDbContext>(cfg =>
{
cfg.UseSqlServer(_config.GetConnectionString("LIMSConnectionString"));
});
这个类看起来像:
public class InterfaceDownloadService : BackgroundServiceBase
{
[...]
public InterfaceDownloadService(IHostingEnvironment env,
ILogger<InterfaceDownloadService> logger,
IServiceProvider serviceProvider)
{
_ServiceProvider = serviceProvider;
}
[...]
private void processFiles()
{
[...]
_ServiceProvider.GetService<IQLDwnldService>().QLDownloadAsync(ev);
}
}
public abstract class BackgroundServiceBase : IHostedService, IDisposable
{
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts =
new CancellationTokenSource();
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it,
// this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
}
这是关键点,我有例外:
public async Task QLDownloadAsync(FileReceivedEvent fileReceivedEvent)
{
Logger.LogInformation($"QLDwnld file {fileReceivedEvent.Event.FullPath} received for Processing");
try
{
QualityLotDownload qualityRoutingDwnld = deserializeObject<QualityLotDownload>(fileReceivedEvent.XsltPath, fileReceivedEvent.Event.FullPath);
Logger.LogDebug($"QLDwnld file {fileReceivedEvent.Event.FullPath} deserialized correctly. Need to determinate whether Insert or Update QualityLot {qualityRoutingDwnld.QualityLots.QualityLot.QualityLotID}");
for (int remainingRetries = fileReceivedEvent.MaxRetries; remainingRetries > 0; remainingRetries--)
{
using (var transaction = await DbContext.Database.BeginTransactionAsync())
{
try
{
var qualityLotDeserialized = qualityRoutingDwnld.QualityLots.QualityLot;
// insert the object into the database
var qualityLot = await DbContext.QualityLot.Where(x => x.QualityLotID == qualityLotDeserialized.QualityLotID).FirstOrDefaultAsync();
if (qualityLot == null) // INSERT QL
{
await InsertQualityLot(qualityLotDeserialized);
}
else // UPDATE QL
{
await UpdateQualityLot(qualityLot, qualityLotDeserialized);
}
[...]
transaction.Commit();
}
catch (Exception ex)
{
Logger.LogError(ex, $"Retry {fileReceivedEvent.MaxRetries - remainingRetries +1}: Exception processing QLDwnld file {fileReceivedEvent.Event.FullPath}.");
transaction.Rollback();
if (remainingRetries == 1)
{
return;
}
}
方法UpdateQualityLot(qualityLot, qualityLotDeserialized);
被调用是因为实体已经存在于数据库中
private async Task UpdateQualityLot(QualityLot qualityLot, QualityLotDownloadQualityLotsQualityLot qualityLotDeserialized)
{
[fields update]
DbContext.QualityLot.Update(qualityLot);
await DbContext.SaveChangesAsync();
}
调用DbContext.QualityLot.Update(qualityLot);
失败。
从我所看到的QLDwnldService
的实例对于正在处理的每个文件QLDwnldService
都是新的,换句话说,每次新对象(如配置到 Startup.cs 中)时,以下方法都会返回
_ServiceProvider.GetService<IQLDwnldService>().QLDownloadAsync(ev);
,而 DbContext 被重用,这可能是实体结果已经被跟踪的原因。
我也尝试在 DbContext OnConfiguring()
设置非跟踪选项
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
所以我的问题是。 这里有什么问题? 可能是架构有问题,还是 e 核的配置有误导性? 在此先感谢您的任何支持。
老实说,我无法弄清楚您的 DBContext 是从您的代码中实际注入的位置。
但是根据错误消息,我会说您的上下文在不应该出现的地方被重用。 所以它被注射一次,然后一遍又一遍地使用。
您已将服务注册为“Scoped”(因为这是默认设置)。
您应该将其注册为“Transient”,以确保每次调用您的服务提供商时都会获得一个新实例:
services.AddDbContext<MyDbContext>(cfg =>
{
cfg.UseSqlServer(_config.GetConnectionString("LIMSConnectionString"));
},
ServiceLifetime.Transient);
布拉德提到这将对您申请的其余部分产生影响,他是对的。
更好的选择可能是保留DbContext
范围并将IServiceScopeFactory
注入您的托管服务。 然后在需要的地方创建一个新范围:
using(var scope = injectedServiceScopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetService<DbContext>();
// do your processing with context
} // this will end the scope, the scoped dbcontext will be disposed here
请注意,这仍然并不意味着您应该并行访问 DbContext。 我不知道为什么你的电话都是异步的。 如果您实际上是在进行并行工作,请确保为每个线程创建一个 DbContext。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.