Skip to content

Commit 576b31d

Browse files
committed
Fix sample code in SQL querying page
Fixes #4919
1 parent c6d6227 commit 576b31d

File tree

2 files changed

+73
-36
lines changed

2 files changed

+73
-36
lines changed

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

Lines changed: 70 additions & 9 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,36 @@ 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

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

4661
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.
4762

4863
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:
4964

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

5273
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:
5374

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

5683
> [!NOTE]
5784
>
@@ -76,7 +103,14 @@ First, it's important to consider the implications of dynamically constructing a
76103

77104
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:
78105

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

81115
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.
82116

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

91125
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.
92126

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

95137
The above query generates the following SQL:
96138

@@ -107,7 +149,14 @@ ORDER BY [b].[Rating] DESC
107149

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

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

112161
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:
113162

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

124173
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):
125174

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

128184
## Querying scalar (non-entity) types
129185

@@ -205,7 +261,12 @@ var overAverageIds = context.Database
205261

206262
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*>:
207263

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

210271
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.
211272

samples/core/Querying/SqlQueries/Program.cs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -49,128 +49,104 @@ from [Post] as [p]
4949

5050
using (var context = new BloggingContext())
5151
{
52-
#region FromSql
5352
var blogs = context.Blogs
5453
.FromSql($"SELECT * FROM dbo.Blogs")
5554
.ToList();
56-
#endregion
5755
}
5856

5957
using (var context = new BloggingContext())
6058
{
61-
#region FromSqlStoredProcedure
6259
var blogs = context.Blogs
6360
.FromSql($"EXECUTE dbo.GetMostPopularBlogs")
6461
.ToList();
65-
#endregion
6662
}
6763

6864
using (var context = new BloggingContext())
6965
{
70-
#region FromSqlStoredProcedureParameter
7166
var user = "johndoe";
7267

7368
var blogs = context.Blogs
7469
.FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser {user}")
7570
.ToList();
76-
#endregion
7771
}
7872

7973
using (var context = new BloggingContext())
8074
{
81-
#region FromSqlStoredProcedureNamedSqlParameter
82-
var user = new SqlParameter("user", "johndoe");
75+
var user = new SqliteParameter("user", "johndoe");
8376

8477
var blogs = context.Blogs
8578
.FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser @filterByUser={user}")
8679
.ToList();
87-
#endregion
8880
}
8981

9082
using (var context = new BloggingContext())
9183
{
92-
#region FromSqlStoredProcedureSqlParameter
93-
var user = new SqlParameter("user", "johndoe");
84+
var user = new SqliteParameter("user", "johndoe");
9485

9586
var blogs = context.Blogs
9687
.FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser {user}")
9788
.ToList();
98-
#endregion
9989
}
10090

10191
using (var context = new BloggingContext())
10292
{
103-
#region FromSqlRawStoredProcedureParameter
10493
var columnName = "Url";
105-
var columnValue = new SqlParameter("columnValue", "http://SomeURL");
94+
var columnValue = new SqliteParameter("columnValue", "http://SomeURL");
10695

10796
var blogs = context.Blogs
10897
.FromSqlRaw($"SELECT * FROM [Blogs] WHERE {columnName} = @columnValue", columnValue)
10998
.ToList();
110-
#endregion
11199
}
112100

113101
using (var context = new BloggingContext())
114102
{
115-
#region FromSqlComposed
116103
var searchTerm = "Lorem ipsum";
117104

118105
var blogs = context.Blogs
119106
.FromSql($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
120107
.Where(b => b.Rating > 3)
121108
.OrderByDescending(b => b.Rating)
122109
.ToList();
123-
#endregion
124110
}
125111

126112
using (var context = new BloggingContext())
127113
{
128-
#region FromSqlInclude
129114
var searchTerm = "Lorem ipsum";
130115

131116
var blogs = context.Blogs
132117
.FromSql($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
133118
.Include(b => b.Posts)
134119
.ToList();
135-
#endregion
136120
}
137121

138122
using (var context = new BloggingContext())
139123
{
140-
#region FromSqlAsNoTracking
141124
var searchTerm = "Lorem ipsum";
142125

143126
var blogs = context.Blogs
144127
.FromSql($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
145128
.AsNoTracking()
146129
.ToList();
147-
#endregion
148130
}
149131

150132
using (var context = new BloggingContext())
151133
{
152-
#region SqlQuery
153134
var ids = context.Database
154135
.SqlQuery<int>($"SELECT [BlogId] FROM [Blogs]")
155136
.ToList();
156-
#endregion
157137
}
158138

159139
using (var context = new BloggingContext())
160140
{
161-
#region SqlQueryComposed
162141
var overAverageIds = context.Database
163142
.SqlQuery<int>($"SELECT [BlogId] AS [Value] FROM [Blogs]")
164143
.Where(id => id > context.Blogs.Average(b => b.BlogId))
165144
.ToList();
166-
#endregion
167145
}
168146

169-
#region ExecuteSql
170147
using (var context = new BloggingContext())
171148
{
172149
var rowsModified = context.Database.ExecuteSql($"UPDATE [Blogs] SET [Url] = NULL");
173150
}
174-
#endregion
175151
}
176152
}

0 commit comments

Comments
 (0)