7

我正在尝试从 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

4 回答 4

8

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

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

于 2013-11-06T16:45:32.450 回答
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 识别。结合惰性求值和 AsNoTracking,无需分页,可以推迟上下文销毁。

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

于 2013-11-06T17:24:22.013 回答
1

一些东西。

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

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

以这种方式使用,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);
}
于 2013-11-06T16:47:02.400 回答
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 操作。对于更专业的解决方案,处理数百万行是一个很好的用例。

于 2013-11-06T16:43:52.563 回答