如何在EF5中使用大型数据集减少内存占用?

时间:2013-11-06 16:35:44

标签: c# entity-framework-5

我正在尝试从SQL Server中提取大型数据集(140万条记录)并转储到WinForms应用程序中的文件。我试图用分页来做这件事,所以我不会立刻在内存中占用太多,但是这个过程会继续增加内存占用量。大约25%通过,它占了600,000K。我在做错分页吗?我可以就如何保持内存使用量不断增长得到一些建议吗?

var query = (from organizations in ctxObj.Organizations
                 where organizations.org_type_cd == 1
                 orderby organizations.org_ID
                 select organizations);
int recordCount = query.Count();
int skipTo = 0;
int take = 1000;
if (recordCount > 0)
{
    while (skipTo < recordCount)
    {
        if (skipTo + take > recordCount) 
            take = recordCount - skipTo;

        foreach (Organization o in query.Skip(skipTo).Take(take))
        {
            writeRecord(o);
        }
        skipTo += take;
    }
}

4 个答案:

答案 0 :(得分:8)

对象上下文将保留在内存中的对象上,直到它被丢弃为止。我建议在每批之后处理上下文,以防止内存占用继续增长。

您也可以使用AsNoTracking()http://msdn.microsoft.com/en-us/library/gg679352(v=vs.103).aspx),因为您没有保存回数据库。

答案 1 :(得分:5)

摆脱分页并使用AsNoTracking

测试代码

 static void Main(string[] args)
        {
            var sw = new Stopwatch();
            sw.Start();
            using (var context = new MyEntities())
            {
                var query = (from organizations in context.LargeSampleTable.AsNoTracking()
                             where organizations.ErrorID != null
                             orderby organizations.ErrorID
                             select organizations);//large sample table, 146994 rows

                foreach (MyObject o in query)
                {
                    writeRecord(o);
                }

            }
            sw.Stop();

            Console.WriteLine("Completed after: {0}", sw.Elapsed);
            Console.ReadLine();
        }

        private static void writeRecord(ApplicationErrorLog o)
        {
            ;
        }

测试用例结果:

记忆消耗减少: 96%
执行时间缩短: 50%

<强>解释

由于显而易见的原因,AsNoTracking为内存使用带来了好处,因此我们在将实体加载到内存时不必维护对实体的引用。物体几乎可以立即被GC读取。结合lazy evaluation和AsNoTracking,不需要分页和上下文销毁。

虽然这是一次测试,但是大量的行和大多数外部因素的排除使得它成为一般情况的良好表示。

答案 2 :(得分:1)

一些事情。

  1. 调用Count()运行您的查询。然后,您再次运行它以获得结果。你不需要这样做。

  2. 您看到的内存是由于将实体加载到内存中。如果您只需要字段的子集,则投影到匿名类型(或更简单的命名类型。)这将避免任何更改跟踪和开销。

  3. 以这种方式使用,EF可以成为轻量级SQL查询的一个很好的强类型API。

    这样的事情可以解决问题:

    var query = from organizations in ctxObj.Organizations
                 where organizations.org_type_cd == 1
                 orderby organizations.org_ID
                 select new { o.Id, o.Name };
    
    foreach (var org in query)
    {
        write(org.Id, org.Name);
    }
    

答案 3 :(得分:0)

为什么不使用标准System.Data.SqlClient.SqlConnection课程?您可以使用SqlDataReader类逐行读取命令的结果,并将每行写入文件。您可以完全控制以确保您的代码一次仅引用一行记录。

using (var writer = new System.IO.StreamWriter(fileName))
using (var conn = new SqlConnection(connectionString))
{
    using (var cmd = new SqlCommand())
    {
        cmd.CommandText = "SELECT * FROM Organizations WHERE org_type_cd = 1 ORDER BY org_ID";

        using (var reader = cmd.ExecuteReader())
        {
            while (reader.Read())
            {
                int id = (int)reader["org_ID"];
                int org_type_cd = (int)reader["org_type_cd"];

                writer.WriteLine(...);
            }
        }
    }
}

实体框架并非旨在解决所有问题或成为您的专有数据访问框架。这意味着更容易编写简单的CRUD操作。处理数百万行是一个很好的用例,可用于更专业的解决方案。