【问题标题】:Improve EFCore Query for fast operation改进 EFCore 查询以实现快速操作
【发布时间】:2021-08-26 05:43:51
【问题描述】:

我读了一个sqlite数据库(数据库大小大约3 MB,所以信息不多,每个表大约1或2千行)并从中提取信息,然后我将这些信息添加到新的数据库。 整个操作大约需要 40 秒。

如何缩短此时间并尽快完成操作? (任务、并行、异步...)

我目前正在使用此代码:

await Task.Run(async () =>
            {
                var pkgs = new ManifestTable();
                var mydb = new dbContext();
                await mydb.Database.EnsureDeletedAsync();
                await mydb.Database.EnsureCreatedAsync();
                using (var msixDB = new MSIXContext())
                {
                    foreach (var item in await msixDB.IdsMSIXTable.ToListAsync())
                    {
                        var rowId = item.rowid;
                        var manifests = await msixDB.Set<ManifestMSIXTable>().Where((e) => e.id == rowId).ToListAsync();

                        foreach (var manifest in manifests)
                        {
                            pkgs = new ManifestTable();
                            pkgs.PackageId = item.id;


                            var productMap = await msixDB.ProductCodesMapMSIXTable.FirstOrDefaultAsync((e) => e.manifest == manifest.rowid);
                            if (productMap != null)
                            {
                                var prdCode = await msixDB.ProductCodesMSIXTable.FirstOrDefaultAsync((e) => e.rowid == productMap.productcode);
                                if (prdCode != null)
                                {
                                    pkgs.ProductCode = prdCode.productcode;
                                }
                            }
                            var publisherMap = await msixDB.Set<PublishersMapMSIXTable>().FirstOrDefaultAsync((e) => e.manifest == manifest.rowid);

                            if (publisherMap != null)
                            {
                                var publisher = await msixDB.PublishersMSIXTable.FirstOrDefaultAsync((e) => e.rowid == publisherMap.norm_publisher);

                                if (publisher != null)
                                {
                                    pkgs.Publisher = publisher.norm_publisher;
                                }
                            }

                            var pathPart = manifest.pathpart;
                            var yml = await msixDB.PathPartsMSIXTable.FirstOrDefaultAsync((e) => e.rowid == pathPart);
                            if (yml != null)
                            {
                                pkgs.YamlName = yml.pathpart;
                            }

                            var version = await msixDB.VersionsMSIXTable.FirstOrDefaultAsync((e) => e.rowid == manifest.version);
                            if (version != null)
                            {
                                pkgs.Version = version.version;
                            }
                            await mydb.ManifestTable.AddAsync(pkgs);
                        }
                    }
                     await mydb.SaveChangesAsync();
                }

            });

【问题讨论】:

    标签: c# wpf async-await entity-framework-core task


    【解决方案1】:

    在尝试并行执行等操作之前,您应该先查看是否有任何算法改进。

    您有两个嵌套循环,因此如果每个表有几千行,则内部循环主体将以 10^6 的幅度运行,这并不可怕,但数量相当。

    在内部循环中,您将运行一大堆FirstOrDefaultAsync 语句。如果这些没有索引,则需要扫描所有行,这会很慢。因此,首先要确保所有表都有适当的索引。这样做是为了确保在恒定时间内搜索特定项目。

    您似乎还在使用相同的参数重复查找PublishersMapMSIXTable。避免不必要的重复操作应该是首先要解决的问题之一,因为这只是浪费循环。

    如果整个操作在后台线程上运行,所有异步调用不太可能有太大帮助,它会节省一点内存,但会导致线程之间出现一些反弹。因此,如果重要的常规同步方法的性能可能会更快一些。

    与以往一样,关于性能,衡量。一个好的性能分析器应该告诉你大部分时间都花在了哪些地方,如果你没有秒表,添加一些秒表很容易。如果他们试图猜测慢的部分是什么,即使是非常有经验的程序员也可能完全错误。

    【讨论】:

      【解决方案2】:

      将数据库视为对象存储是有史以来最糟糕的想法。您必须尽可能减少数据库往返。在您的情况下 - 只需一个请求。如果您不知道哪个部分慢,也不要玩 Task.Run、Parallel 等。在您的情况下 - 数据库往返。

      var mydb = new dbContext();
      await mydb.Database.EnsureDeletedAsync();
      await mydb.Database.EnsureCreatedAsync();
      
      using (var msixDB = new MSIXContext())
      {
          var query = 
              from item in msixDB.IdsMSIXTable
              from manifest in msixDB.Set<ManifestMSIXTable>().Where(e => e.id == item.rowId)
              from productMap in msixDB.ProductCodesMapMSIXTable.Where(e => e.manifest == manifest.rowid).Take(1).DefaultIfEmpty()
              from prdCode in msixDB.ProductCodesMSIXTable.Where(e => e.rowid == productMap.productcode).Take(1).DefaultIfEmpty();
              from publisherMap in msixDB.Set<PublishersMapMSIXTable>().Where(e => e.manifest == manifest.rowid).Take(1).DefaultIfEmpty()
              from publisher in msixDB.PublishersMSIXTable.Where(e => e.rowid == publisherMap.norm_publisher).Take(1).DefaultIfEmpty()
              from yml in msixDB.PathPartsMSIXTable.Where(e => e.rowid == manifest.pathpart).Take(1).DefaultIfEmpty()
              from version in msixDB.VersionsMSIXTable.Where(e => e.rowid == manifest.version).Take(1).DefaultIfEmpty()
              select new ManifestTable
              {
                  PackageId = item.id,
                  ProductCode = prdCode.productcode,
                  Publisher = publisher.norm_publisher,
                  YamlName = yml.pathpart,
                  Version = version.version
              };
      
          mydb.ManifestTable.AddRange(await query.ToListAsync());
          await mydb.SaveChangesAsync();
      }
      

      【讨论】:

      • 谢谢,我应该把这段代码放到foreach循环中吗?
      • 可以使用代码了。不需要foreach
      • 因为我收到错误错误 CS0103: The name 'manifest' does not exist in the current context
      • 只是错字。固定。
      • 是的,谢谢,现在有2个新问题,prdCode和publisher不存在
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-03-29
      • 2011-07-30
      • 1970-01-01
      • 1970-01-01
      • 2020-07-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多