Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3343e40

Browse files
authored
Merge pull request#36193 from dotnet/release/8.0-staging
[release/8.0] Merge release/8.0-staging => release/8.0
2 parentsbf8cff1 +cda1db5 commit3343e40

File tree

4 files changed

+150
-12
lines changed

4 files changed

+150
-12
lines changed

‎src/EFCore.Relational/Query/QuerySqlGenerator.cs‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public class QuerySqlGenerator : SqlExpressionVisitor
4444
privatestaticreadonlyboolUseOldBehavior32375=
4545
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32375",outvarenabled32375)&&enabled32375;
4646

47+
privatestaticreadonlyboolUseOldBehavior36105=
48+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue36105",outvarenabled36105)&&enabled36105;
49+
4750
/// <summary>
4851
/// Creates a new instance of the <see cref="QuerySqlGenerator" /> class.
4952
/// </summary>
@@ -1276,9 +1279,16 @@ static string GetSetOperation(SetOperationBase operation)
12761279
protectedvirtualvoidGenerateSetOperationOperand(SetOperationBasesetOperation,SelectExpressionoperand)
12771280
{
12781281
// INTERSECT has higher precedence over UNION and EXCEPT, but otherwise evaluation is left-to-right.
1279-
// To preserve meaning, add parentheses whenever a set operation is nested within a different set operation.
1282+
// To preserve evaluation order, add parentheses whenever a set operation is nested within a different set operation
1283+
// - including different distinctness.
1284+
// In addition, EXCEPT is non-commutative (unlike UNION/INTERSECT), so add parentheses for that case too (see #36105).
12801285
if(IsNonComposedSetOperation(operand)
1281-
&&operand.Tables[0].GetType()!=setOperation.GetType())
1286+
&&((UseOldBehavior36105&&operand.Tables[0].GetType()!=setOperation.GetType())
1287+
||(!UseOldBehavior36105
1288+
&&operand.Tables[0]isSetOperationBasenestedSetOperation
1289+
&&(nestedSetOperationisExceptExpression
1290+
||nestedSetOperation.GetType()!=setOperation.GetType()
1291+
||nestedSetOperation.IsDistinct!=setOperation.IsDistinct))))
12821292
{
12831293
_relationalCommandBuilder.AppendLine("(");
12841294
using(_relationalCommandBuilder.Indent())

‎src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs‎

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
usingSystem.Diagnostics.CodeAnalysis;
45
usingMicrosoft.EntityFrameworkCore.Query.SqlExpressions;
56
usingMicrosoft.EntityFrameworkCore.Sqlite.Query.SqlExpressions.Internal;
67

@@ -14,6 +15,9 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal;
1415
/// </summary>
1516
publicclassSqliteQuerySqlGenerator:QuerySqlGenerator
1617
{
18+
privatestaticreadonlyboolUseOldBehavior36112=
19+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue36112",outvarenabled36112)&&enabled36112;
20+
1721
/// <summary>
1822
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
1923
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -98,8 +102,63 @@ protected override void GenerateLimitOffset(SelectExpression selectExpression)
98102
/// doing so can result in application failures when updating to a new Entity Framework Core release.
99103
/// </summary>
100104
protectedoverridevoidGenerateSetOperationOperand(SetOperationBasesetOperation,SelectExpressionoperand)
101-
// Sqlite doesn't support parentheses around set operation operands
102-
=>Visit(operand);
105+
{
106+
// Most databases support parentheses around set operations to determine evaluation order, but SQLite does not;
107+
// however, we can instead wrap the nested set operation in a SELECT * FROM () to achieve the same effect.
108+
// The following is a copy-paste of the base implementation from QuerySqlGenerator, adding the SELECT.
109+
110+
// INTERSECT has higher precedence over UNION and EXCEPT, but otherwise evaluation is left-to-right.
111+
// To preserve evaluation order, add parentheses whenever a set operation is nested within a different set operation
112+
// - including different distinctness.
113+
// In addition, EXCEPT is non-commutative (unlike UNION/INTERSECT), so add parentheses for that case too (see #36105).
114+
if(!UseOldBehavior36112
115+
&&TryUnwrapBareSetOperation(operand,outvarnestedSetOperation)
116+
&&(nestedSetOperationisExceptExpression
117+
||nestedSetOperation.GetType()!=setOperation.GetType()
118+
||nestedSetOperation.IsDistinct!=setOperation.IsDistinct))
119+
{
120+
Sql.AppendLine("SELECT * FROM (");
121+
122+
using(Sql.Indent())
123+
{
124+
Visit(operand);
125+
}
126+
127+
Sql.AppendLine().Append(")");
128+
}
129+
else
130+
{
131+
Visit(operand);
132+
}
133+
134+
staticboolTryUnwrapBareSetOperation(SelectExpressionselectExpression,[NotNullWhen(true)]outSetOperationBase?setOperation)
135+
{
136+
if(selectExpressionis
137+
{
138+
Tables:[SetOperationBases],
139+
Predicate:null,
140+
Orderings:[],
141+
Offset:null,
142+
Limit:null,
143+
IsDistinct:false,
144+
Having:null,
145+
GroupBy:[]
146+
}
147+
&&selectExpression.Projection.Count==s.Source1.Projection.Count
148+
&&selectExpression.Projection.Select(
149+
(pe,index)=>pe.ExpressionisColumnExpressioncolumn
150+
&&column.TableAlias==s.Alias
151+
&&column.Name==s.Source1.Projection[index].Alias)
152+
.All(e=>e))
153+
{
154+
setOperation=s;
155+
returntrue;
156+
}
157+
158+
setOperation=null;
159+
returnfalse;
160+
}
161+
}
103162

104163
privatevoidGenerateGlob(GlobExpressionglobExpression,boolnegated=false)
105164
{

‎test/EFCore.Specification.Tests/Query/NorthwindSetOperationsQueryTestBase.cs‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,19 @@ public virtual Task Except_nested(bool async)
8282
.Except(ss.Set<Customer>().Where(s=>s.City=="México D.F."))
8383
.Except(ss.Set<Customer>().Where(e=>e.City=="Seattle")));
8484

85+
// EXCEPT is non-commutative, unlike UNION/INTERSECT. Therefore, parentheses are needed in the following query
86+
// to ensure that the inner EXCEPT is evaluated first. See #36105.
87+
[ConditionalTheory]
88+
[MemberData(nameof(IsAsyncData))]
89+
publicvirtualTaskExcept_nested2(boolasync)
90+
=>AssertQuery(
91+
async,
92+
ss=>ss.Set<Customer>()
93+
.Except(ss.Set<Customer>()
94+
.Where(s=>s.City=="Seattle")
95+
.Except(ss.Set<Customer>()
96+
.Where(e=>e.City=="Seattle"))));
97+
8598
[ConditionalTheory]
8699
[MemberData(nameof(IsAsyncData))]
87100
publicvirtualTaskExcept_non_entity(boolasync)
@@ -221,6 +234,17 @@ public virtual Task Union_Intersect(bool async)
221234
.Union(ss.Set<Customer>().Where(c=>c.City=="London"))
222235
.Intersect(ss.Set<Customer>().Where(c=>c.ContactName.Contains("Thomas"))));
223236

237+
// The evaluation order of Concat and Union can matter: A UNION ALL (B UNION C) can be different from (A UNION ALL B) UNION C.
238+
// Make sure parentheses are added.
239+
[ConditionalTheory]
240+
[MemberData(nameof(IsAsyncData))]
241+
publicvirtualTaskUnion_inside_Concat(boolasync)
242+
=>AssertQuery(
243+
async,
244+
ss=>ss.Set<Customer>().Where(c=>c.City=="Berlin")
245+
.Concat(ss.Set<Customer>().Where(c=>c.City=="London")
246+
.Union(ss.Set<Customer>().Where(c=>c.City=="Berlin"))));
247+
224248
[ConditionalTheory]
225249
[MemberData(nameof(IsAsyncData))]
226250
publicvirtualTaskUnion_Take_Union_Take(boolasync)

‎test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSetOperationsQuerySqlServerTest.cs‎

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,28 @@ WHERE [c1].[ContactName] LIKE N'%Thomas%'
201201
""");
202202
}
203203

204+
publicoverrideasyncTaskUnion_inside_Concat(boolasync)
205+
{
206+
awaitbase.Union_inside_Concat(async);
207+
208+
AssertSql(
209+
"""
210+
SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region]
211+
FROM [Customers] AS [c]
212+
WHERE [c].[City] = N'Berlin'
213+
UNION ALL
214+
(
215+
SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region]
216+
FROM [Customers] AS [c0]
217+
WHERE [c0].[City] = N'London'
218+
UNION
219+
SELECT [c1].[CustomerID], [c1].[Address], [c1].[City], [c1].[CompanyName], [c1].[ContactName], [c1].[ContactTitle], [c1].[Country], [c1].[Fax], [c1].[Phone], [c1].[PostalCode], [c1].[Region]
220+
FROM [Customers] AS [c1]
221+
WHERE [c1].[City] = N'Berlin'
222+
)
223+
""");
224+
}
225+
204226
publicoverrideasyncTaskUnion_Take_Union_Take(boolasync)
205227
{
206228
awaitbase.Union_Take_Union_Take(async);
@@ -1234,21 +1256,44 @@ public override async Task Except_nested(bool async)
12341256
awaitbase.Except_nested(async);
12351257

12361258
AssertSql(
1237-
"""
1238-
SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region]
1239-
FROM [Customers] AS [c]
1240-
WHERE [c].[ContactTitle] = N'Owner'
1241-
EXCEPT
1242-
SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region]
1243-
FROM [Customers] AS [c0]
1244-
WHERE [c0].[City] = N'México D.F.'
1259+
"""
1260+
(
1261+
SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region]
1262+
FROM [Customers] AS [c]
1263+
WHERE [c].[ContactTitle] = N'Owner'
1264+
EXCEPT
1265+
SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region]
1266+
FROM [Customers] AS [c0]
1267+
WHERE [c0].[City] = N'México D.F.'
1268+
)
12451269
EXCEPT
12461270
SELECT [c1].[CustomerID], [c1].[Address], [c1].[City], [c1].[CompanyName], [c1].[ContactName], [c1].[ContactTitle], [c1].[Country], [c1].[Fax], [c1].[Phone], [c1].[PostalCode], [c1].[Region]
12471271
FROM [Customers] AS [c1]
12481272
WHERE [c1].[City] = N'Seattle'
12491273
""");
12501274
}
12511275

1276+
publicoverrideasyncTaskExcept_nested2(boolasync)
1277+
{
1278+
awaitbase.Except_nested2(async);
1279+
1280+
AssertSql(
1281+
"""
1282+
SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region]
1283+
FROM [Customers] AS [c]
1284+
EXCEPT
1285+
(
1286+
SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region]
1287+
FROM [Customers] AS [c0]
1288+
WHERE [c0].[City] = N'Seattle'
1289+
EXCEPT
1290+
SELECT [c1].[CustomerID], [c1].[Address], [c1].[City], [c1].[CompanyName], [c1].[ContactName], [c1].[ContactTitle], [c1].[Country], [c1].[Fax], [c1].[Phone], [c1].[PostalCode], [c1].[Region]
1291+
FROM [Customers] AS [c1]
1292+
WHERE [c1].[City] = N'Seattle'
1293+
)
1294+
""");
1295+
}
1296+
12521297
publicoverrideasyncTaskIntersect_non_entity(boolasync)
12531298
{
12541299
awaitbase.Intersect_non_entity(async);

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp