【问题标题】:Optimizing Entity Framework queries for import优化实体框架查询以进行导入
【发布时间】:2016-07-20 23:49:53
【问题描述】:

因为我已经从上下文MICSYDBCONTEXT 的数据库中导入数据到BRIA_REALDBCONTEXT 的数据库中。

由于MICSYDBCONTEXT 数据库中有 280'000 条记录,我发现仅导入 10000 条记录需要 5 个小时,因此我迫切需要对我的查询进行某种优化。我正在使用 Entity Framework 6,作为初级开发人员,我将不胜感激。

using (var context = new MICSYDBContext())
{
    var Doc = context.DOCUMENTS.OrderBy(x => x.ID).Take(100);

    try
    {
        using (var context2 = new BRIA_REALDBCONTEXT())
        {
            Stopwatch sw = Stopwatch.StartNew();

            foreach (var item in Doc)
            {
              var DOC = new DLV_DOC();
              DOC.FOLDID = item.ID;
              DOC.FOBJECT = 1;
              DOC.FIO = 40;              
              DOC.FID = newId;
              DOC.FACTIVE = "Y";
              DOC.FCODEWRT = 85;
              DOC.FDOC_COUNT_ORIGINAL = 'N';
              DOC.FUSERINSERT = 85;
              DOC.FMODULE = 1;
              DOC.FSERVICE_TYPE = 1;

              var micsysserviceName = context.DEFDOCTYPE.Where(x => x.ID == item.DOCTYPE).FirstOrDefault();

              if (micsysserviceName != null)
              {
                var service = context2.DLV_SSERVICE.Where(x => x.FNAME == micsysserviceName.NAME && x.FSERVICE_TYPE == 1).FirstOrDefault();

                if (service != null)
                {
                  DOC.FSERVICE = service.FID;
                }
              }
              else
              {
                DOC.FSERVICE = 1;
              }

              var personcorespondent = context2.UCM_PERSON.Where(x => x.FOLD_ID == item.DOCKORID.ToString()).FirstOrDefault();

              if (personcorespondent != null)
              {
                DOC.FPERSON = personcorespondent.FID;
              }
              else
              { 
                  DOC.FPERSON = 142; 
              }

              DOC.FDATEDOC = item.DATA;

              foreach (var docsteps in context.DOCMOVE.Where(x => x.DOCID == item.ID).OrderBy(x => x.DATA))
              {//СТъпка
                DLV_DOC_STEP DLVSTEP = new DLV_DOC_STEP();
                DLVSTEP.FDOC = DOC.FID;
                DLVSTEP.FOBJECT = 1;
                DLVSTEP.FCODEWRT = 85;


                var whofirst = context2.UCM_PERSON.Where(x => x.FOLD_ID == docsteps.WHOFIRST.ToString()).FirstOrDefault();
                if (whofirst != null)
                {
                  DLVSTEP.FPERSON = whofirst.FID;
                }
                var whonext = context2.UCM_PERSON.Where(x => x.FOLD_ID == docsteps.WHONEXT.ToString()).FirstOrDefault();
                if (whonext != null)
                {
                  DLVSTEP.FTOPERSON = whonext.FID;
                }

                context2.DLV_DOC_STEP.Add(DLVSTEP);
              }

              context2.DLV_DOC.Add(DOC);
              i++;
              Console.WriteLine("Added " +i + "DOC");
            }
            context2.SaveChanges();

            Console.WriteLine("End of docs");
            Console.WriteLine(sw.Elapsed.Hours);
          }
        }
        catch (Exception ex)
        {
          string s = ex.Message;
          Console.WriteLine("Problem with record");
          Console.WriteLine(ex.Message);
          Console.WriteLine(ex.StackTrace);
          Console.WriteLine(ex.InnerException);
        }
      }

【问题讨论】:

  • 您要订购 280000 条记录。这需要一段时间。

标签: c# entity-framework-6


【解决方案1】:

这个问题很常见,

我们可以把它分为三类:

  • Add 与 AddRange 性能
  • 读取和数据库往返
  • 写入和数据库往返

Add vs AddRange 性能

每次添加新记录时,Add 方法都会尝试检测更改,而 AddRange 只执行一次。每次检测更改可能需要几分钟时间。

这个问题很容易解决,只需创建两个列表,将实体添加到此列表中,然后在列表末尾使用 AddRange。

var DLV_DOC_STEPS = new List<DLV_DOC_STEP>();
var DLV_DOCS = new List<DLV_DOC>();

foreach(...)
{
    foreach(...)
    {
        DLV_DOC_STEPS.Add(DLVSTEP);
    }

    DLV_DOCS.Add(DOC);
}

context2.DLV_DOC_STEP.AddRange(DLV_DOC_STEPS);
context2.DLV_DOC.AddRange(DLV_DOC);
context2.SaveChanges

另一种解决方案是在开始时禁用 AutoDetectChange && 在 SaveChanges 之前重新启用它。我推荐第一个解决方案,但两者都有效。

ctx.Configuration.AutoDetectChangesEnabled = false;
foreach(var item in Doc)
{
    // ...code...
}

ctx.Configuration.AutoDetectChangesEnabled = true;
ctx.SaveChanges();

读取和数据库往返

应用程序执行的数据库往返次数过多

您多次在以下实体中进行查询:

  • DEFDOCTYPE
  • DLV_SSERVICE
  • UCM_PERSON
  • DOCMOVE

您执行数百万次数据库往返非常疯狂,因此您的应用程序当然非常慢!

解决方案?

  • 尝试先加载所有数据,然后改用字典从内存中获取实体(如果表不包含太多记录)。

例子:

var defDoctTypeDict = context.DEFDOCTYPE.AsNoTracking().ToList().ToDictionary(x => x.ID);

foreach(var item in Doc)
{
    DEFDOCTYPE micsysserviceName;

    defDoctTypeDict.TryGetValue(item.DOCTYPE, out micsysserviceName);
    if(micsysserviceName != null)
    {
        // ...code...
    }

    // ...code...
}
  • 尝试通过创建列表并改用 List.Contains 来批量加载数据 您可以在此处阅读遇到类似问题的人:https://stackoverflow.com/a/38355262/5619143

  • 如果您不需要跟踪实体,请使用 AsNoTracking

写入和数据库往返

每次保存记录时,都会执行一次数据库往返。

免责声明:我是项目的所有者Entity Framework Extensions

这个库允许执行:

  • 批量保存更改
  • 批量插入
  • 批量更新
  • 批量删除
  • 批量合并

您可以在批处理结束时调用 BulkSaveChanges 或创建一个列表以插入并直接使用 BulkInsert 来获得更高的性能。

BulkSaveChanges 解决方案(比 SaveChanges 快得多)

context2.DLV_DOC_STEP.AddRange(DLV_DOC_STEPS);
context2.DLV_DOC.AddRange(DLV_DOC);
context2.BulkSaveChanges

BulkInsert 解决方案(比 BulkSaveChanges 最快,但不保存相关实体)

context2.BulkInsert(DLV_DOC_STEPS);
context2.BulkInsert(DLV_DOC);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-27
    • 1970-01-01
    相关资源
    最近更新 更多