Skip to content

Commit a7a830a

Browse files
authored
Fix sample code in SQL querying page (#4920)
Fixes #4919
1 parent c6d6227 commit a7a830a

File tree

2 files changed

+74
-37
lines changed

2 files changed

+74
-37
lines changed

entity-framework/core/querying/sql-queries.md

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,23 @@ Entity Framework Core allows you to drop down to SQL queries when working with a
1616

1717
You can use <xref:Microsoft.EntityFrameworkCore.RelationalQueryableExtensions.FromSql*> to begin a LINQ query based on a SQL query:
1818

19-
[!code-csharp[Main](../../../samples/core/Querying/SqlQueries/Program.cs#FromSql)]
19+
```csharp
20+
var blogs = context.Blogs
21+
.FromSql($"SELECT * FROM dbo.Blogs")
22+
.ToList();
23+
```
2024

2125
> [!NOTE]
2226
>
2327
> <xref:Microsoft.EntityFrameworkCore.RelationalQueryableExtensions.FromSql*> was introduced in EF Core 7.0. When using older versions, use <xref:Microsoft.EntityFrameworkCore.RelationalQueryableExtensions.FromSqlInterpolated*> instead.
2428
2529
SQL queries can be used to execute a stored procedure which returns entity data:
2630

27-
[!code-csharp[Main](../../../samples/core/Querying/SqlQueries/Program.cs#FromSqlStoredProcedure)]
31+
```csharp
32+
var blogs = context.Blogs
33+
.FromSql($"EXECUTE dbo.GetMostPopularBlogs")
34+
.ToList();
35+
```
2836

2937
> [!NOTE]
3038
>
@@ -41,17 +49,35 @@ SQL queries can be used to execute a stored procedure which returns entity data:
4149
4250
The following example passes a single parameter to a stored procedure by including a parameter placeholder in the SQL query string and providing an additional argument:
4351

44-
[!code-csharp[Main](../../../samples/core/Querying/SqlQueries/Program.cs#FromSqlStoredProcedureParameter)]
52+
```csharp
53+
var user = "johndoe";
54+
55+
var blogs = context.Blogs
56+
.FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser {user}")
57+
.ToList();
58+
```
4559

4660
While this syntax may look like regular C# [string interpolation](/dotnet/csharp/language-reference/tokens/interpolated), the supplied value is wrapped in a `DbParameter` and the generated parameter name inserted where the `{0}` placeholder was specified. This makes <xref:Microsoft.EntityFrameworkCore.RelationalQueryableExtensions.FromSql*> safe from SQL injection attacks, and sends the value efficiently and correctly to the database.
4761

4862
When executing stored procedures, it can be useful to use named parameters in the SQL query string, especially when the stored procedure has optional parameters:
4963

50-
[!code-csharp[Main](../../../samples/core/Querying/SqlQueries/Program.cs#FromSqlStoredProcedureNamedSqlParameter)]
64+
```csharp
65+
var user = new SqlParameter("user", "johndoe");
66+
67+
var blogs = context.Blogs
68+
.FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser @filterByUser={user}")
69+
.ToList();
70+
```
5171

5272
If you need more control over the database parameter being sent, you can also construct a `DbParameter` and supply it as a parameter value. This allows you to set the precise database type of the parameter, or facets such as its size, precision or length:
5373

54-
[!code-csharp[Main](../../../samples/core/Querying/SqlQueries/Program.cs#FromSqlStoredProcedureSqlParameter)]
74+
```csharp
75+
var user = new SqlParameter("user", "johndoe");
76+
77+
var blogs = context.Blogs
78+
.FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser {user}")
79+
.ToList();
80+
```
5581

5682
> [!NOTE]
5783
>
@@ -76,7 +102,14 @@ First, it's important to consider the implications of dynamically constructing a
76102

77103
If you've decided you do want to dynamically construct your SQL, you'll have to use <xref:Microsoft.EntityFrameworkCore.RelationalQueryableExtensions.FromSqlRaw*>, which allows interpolating variable data directly into the SQL string, instead of using a database parameter:
78104

79-
[!code-csharp[Main](../../../samples/core/Querying/SqlQueries/Program.cs#FromSqlRawStoredProcedureParameter)]
105+
```csharp
106+
var columnName = "Url";
107+
var columnValue = new SqlParameter("columnValue", "http://SomeURL");
108+
109+
var blogs = context.Blogs
110+
.FromSqlRaw($"SELECT * FROM [Blogs] WHERE {columnName} = @columnValue", columnValue)
111+
.ToList();
112+
```
80113

81114
In the above code, the column name is inserted directly into the SQL, using C# string interpolation. It is your responsibility to make sure this string value is safe, sanitizing it if it comes from an unsafe origin; this means detecting special characters such as semicolons, comments, and other SQL constructs, and either escaping them properly or rejecting such inputs.
82115

@@ -90,7 +123,15 @@ On the other hand, the column value is sent via a `DbParameter`, and is therefor
90123

91124
You can compose on top of the initial SQL query using LINQ operators; EF Core will treat your SQL as a subquery and compose over it in the database. The following example uses a SQL query that selects from a Table-Valued Function (TVF). And then composes on it using LINQ to do filtering and sorting.
92125

93-
[!code-csharp[Main](../../../samples/core/Querying/SqlQueries/Program.cs#FromSqlComposed)]
126+
```csharp
127+
var searchTerm = "Lorem ipsum";
128+
129+
var blogs = context.Blogs
130+
.FromSql($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
131+
.Where(b => b.Rating > 3)
132+
.OrderByDescending(b => b.Rating)
133+
.ToList();
134+
```
94135

95136
The above query generates the following SQL:
96137

@@ -107,7 +148,14 @@ ORDER BY [b].[Rating] DESC
107148

108149
The [`Include`](xref:core/querying/related-data/eager) operator can be used to load related data, just like with any other LINQ query:
109150

110-
[!code-csharp[Main](../../../samples/core/Querying/SqlQueries/Program.cs#FromSqlInclude)]
151+
```csharp
152+
var searchTerm = "Lorem ipsum";
153+
154+
var blogs = context.Blogs
155+
.FromSql($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
156+
.Include(b => b.Posts)
157+
.ToList();
158+
```
111159

112160
Composing with LINQ requires your SQL query to be composable, since EF Core will treat the supplied SQL as a subquery. Composable SQL queries generally begin with the `SELECT` keyword, and cannot contain SQL features that aren't valid in a subquery, such as:
113161

@@ -123,7 +171,14 @@ Queries that use <xref:Microsoft.EntityFrameworkCore.RelationalQueryableExtensio
123171

124172
The following example uses a SQL query that selects from a Table-Valued Function (TVF), then disables change tracking with the call to [`AsNoTracking`](xref:core/querying/tracking#no-tracking-queries):
125173

126-
[!code-csharp[Main](../../../samples/core/Querying/SqlQueries/Program.cs#FromSqlAsNoTracking)]
174+
```csharp
175+
var searchTerm = "Lorem ipsum";
176+
177+
var blogs = context.Blogs
178+
.FromSql($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
179+
.AsNoTracking()
180+
.ToList();
181+
```
127182

128183
## Querying scalar (non-entity) types
129184

@@ -205,7 +260,12 @@ var overAverageIds = context.Database
205260

206261
In some scenarios, it may be necessary to execute SQL which does not return any data, typically for modifying data in the database or calling a stored procedure which doesn't return any result sets. This can be done via <xref:Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.ExecuteSql*>:
207262

208-
[!code-csharp[Main](../../../samples/core/Querying/SqlQueries/Program.cs#ExecuteSql)]
263+
```csharp
264+
using (var context = new BloggingContext())
265+
{
266+
var rowsModified = context.Database.ExecuteSql($"UPDATE [Blogs] SET [Url] = NULL");
267+
}
268+
```
209269

210270
This executes the provided SQL and returns the number of rows modified. <xref:Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.ExecuteSql*> protects against SQL injection by using safe parameterization, just like <xref:Microsoft.EntityFrameworkCore.RelationalQueryableExtensions.FromSql*>, and <xref:Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.ExecuteSqlRaw*> allows for dynamic construction of SQL queries, just like <xref:Microsoft.EntityFrameworkCore.RelationalQueryableExtensions.FromSqlRaw*> does for queries.
211271

samples/core/Querying/SqlQueries/Program.cs

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Linq;
22
using Microsoft.Data.SqlClient;
3+
using Microsoft.Data.Sqlite;
34
using Microsoft.EntityFrameworkCore;
45

56
namespace EFQuerying.RawSQL;
@@ -49,128 +50,104 @@ from [Post] as [p]
4950

5051
using (var context = new BloggingContext())
5152
{
52-
#region FromSql
5353
var blogs = context.Blogs
5454
.FromSql($"SELECT * FROM dbo.Blogs")
5555
.ToList();
56-
#endregion
5756
}
5857

5958
using (var context = new BloggingContext())
6059
{
61-
#region FromSqlStoredProcedure
6260
var blogs = context.Blogs
6361
.FromSql($"EXECUTE dbo.GetMostPopularBlogs")
6462
.ToList();
65-
#endregion
6663
}
6764

6865
using (var context = new BloggingContext())
6966
{
70-
#region FromSqlStoredProcedureParameter
7167
var user = "johndoe";
7268

7369
var blogs = context.Blogs
7470
.FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser {user}")
7571
.ToList();
76-
#endregion
7772
}
7873

7974
using (var context = new BloggingContext())
8075
{
81-
#region FromSqlStoredProcedureNamedSqlParameter
82-
var user = new SqlParameter("user", "johndoe");
76+
var user = new SqliteParameter("user", "johndoe");
8377

8478
var blogs = context.Blogs
8579
.FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser @filterByUser={user}")
8680
.ToList();
87-
#endregion
8881
}
8982

9083
using (var context = new BloggingContext())
9184
{
92-
#region FromSqlStoredProcedureSqlParameter
93-
var user = new SqlParameter("user", "johndoe");
85+
var user = new SqliteParameter("user", "johndoe");
9486

9587
var blogs = context.Blogs
9688
.FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser {user}")
9789
.ToList();
98-
#endregion
9990
}
10091

10192
using (var context = new BloggingContext())
10293
{
103-
#region FromSqlRawStoredProcedureParameter
10494
var columnName = "Url";
105-
var columnValue = new SqlParameter("columnValue", "http://SomeURL");
95+
var columnValue = new SqliteParameter("columnValue", "http://SomeURL");
10696

10797
var blogs = context.Blogs
10898
.FromSqlRaw($"SELECT * FROM [Blogs] WHERE {columnName} = @columnValue", columnValue)
10999
.ToList();
110-
#endregion
111100
}
112101

113102
using (var context = new BloggingContext())
114103
{
115-
#region FromSqlComposed
116104
var searchTerm = "Lorem ipsum";
117105

118106
var blogs = context.Blogs
119107
.FromSql($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
120108
.Where(b => b.Rating > 3)
121109
.OrderByDescending(b => b.Rating)
122110
.ToList();
123-
#endregion
124111
}
125112

126113
using (var context = new BloggingContext())
127114
{
128-
#region FromSqlInclude
129115
var searchTerm = "Lorem ipsum";
130116

131117
var blogs = context.Blogs
132118
.FromSql($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
133119
.Include(b => b.Posts)
134120
.ToList();
135-
#endregion
136121
}
137122

138123
using (var context = new BloggingContext())
139124
{
140-
#region FromSqlAsNoTracking
141125
var searchTerm = "Lorem ipsum";
142126

143127
var blogs = context.Blogs
144128
.FromSql($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
145129
.AsNoTracking()
146130
.ToList();
147-
#endregion
148131
}
149132

150133
using (var context = new BloggingContext())
151134
{
152-
#region SqlQuery
153135
var ids = context.Database
154136
.SqlQuery<int>($"SELECT [BlogId] FROM [Blogs]")
155137
.ToList();
156-
#endregion
157138
}
158139

159140
using (var context = new BloggingContext())
160141
{
161-
#region SqlQueryComposed
162142
var overAverageIds = context.Database
163143
.SqlQuery<int>($"SELECT [BlogId] AS [Value] FROM [Blogs]")
164144
.Where(id => id > context.Blogs.Average(b => b.BlogId))
165145
.ToList();
166-
#endregion
167146
}
168147

169-
#region ExecuteSql
170148
using (var context = new BloggingContext())
171149
{
172150
var rowsModified = context.Database.ExecuteSql($"UPDATE [Blogs] SET [Url] = NULL");
173151
}
174-
#endregion
175152
}
176153
}

0 commit comments

Comments
 (0)