繁体   English   中英

HostedService:无法跟踪实体类型的实例

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM