首页 > 解决方案 > 使用 LINQ Lambda 选择的 ASP.Net MVC 异步导致“在前一个操作完成之前,在此上下文上启动了第二个操作。”

问题描述

我一直在与被证明难以追踪涉及 LINQ Lambda 选择中的等待任务的问题作斗争。

注意:下面的代码已被简化并使用演示命名,即 Town、Region 等。但工作流程与出现问题的原始代码相同。

        public async Task<IEnumerable<TownSearchOutputDto>> GetTowns(TownSearchInputDto input)
        {
            var numResults = 25;

            var query = _townRepository.GetAll()
                                          .Include(t => t.Tests)
                                          .Include(t => t.Region.Select(s => s.SubRegion))
                                          .Include(t => t.Province); // Eager load relations.

            // Filter based on CountyId.
            if (input.CountyId > 0)
            {
                query = query.Where(t => t.CountyId == input.CountyId);
            }

            var townList = await query
                                    .OrderBy(t => t.Name)
                                    .Take(numResults)
                                    .ToListAsync();

            IEnumerable<Task<TownSearchOutputDto>> tasks = townList.Select(async t => {
                var townLevel = this.GetTownLevel(t);
                bool hasSomeSpecialTownProperty = false;
                if (townLevel.Key > 1)
                {
                    hasSomeSpecialTownProperty = await this.CheckTownSpecialProperty(t);
                }
                return new TownSearchOutputDto
                {
                    Id = t.Id,
                    CountyId = t.CountyId,
                    RegionName = t.Region.Name,
                    SubRegionName = t.SubRegion.Name,
                    ProvinceName = t.Province.Name,
                    TownLevelOrder = townLevel.Key,
                    TownLevelName = townLevel.Value,
                    HasSomeSpecialTownProperty = hasSomeSpecialTownProperty,
                    Disabled = townLevel.Key > 2 || t.Tests.Any(a => a.TownId == t.Id)
                };
            }).ToList();

            var towns = await Task.WhenAll(tasks);

            return towns;
        }

        private KeyValuePair<int, string> GetTownLevel(Town town)
        {
            if (!string.IsNullOrWhiteSpace(town.Tiny))
            {
                return new KeyValuePair<int, string>(3, "Tiny");
            }
            else if (!string.IsNullOrWhiteSpace(town.Small))
            {
                return new KeyValuePair<int, string>(2, "Small");
            }
            else if (!string.IsNullOrWhiteSpace(town.Big))
            {
                return new KeyValuePair<int, string>(1, "Big");
            }
            return new KeyValuePair<int, string>(-1, null);
        }

        private async Task<bool> CheckTownSpecialProperty(Town town)
        {
            // If no town is found then we cannot continue.
            if (town is null)
            {
                throw new UserFriendlyException("Town not found. Cannot continue!");
            }

            var townLevel = this.GetTownLevel(town);

            if(townLevel.Key == 1) // This is big town level, exit early. We are just testing against smaller towns.
            {
                return false;
            }

            // Find any big town of the same name also being tested.
            // This is where the issue occurs.
            // In debug mode all works well but when running freely, I get: 
            // "A second operation was started on this context before a previous operation completed."
            var townList = await _townRepository.GetAll()
                                            .Include(t => t.Tests)
                                            .Include(t => t.Region.Select(s => s.SubRegion))
                                            .Include(t => t.Province); // Eager load relations.
                .Where(t => t.Id != town.Id
                                && t.Region.Name == town.Region.Name
                                && t.Name == town.Name
                                && t.SubRegion.Name == town.SubRegion.Name
                                && t.Tests.Any(a => a.TownId == t.Id))
                .ToListAsync();

            // If we have found a big town also being tested return true.
            if (townList.Any(t => this.GetTownLevel(t).Key == 1))
            {
                return true;
            }

            return false;
        }

我试图解决这个问题的事情

  1. 制作CheckTownSpecialProperty()同步。但这违背了 GetTowns 一开始就异步的目的,它实际上只是为了让我知道该功能工作正常。
  2. 我尝试添加 using 块并手动控制上下文实例。我推测这会起作用,因为将为每个任务重新创建上下文实例。
    • 但这会导致:"The operation cannot be completed because the DbContext has been disposed."
    • 我也在 GetTowns() 函数中尝试了相同的方法,并"The operation cannot be completed because the DbContext has been disposed."引发了相同的错误。
        List<Town> townList = null;
        using (var context = _abpContext.GetDbContext())
            {     
                townList = await context.Towns
                                .Include(t => t.Tests)
                                .Include(t => t.Region.Select(s => s.SubRegion))
                                .Include(t => t.Province); // Eager load relations.
                .Where(t => t.Id != town.Id
                                && t.Region.Name == town.Region.Name
                                && t.Name == town.Name
                                && t.SubRegion.Name == town.SubRegion.Name
                                && t.Tests.Any(a => a.TownId == t.Id))
                .ToListAsync();
            }
  1. 通过 . 关闭 using 块中上下文的延迟加载context.Configuration.LazyLoadingEnabled = false;。我希望完全实现对象可以防止错误,但这没有任何区别。
  2. 添加AsNoTracking()到查询。这是最后一次尝试,看看我是否可以完全实现对象并防止并发问题。可悲的是它没有用。

标签: c#asp.net-mvcentity-frameworklinqasync-await

解决方案


你的问题在GetTowns. 这是该代码正在执行的操作:

  1. 创建一个query.
  2. _townRepository针对get异步执行该查询townList
  3. 启动几个任务,其中几个可能正在调用CheckTownSpecialProperty.
  4. 使用 . 异步等待所有这些任务完成Task.WhenAll

所以CheckTownSpecialProperty可能会被同时调用,并且所有那些并发执行都使用相同的_townRepository. 这是不允许的。

要解决此问题,请执行以下任一操作:

  1. 使您的原始文件query更复杂(例如,进行连接),这样就不需要进行第二次查找了。
  2. 在 中新建一个存储库CheckTownSpecialProperty,以便每个并发执行都有自己的存储库。
  3. 更改逻辑,以便CheckTownSpecialProperty一次运行一个辅助查询。它们仍然可以是异步的,但是是串行的。
  4. 将辅助查询组合成一个辅助查询。

推荐阅读