Skip to content

Commit 84c08ae

Browse files
committed
Move source snippets to samples
1 parent 3b7dfb5 commit 84c08ae

13 files changed

+433
-146
lines changed

entity-framework/core/performance/advanced-performance-topics.md

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,7 @@ When EF receives a LINQ query tree for execution, it must first "compile" that t
4646

4747
Consider the following two queries:
4848

49-
```csharp
50-
var blog1 = ctx.Blogs.FirstOrDefault(b => b.Name == "blog1");
51-
var blog2 = ctx.Blogs.FirstOrDefault(b => b.Name == "blog2");
52-
```
49+
[!code-csharp[Main](../../../samples/core/Performance/Program.cs#QueriesWithConstants)]
5350

5451
Since the expression trees contains different constants, the expression tree differs and each of these queries will be compiled separately by EF Core. In addition, each query produces a slightly different SQL command:
5552

@@ -67,12 +64,7 @@ Because the SQL differs, your database server will likely also need to produce a
6764

6865
A small modification to your queries can change things considerably:
6966

70-
```csharp
71-
var blogName = "blog1";
72-
var blog1 = ctx.Blogs.FirstOrDefault(b => b.Name == blogName);
73-
blogName = "blog2";
74-
var blog2 = ctx.Blogs.FirstOrDefault(b => b.Name == blogName);
75-
```
67+
[!code-csharp[Main](../../../samples/core/Performance/Program.cs#QueriesWithParameterization)]
7668

7769
Since the blog name is now *parameterized*, both queries have the same tree shape, and EF only needs to be compiled once. The SQL produced is also parameterized, allowing the database to reuse the same query plan:
7870

entity-framework/core/performance/efficient-querying.md

Lines changed: 9 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,9 @@ Querying efficiently is a vast subject, that covers subjects as wide-ranging as
1313

1414
The main deciding factor in whether a query runs fast or not is whether it will properly utilize indexes where appropriate: databases are typically used to hold large amounts of data, and queries which traverse entire tables are typically sources of serious performance issues. Indexing issues aren't easy to spot, because it isn't immediately obvious whether a given query will use an index or not. For example:
1515

16-
```csharp
17-
_ = ctx.Blogs.Where(b => b.Name.StartsWith("A")).ToList(); // Uses an index defined on Name on SQL Server
18-
_ = ctx.Blogs.Where(b => b.Name.EndsWith("B")).ToList(); // Does not use the index
19-
```
16+
[!code-csharp[Main](../../../samples/core/Performance/Program.cs#Indexes)]
2017

21-
The main way the spot indexing issues is to first pinpoint a slow query, and then examine its query plan via your database's favorite tool; see the [performance diagnosis](xref:core/performance/performance-diagnosis) page for more information on how to do that. The query plan displays whether the query traverses the entire table, or uses an index.
18+
A good way to spot indexing issues is to first pinpoint a slow query, and then examine its query plan via your database's favorite tool; see the [performance diagnosis](xref:core/performance/performance-diagnosis) page for more information on how to do that. The query plan displays whether the query traverses the entire table, or uses an index.
2219

2320
As a general rule, there isn't any special EF knowledge to using indexes or diagnosing performance issues related to them; general database knowledge related to indexes is just as relevant to EF applications as to applications not using EF. The following lists some general guidelines to keep in mind when using indexes:
2421

@@ -31,12 +28,7 @@ As a general rule, there isn't any special EF knowledge to using indexes or diag
3128

3229
EF Core makes it very easy to query out entity instances, and then use those instances in code. However, querying entity instances can frequently pull back more data than necessary from your database. Consider the following:
3330

34-
```csharp
35-
foreach (var blog in ctx.Blogs)
36-
{
37-
Console.WriteLine("Blog: " + blog.Url);
38-
}
39-
```
31+
[!code-csharp[Main](../../../samples/core/Performance/Program.cs#ProjectEntities)]
4032

4133
Although this code only actually needs each Blog's `Url` property, the entire Blog entity is fetched, and unneeded columns are transferred from the database:
4234

@@ -47,12 +39,7 @@ FROM [Blogs] AS [b]
4739

4840
This can be optimized by using `Select` to tell EF which columns to project out:
4941

50-
```csharp
51-
foreach (var blogName in ctx.Blogs.Select(b => b.Url))
52-
{
53-
Console.WriteLine("Blog: " + blogName);
54-
}
55-
```
42+
[!code-csharp[Main](../../../samples/core/Performance/Program.cs#ProjectSingleProperty)]
5643

5744
The resulting SQL pulls back only the needed columns:
5845

@@ -69,22 +56,13 @@ Note that this technique is very useful for read-only queries, but things get mo
6956

7057
By default, a query returns all rows that matches its filters:
7158

72-
```csharp
73-
var blogs = ctx.Blogs
74-
.Where(b => b.Name.StartsWith("A"))
75-
.ToList();
76-
```
59+
[!code-csharp[Main](../../../samples/core/Performance/Program.cs#NoLimit)]
7760

7861
Since the number of rows returned depends on actual data in your database, it's impossible to know how much data will be loaded from the database, how much memory will be taken up by the results, and how much additional load will be generated when processing these results (e.g. by sending them to a user browser over the network). Crucially, test databases frequently contain little data, so that everything works well while testing, but performance problems suddenly appear when the query starts running on real-world data and many rows are returned.
7962

8063
As a result, it's usually worth giving thought to limiting the number of results:
8164

82-
```csharp
83-
var blogs = ctx.Blogs
84-
.Where(b => b.Name.StartsWith("A"))
85-
.Take(25)
86-
.ToList();
87-
```
65+
[!code-csharp[Main](../../../samples/core/Performance/Program.cs#Limit25)]
8866

8967
At a minimum, your UI could show a message indicating that more rows may exist in the database (and allow retrieving them in some other manner). A full-blown solution would implement *paging*, where your UI only shows a certain number of rows at a time, and allow users to advance to the next page as needed; this typically combines the <xref:System.Linq.Enumerable.Take%2A> and <xref:System.Linq.Enumerable.Skip%2A> operators to select a specific range in the resultset each time.
9068

@@ -122,15 +100,7 @@ In other scenarios, we may not know which related entity we're going to need bef
122100

123101
Consider the following:
124102

125-
```csharp
126-
foreach (var blog in ctx.Blogs.ToList())
127-
{
128-
foreach (var post in blog.Posts)
129-
{
130-
Console.WriteLine($"Blog {blog.Url}, Post: {post.Title}");
131-
}
132-
}
133-
```
103+
[!code-csharp[Main](../../../samples/core/Performance/Program.cs#NPlusOne)]
134104

135105
This seemingly innocent piece of code iterates through all the blogs and their posts, printing them out. Turning on EF Core's [statement logging](xref:core/logging-events-diagnostics/index) reveals the following:
136106

@@ -162,15 +132,7 @@ What's going on here? Why are all these queries being sent for the simple loops
162132

163133
Assuming we're going to need all of the blogs' posts, it makes sense to use eager loading here instead. We can use the [Include](xref:core/querying/related-data/eager#eager-loading) operator to perform the loading, but since we only need the Blogs' URLs (and we should only [load what's needed](xref:core/performance/efficient-updating#project-only-properties-you-need)). So we'll use a projection instead:
164134

165-
```csharp
166-
foreach (var blog in ctx.Blogs.Select(b => new { b.Url, b.Posts }).ToList())
167-
{
168-
foreach (var post in blog.Posts)
169-
{
170-
Console.WriteLine($"Blog {blog.Url}, Post: {post.Title}");
171-
}
172-
}
173-
```
135+
[!code-csharp[Main](../../../samples/core/Performance/Program.cs#EagerlyLoadRelatedAndProject)]
174136

175137
This will make EF Core fetch all the Blogs - along with their Posts - in a single query. In some cases, it may also be useful to avoid cartesian explosion effects by using [split queries](xref:core/querying/single-split-queries).
176138

@@ -183,23 +145,7 @@ Buffering refers to loading all your query results into memory, whereas streamin
183145

184146
Whether a query buffers or streams depends on how it is evaluated:
185147

186-
```csharp
187-
// ToList and ToArray cause the entire resultset to be buffered:
188-
var blogsList = context.Blogs.Where(b => b.Name.StartsWith("A")).ToList();
189-
var blogsArray = context.Blogs.Where(b => b.Name.StartsWith("A")).ToArray();
190-
191-
// Foreach streams, processing one row at a time:
192-
foreach (var blog in context.Blogs.Where(b => b.Name.StartsWith("A")))
193-
{
194-
// ...
195-
}
196-
197-
// AsEnumerable also streams, allowing you to execute LINQ operators on the client-side:
198-
var groupedBlogs = context.Blogs
199-
.Where(b => b.Name.StartsWith("A"))
200-
.AsEnumerable()
201-
.Where(b => SomeDotNetMethod(b));
202-
```
148+
[!code-csharp[Main](../../../samples/core/Performance/Program.cs#BufferingAndStreaming)]
203149

204150
If your queries return just a few results, then you probably don't have to worry about this. However, if your query might return large numbers of rows, it's worth giving thought to streaming instead of buffering.
205151

entity-framework/core/performance/efficient-updating.md

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,38 +11,21 @@ uid: core/performance/efficient-updating
1111

1212
EF Core helps minimize roundtrips by automatically batching together all updates in a single roundtrip. Consider the following:
1313

14-
```csharp
15-
var blog = context.Blogs.Single(b => b.Name == "EF Core Blog");
16-
blog.Url = "http://some.new.website";
17-
context.Add(new Blog { Name = "Another blog"});
18-
context.Add(new Blog { Name = "Yet another blog"});
19-
context.SaveChanges();
20-
```
14+
[!code-csharp[Main](../../../samples/core/Performance/Program.cs#SaveChangesBatching)]
2115

2216
The above loads a blog from the database, changes its name, and then adds two new blogs; to apply this, two SQL INSERT statements and one UPDATE statement are sent to the database. Rather than sending them one by one, as Blog instances are added, EF Core tracks these changes internally, and executes them in a single roundtrip when <xref:Microsoft.EntityFrameworkCore.DbContext.SaveChanges%2A> is called.
2317

2418
The number of statements that EF batches in a single roundtrip depends on the database provider being used. For example, performance analysis has shown batching to be generally less efficient for SQL Server when less than 4 statements are involved. Similarly, the benefits of batching degrade after around 40 statements for SQL Server, so EF Core will by default only execute up to 42 statements in a single batch, and execute additional statements in separate roundtrips.
2519

2620
Users can also tweak these thresholds to achieve potentially higher performance - but benchmark carefully before modifying these:
2721

28-
```csharp
29-
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
30-
=> optionsBuilder.UseSqlServer(@"...", o => o
31-
.MinBatchSize(1)
32-
.MaxBatchSize(100))
33-
```
22+
[!code-csharp[Main](../../../samples/core/Performance/BatchTweakingContext.cs#BatchTweaking)]
3423

3524
## Bulk updates
3625

37-
Let's assume you want to give all Employees of a certain department a raise. A typical implementation for this in EF Core would look like the following
26+
Let's assume you want to give all your employees a raise. A typical implementation for this in EF Core would look like the following:
3827

39-
```csharp
40-
foreach (var employee in context.Employees.Where(e => e.Department.Id == 10))
41-
{
42-
employee.Salary += 1000;
43-
}
44-
context.SaveChanges();
45-
```
28+
[!code-csharp[Main](../../../samples/core/Performance/BatchTweakingContext.cs#UpdateWithoutBulk)]
4629

4730
While this is perfectly valid code, let's analyze what it does from a performance perspective:
4831

@@ -53,13 +36,11 @@ While this is perfectly valid code, let's analyze what it does from a performanc
5336
Relational databases also support *bulk updates*, so the above could be rewritten as the following single SQL statement:
5437

5538
```sql
56-
UPDATE [Employees] SET [Salary] = [Salary] + 1000 WHERE [DepartmentId] = 10;
39+
UPDATE [Employees] SET [Salary] = [Salary] + 1000;
5740
```
5841

59-
This performs the entire operation in a single roundtrip, without loading or sending any actual data to the database, and without making use of EF's change tracking machinery, which does have an overhead cost.
42+
This performs the entire operation in a single roundtrip, without loading or sending any actual data to the database, and without making use of EF's change tracking machinery, which imposes an additional overhead.
6043

6144
Unfortunately, EF doesn't currently provide APIs for performing bulk updates. Until these are introduced, you can use raw SQL to perform the operation where performance is sensitive:
6245

63-
```csharp
64-
context.Database.ExecuteSqlRaw("UPDATE [Employees] SET [Salary] = [Salary] + 1000 WHERE [DepartmentId] = {0}", departmentId);
65-
```
46+
[!code-csharp[Main](../../../samples/core/Performance/Program.cs#UpdateWithBulk)]

entity-framework/core/performance/performance-diagnosis.md

Lines changed: 32 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,11 @@ EF makes it very easy to capture command execution times, via either [simple log
1717

1818
### [Simple logging](#tab/simple-logging)
1919

20-
```csharp
21-
class MyDbContext
22-
{
23-
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
24-
{
25-
optionsBuilder
26-
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Integrated Security=True")
27-
.LogTo(Console.WriteLine, LogLevel.Information);
28-
}
29-
}
30-
```
20+
[!code-csharp[Main](../../../samples/core/Performance/BloggingContext.cs#SimpleLogging)]
3121

3222
### [Microsoft.Extensions.Logging](#tab/microsoft-extensions-logging)
3323

34-
```csharp
35-
class MyDbContext
36-
{
37-
static ILoggerFactory ContextLoggerFactory
38-
=> LoggerFactory.Create(b => b.AddConsole().AddFilter("", LogLevel.Information));
39-
40-
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
41-
=> optionsBuilder
42-
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Integrated Security=True")
43-
.UseLoggerFactory(ContextLoggerFactory);
44-
}
45-
```
24+
[!code-csharp[Main](../../../samples/core/Performance/ExtensionsLoggingContext.cs#ExtensionsLogging)]
4625

4726
***
4827

@@ -65,23 +44,16 @@ The above command took 4 milliseconds. If a certain command takes more than expe
6544

6645
One problem with command execution logging is that it's sometimes difficult to correlate SQL queries and LINQ queries: the SQL commands executed by EF can look very different from the LINQ queries from which they were generated. To help with this difficulty, you may want to use EF's [query tags](xref:core/querying/tags) feature, which allows you to inject a small, identifying comment into the SQL query:
6746

68-
```csharp
69-
var blogs = ctx.Blogs
70-
.TagWith("GetBlogByName")
71-
.Where(b => b.Name == "foo")
72-
.ToList();
73-
```
47+
[!code-csharp[Main](../../../samples/core/Querying/Tags/Program.cs#BasicQueryTag)]
7448

7549
The tag shows up in the logs:
7650

77-
```csharp
78-
info: 06/12/2020 09:25:42.951 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
79-
Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
80-
-- GetBlogByName
51+
```sql
52+
-- This is my spatial query!
8153

82-
SELECT [b].[Id], [b].[Name]
83-
FROM [Blogs] AS [b]
84-
WHERE [b].[Name] = N'foo'
54+
SELECT TOP(@__p_1) [p].[Id], [p].[Location]
55+
FROM [People] AS [p]
56+
ORDER BY [p].[Location].STDistance(@__myLocation_0) DESC
8557
```
8658

8759
It's often worth tagging the major queries of an application in this way, to make the command execution logs more immediately readable.
@@ -129,18 +101,34 @@ As a simple benchmark scenario, let's compare the following different methods of
129101
* Avoid loading the entire Blog entity instances at all, by projecting out the ranking only. The saves us from transferring the other, unneeded columns of the Blog entity type.
130102
* Calculate the average in the database by making it part of the query. This should be the fastest way, since everything is calculated in the database and only the result is transferred back to the client.
131103

132-
With BenchmarkDotNet, you write the code to be benchmarked as a simple method - just like a unit test - and BenchmarkDotNet automatically runs each method for sufficient number of iterations, reliably measuring how long it takes and how much memory is allocated. Here's the benchmark code:
104+
With BenchmarkDotNet, you write the code to be benchmarked as a simple method - just like a unit test - and BenchmarkDotNet automatically runs each method for sufficient number of iterations, reliably measuring how long it takes and how much memory is allocated. Here are the different method ([the full benchmark code can be seen here](https://github.com/dotnet/EntityFramework.Docs/tree/master/samples/core/Benchmarks/AverageBlogRanking.cs)):
105+
106+
### [Load entities](#tab/load-entities)
133107

134-
[!code-csharp[Main](../../../samples/core/Benchmarks/AverageBlogRanking.cs?name=Benchmarks)]
108+
[!code-csharp[Main](../../../samples/core/Benchmarks/AverageBlogRanking.cs?name=LoadEntities)]
109+
110+
### [Load entities, no tracking](#tab/load-entities-no-tracking)
111+
112+
[!code-csharp[Main](../../../samples/core/Benchmarks/AverageBlogRanking.cs?name=LoadEntitiesNoTracking)]
113+
114+
### [Project only ranking](#tab/project-only-ranking)
115+
116+
[!code-csharp[Main](../../../samples/core/Benchmarks/AverageBlogRanking.cs?name=ProjectOnlyRanking)]
117+
118+
### [Calculate in database](#tab/calculate-in-database)
119+
120+
[!code-csharp[Main](../../../samples/core/Benchmarks/AverageBlogRanking.cs?name=CalculateInDatabase)]
121+
122+
***
135123

136124
The results are below, as printed by BenchmarkDotNet:
137125

138-
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
139-
|------------------------ |-----------:|---------:|---------:|-----------:|------:|--------:|---------:|--------:|------:|-----------:|
140-
| LoadEntities | 2,860.4 us | 54.31 us | 93.68 us | 2,844.5 us | 4.55 | 0.33 | 210.9375 | 70.3125 | - | 1309.56 KB |
141-
| LoadEntitiesNonTracking | 1,353.0 us | 21.26 us | 18.85 us | 1,355.6 us | 2.10 | 0.14 | 87.8906 | 3.9063 | - | 540.09 KB |
142-
| ProjectOnlyRanking | 910.9 us | 20.91 us | 61.65 us | 892.9 us | 1.46 | 0.14 | 41.0156 | 0.9766 | - | 252.08 KB |
143-
| CalculateInDatabase | 627.1 us | 14.58 us | 42.54 us | 626.4 us | 1.00 | 0.00 | 4.8828 | - | - | 33.27 KB |
126+
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
127+
|----------------------- |-----------:|---------:|---------:|-----------:|------:|--------:|---------:|--------:|------:|-----------:|
128+
| LoadEntities | 2,860.4 us | 54.31 us | 93.68 us | 2,844.5 us | 4.55 | 0.33 | 210.9375 | 70.3125 | - | 1309.56 KB |
129+
| LoadEntitiesNoTracking | 1,353.0 us | 21.26 us | 18.85 us | 1,355.6 us | 2.10 | 0.14 | 87.8906 | 3.9063 | - | 540.09 KB |
130+
| ProjectOnlyRanking | 910.9 us | 20.91 us | 61.65 us | 892.9 us | 1.46 | 0.14 | 41.0156 | 0.9766 | - | 252.08 KB |
131+
| CalculateInDatabase | 627.1 us | 14.58 us | 42.54 us | 626.4 us | 1.00 | 0.00 | 4.8828 | - | - | 33.27 KB |
144132

145133
> [!NOTE]
146134
> As the methods instantiate and dispose the context within the method, these operations are counted for the benchmark, although strictly speaking they are not part of the querying process. This should not matter if the goal is to compare two alternatives to one another (since the context instantiation and disposal are the same), and gives a more holistic measurement for the entire operation.

samples/core/Benchmarks/AverageBlogRanking.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public void Setup()
2222
context.SeedData();
2323
}
2424

25-
#region Benchmarks
25+
#region LoadEntities
2626
[Benchmark]
2727
public double LoadEntities()
2828
{
@@ -37,9 +37,11 @@ public double LoadEntities()
3737

3838
return sum / count;
3939
}
40+
#endregion
4041

42+
#region LoadEntitiesNoTracking
4143
[Benchmark]
42-
public double LoadEntitiesNonTracking()
44+
public double LoadEntitiesNoTracking()
4345
{
4446
var sum = 0;
4547
var count = 0;
@@ -52,7 +54,9 @@ public double LoadEntitiesNonTracking()
5254

5355
return sum / count;
5456
}
57+
#endregion
5558

59+
#region ProjectOnlyRanking
5660
[Benchmark]
5761
public double ProjectOnlyRanking()
5862
{
@@ -67,14 +71,16 @@ public double ProjectOnlyRanking()
6771

6872
return sum / count;
6973
}
74+
#endregion
7075

76+
#region CalculateInDatabase
7177
[Benchmark(Baseline = true)]
7278
public double CalculateInDatabase()
7379
{
7480
using var ctx = new BloggingContext();
7581
return ctx.Blogs.Average(b => b.Rating);
7682
}
77-
#endregion Benchmarks
83+
#endregion
7884

7985
public class BloggingContext : DbContext
8086
{

0 commit comments

Comments
 (0)