Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>As you've already identified, you cannot save from within a <code>foreach</code> that is still drawing from the database via an active reader.</p> <p>Calling <code>ToList()</code> or <code>ToArray()</code> is fine for small data sets, but when you have thousands of rows, you will be consuming a large amount of memory.</p> <p>It's better to load the rows in chunks.</p> <pre><code>public static class EntityFrameworkUtil { public static IEnumerable&lt;T&gt; QueryInChunksOf&lt;T&gt;(this IQueryable&lt;T&gt; queryable, int chunkSize) { return queryable.QueryChunksOfSize(chunkSize).SelectMany(chunk =&gt; chunk); } public static IEnumerable&lt;T[]&gt; QueryChunksOfSize&lt;T&gt;(this IQueryable&lt;T&gt; queryable, int chunkSize) { int chunkNumber = 0; while (true) { var query = (chunkNumber == 0) ? queryable : queryable.Skip(chunkNumber * chunkSize); var chunk = query.Take(chunkSize).ToArray(); if (chunk.Length == 0) yield break; yield return chunk; chunkNumber++; } } } </code></pre> <p>Given the above extension methods, you can write your query like this:</p> <pre><code>foreach (var client in clientList.OrderBy(c =&gt; c.Id).QueryInChunksOf(100)) { // do stuff context.SaveChanges(); } </code></pre> <p><strong>The queryable object you call this method on must be ordered.</strong> This is because Entity Framework only supports <code>IQueryable&lt;T&gt;.Skip(int)</code> on ordered queries, which makes sense when you consider that multiple queries for different ranges require the ordering to be stable. If the ordering isn't important to you, just order by primary key as that's likely to have a clustered index.</p> <p>This version will query the database in batches of 100. Note that <code>SaveChanges()</code> is called for each entity.</p> <p>If you want to improve your throughput dramatically, you should call <code>SaveChanges()</code> less frequently. Use code like this instead:</p> <pre><code>foreach (var chunk in clientList.OrderBy(c =&gt; c.Id).QueryChunksOfSize(100)) { foreach (var client in chunk) { // do stuff } context.SaveChanges(); } </code></pre> <p>This results in 100 times fewer database update calls. Of course each of those calls takes longer to complete, but you still come out way ahead in the end. Your mileage may vary, but this was worlds faster for me.</p> <p>And it gets around the exception you were seeing.</p> <p><strong>EDIT</strong> I revisited this question after running SQL Profiler and updated a few things to improve performance. For anyone who is interested, here is some sample SQL that shows what is created by the DB.</p> <p>The first loop doesn't need to skip anything, so is simpler.</p> <pre><code>SELECT TOP (100) -- the chunk size [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], FROM [dbo].[Clients] AS [Extent1] ORDER BY [Extent1].[Id] ASC </code></pre> <p>Subsequent calls need to skip previous chunks of results, so introduces usage of <code>row_number</code>:</p> <pre><code>SELECT TOP (100) -- the chunk size [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], FROM ( SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], row_number() OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number] FROM [dbo].[Clients] AS [Extent1] ) AS [Extent1] WHERE [Extent1].[row_number] &gt; 100 -- the number of rows to skip ORDER BY [Extent1].[Id] ASC </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload