diff --git a/src/EFCore/Metadata/Internal/PropertyBase.cs b/src/EFCore/Metadata/Internal/PropertyBase.cs
index fcaabd22dbf..f15a680e016 100644
--- a/src/EFCore/Metadata/Internal/PropertyBase.cs
+++ b/src/EFCore/Metadata/Internal/PropertyBase.cs
@@ -290,7 +290,7 @@ private void UpdateFieldInfoConfigurationSource(ConfigurationSource configuratio
///
public virtual IClrPropertyGetter Getter =>
NonCapturingLazyInitializer.EnsureInitialized(
- ref _getter, this,p => new ClrPropertyGetterFactory().Create(p));
+ ref _getter, this, p => new ClrPropertyGetterFactory().Create(p));
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -300,7 +300,7 @@ private void UpdateFieldInfoConfigurationSource(ConfigurationSource configuratio
///
public virtual IClrPropertySetter Setter =>
NonCapturingLazyInitializer.EnsureInitialized(
- ref _setter, this,p => new ClrPropertySetterFactory().Create(p));
+ ref _setter, this, p => new ClrPropertySetterFactory().Create(p));
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore/Query/Pipeline/EntityEqualityRewritingExpressionVisitor.cs b/src/EFCore/Query/Pipeline/EntityEqualityRewritingExpressionVisitor.cs
index 5892fcbd30f..670b333d155 100644
--- a/src/EFCore/Query/Pipeline/EntityEqualityRewritingExpressionVisitor.cs
+++ b/src/EFCore/Query/Pipeline/EntityEqualityRewritingExpressionVisitor.cs
@@ -10,37 +10,47 @@
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Query.Expressions.Internal;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Query.NavigationExpansion;
+using Microsoft.EntityFrameworkCore.Query.NavigationExpansion.Visitors;
namespace Microsoft.EntityFrameworkCore.Query.Pipeline
{
///
- /// Rewrites comparisons of entities (as opposed to comparisons of their properties) into comparison of their keys.
+ /// Rewrites comparisons of entities (as opposed to comparisons of their properties) into comparison of their keys.
///
///
- /// For example, an expression such as cs.Where(c => c == something) would be rewritten to cs.Where(c => c.Id == something.Id).
+ /// For example, an expression such as cs.Where(c => c == something) would be rewritten to cs.Where(c => c.Id == something.Id).
///
public class EntityEqualityRewritingExpressionVisitor : ExpressionVisitor
{
- protected IDiagnosticsLogger Logger { get; }
- protected IModel Model { get; }
+ ///
+ /// If the entity equality visitors introduces new runtime parameters (because it adds key access over existing parameters),
+ /// those parameters will have this prefix.
+ ///
+ private const string RuntimeParameterPrefix = CompiledQueryCache.CompiledQueryParameterPrefix + "entity_equality_";
+
+ private readonly QueryCompilationContext _queryCompilationContext;
+ private readonly IDiagnosticsLogger _logger;
private static readonly MethodInfo _objectEqualsMethodInfo
= typeof(object).GetRuntimeMethod(nameof(object.Equals), new[] { typeof(object), typeof(object) });
public EntityEqualityRewritingExpressionVisitor(QueryCompilationContext queryCompilationContext)
{
- Model = queryCompilationContext.Model;
- Logger = queryCompilationContext.Logger;
+ _queryCompilationContext = queryCompilationContext;
+ _logger = queryCompilationContext.Logger;
}
public Expression Rewrite(Expression expression) => Unwrap(Visit(expression));
protected override Expression VisitConstant(ConstantExpression constantExpression)
=> constantExpression.IsEntityQueryable()
- ? new EntityReferenceExpression(constantExpression, Model.FindEntityType(((IQueryable)constantExpression.Value).ElementType))
+ ? new EntityReferenceExpression(
+ constantExpression,
+ _queryCompilationContext.Model.FindEntityType(((IQueryable)constantExpression.Value).ElementType))
: (Expression)constantExpression;
protected override Expression VisitNew(NewExpression newExpression)
@@ -278,7 +288,7 @@ protected virtual Expression VisitContainsMethodCall(MethodCallExpression method
// Wrap the source with a projection to its primary key, and the item with a primary key access expression
var param = Expression.Parameter(entityType.ClrType, "v");
- var keySelector = Expression.Lambda(param.CreateEFPropertyExpression(keyProperty, makeNullable: false), param);
+ var keySelector = Expression.Lambda(CreatePropertyAccessExpression(param, keyProperty), param);
var keyProjection = Expression.Call(
LinqMethodHelpers.QueryableSelectMethodInfo.MakeGenericMethod(entityType.ClrType, keyProperty.ClrType),
Unwrap(newSource),
@@ -286,7 +296,7 @@ protected virtual Expression VisitContainsMethodCall(MethodCallExpression method
var rewrittenItem = newItem.IsNullConstantExpression()
? Expression.Constant(null)
- : Unwrap(newItem).CreateEFPropertyExpression(keyProperty, makeNullable: false);
+ : CreatePropertyAccessExpression(Unwrap(newItem), keyProperty);
return Expression.Call(
LinqMethodHelpers.QueryableContainsMethodInfo.MakeGenericMethod(keyProperty.ClrType),
@@ -333,7 +343,7 @@ protected virtual Expression VisitOrderingMethodCall(MethodCallExpression method
var rewrittenKeySelector = Expression.Lambda(
ReplacingExpressionVisitor.Replace(
oldParam, param,
- body.CreateEFPropertyExpression(keyProperty, makeNullable: false)),
+ CreatePropertyAccessExpression(body, keyProperty)),
param);
var orderingMethodInfo = GetOrderingMethodInfo(firstOrdering, isAscending);
@@ -499,8 +509,8 @@ protected virtual Expression VisitJoinMethodCall(MethodCallExpression methodCall
}
///
- /// Replaces the lambda's single parameter with a type wrapper based on the given source, and then visits
- /// the lambda's body.
+ /// Replaces the lambda's single parameter with a type wrapper based on the given source, and then visits
+ /// the lambda's body.
///
protected LambdaExpression RewriteAndVisitLambda(LambdaExpression lambda, EntityReferenceExpression source)
=> Expression.Lambda(
@@ -513,8 +523,8 @@ protected LambdaExpression RewriteAndVisitLambda(LambdaExpression lambda, Entity
lambda.Parameters);
///
- /// Replaces the lambda's two parameters with type wrappers based on the given sources, and then visits
- /// the lambda's body.
+ /// Replaces the lambda's two parameters with type wrappers based on the given sources, and then visits
+ /// the lambda's body.
///
protected LambdaExpression RewriteAndVisitLambda(LambdaExpression lambda,
EntityReferenceExpression source1,
@@ -529,10 +539,10 @@ protected LambdaExpression RewriteAndVisitLambda(LambdaExpression lambda,
lambda.Parameters);
///
- /// Receives already-visited left and right operands of an equality expression and applies entity equality rewriting to them,
- /// if possible.
+ /// Receives already-visited left and right operands of an equality expression and applies entity equality rewriting to them,
+ /// if possible.
///
- /// The rewritten entity equality expression, or null if rewriting could not occur for some reason.
+ /// The rewritten entity equality expression, or null if rewriting could not occur for some reason.
protected virtual Expression RewriteEquality(bool equality, Expression left, Expression right)
{
// TODO: Consider throwing if a child has no flowed entity type, but has a Type that corresponds to an entity type on the model.
@@ -597,7 +607,7 @@ private Expression RewriteNullEquality(
// collection navigation is only null if its parent entity is null (null propagation thru navigation)
// it is probable that user wanted to see if the collection is (not) empty
// log warning suggesting to use Any() instead.
- Logger.PossibleUnintendedCollectionNavigationNullComparisonWarning(lastNavigation);
+ _logger.PossibleUnintendedCollectionNavigationNullComparisonWarning(lastNavigation);
return RewriteNullEquality(equality, lastNavigation.DeclaringEntityType, UnwrapLastNavigation(nonNullExpression), null);
}
@@ -609,7 +619,7 @@ private Expression RewriteNullEquality(
// (this is also why we can do it even over a subquery with a composite key)
return Expression.MakeBinary(
equality ? ExpressionType.Equal : ExpressionType.NotEqual,
- nonNullExpression.CreateEFPropertyExpression(keyProperties[0]),
+ CreatePropertyAccessExpression(nonNullExpression, keyProperties[0], makeNullable: true),
Expression.Constant(null));
}
@@ -625,7 +635,7 @@ private Expression RewriteEntityEquality(
if (leftNavigation?.Equals(rightNavigation) == true)
{
// Log a warning that comparing 2 collections causes reference comparison
- Logger.PossibleUnintendedReferenceComparisonWarning(left, right);
+ _logger.PossibleUnintendedReferenceComparisonWarning(left, right);
return RewriteEntityEquality(
equality, leftNavigation.DeclaringEntityType,
UnwrapLastNavigation(left), null,
@@ -688,11 +698,11 @@ protected virtual Expression VisitNullConditional(NullConditionalExpression expr
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
// TODO: DRY with NavigationExpansionHelpers
- protected static Expression CreateKeyAccessExpression(
+ protected Expression CreateKeyAccessExpression(
[NotNull] Expression target,
[NotNull] IReadOnlyList properties)
=> properties.Count == 1
- ? target.CreateEFPropertyExpression(properties[0])
+ ? CreatePropertyAccessExpression(target, properties[0])
: Expression.New(
AnonymousObject.AnonymousObjectCtor,
Expression.NewArrayInit(
@@ -701,11 +711,52 @@ protected static Expression CreateKeyAccessExpression(
.Select(
p =>
Expression.Convert(
- target.CreateEFPropertyExpression(p),
+ CreatePropertyAccessExpression(target, p),
typeof(object)))
.Cast()
.ToArray()));
+ private Expression CreatePropertyAccessExpression(Expression target, IProperty property, bool makeNullable = false)
+ {
+ // The target is a constant - evaluate the property immediately and return the result
+ if (target is ConstantExpression constantExpression)
+ {
+ return Expression.Constant(property.GetGetter().GetClrValue(constantExpression.Value), property.ClrType);
+ }
+
+ // If the target is a query parameter, we can't simply add a property access over it, but must instead cause a new
+ // parameter to be added at runtime, with the value of the property on the base parameter.
+ if (target is ParameterExpression baseParameterExpression
+ && baseParameterExpression.Name.StartsWith(CompiledQueryCache.CompiledQueryParameterPrefix, StringComparison.Ordinal))
+ {
+ // Generate an expression to get the base parameter from the query context's parameter list, and extract the
+ // property from that
+ var lambda = Expression.Lambda(
+ Expression.Call(
+ _parameterValueExtractor,
+ QueryCompilationContext.QueryContextParameter,
+ Expression.Constant(baseParameterExpression.Name, typeof(string)),
+ Expression.Constant(property, typeof(IProperty))),
+ QueryCompilationContext.QueryContextParameter
+ );
+
+ var newParameterName = $"{RuntimeParameterPrefix}{baseParameterExpression.Name.Substring(CompiledQueryCache.CompiledQueryParameterPrefix.Length)}_{property.Name}";
+ _queryCompilationContext.RegisterRuntimeParameter(newParameterName, lambda);
+ return Expression.Parameter(property.ClrType, newParameterName);
+ }
+
+ return target.CreateEFPropertyExpression(property, makeNullable);
+
+ }
+
+ static object ParameterValueExtractor(QueryContext context, string baseParameterName, IProperty property)
+ {
+ var baseParameter = context.ParameterValues[baseParameterName];
+ return baseParameter == null ? null : property.GetGetter().GetClrValue(baseParameter);
+ }
+
+ private static readonly MethodInfo _parameterValueExtractor
+ = typeof(EntityEqualityRewritingExpressionVisitor).GetMethod(nameof(ParameterValueExtractor), BindingFlags.NonPublic | BindingFlags.Static);
protected static Expression UnwrapLastNavigation(Expression expression)
=> (expression as MemberExpression)?.Expression
@@ -731,7 +782,7 @@ public class EntityReferenceExpression : Expression
public override ExpressionType NodeType => ExpressionType.Extension;
///
- /// The underlying expression being wrapped.
+ /// The underlying expression being wrapped.
///
[NotNull]
public Expression Underlying { get; }
@@ -789,9 +840,9 @@ public EntityReferenceExpression(
}
///
- /// Attempts to find as a navigation from the current node,
- /// and if successful, returns a new wrapping the
- /// given expression. Otherwise returns the given expression without wrapping it.
+ /// Attempts to find as a navigation from the current node,
+ /// and if successful, returns a new wrapping the
+ /// given expression. Otherwise returns the given expression without wrapping it.
///
public virtual Expression TraverseProperty(string propertyName, Expression destinationExpression)
{
diff --git a/src/EFCore/Query/Pipeline/ParameterExtractingExpressionVisitor.cs b/src/EFCore/Query/Pipeline/ParameterExtractingExpressionVisitor.cs
index 4c9c6a6d3f8..2acb85eb3c5 100644
--- a/src/EFCore/Query/Pipeline/ParameterExtractingExpressionVisitor.cs
+++ b/src/EFCore/Query/Pipeline/ParameterExtractingExpressionVisitor.cs
@@ -175,7 +175,7 @@ private Expression TryGetConstantValue(Expression expression)
{
if (_evaluatableExpressions.ContainsKey(expression))
{
- var value = GetValue(expression, out var _);
+ var value = GetValue(expression, out _);
if (value is bool)
{
diff --git a/src/EFCore/Query/Pipeline/QueryCompilationContext.cs b/src/EFCore/Query/Pipeline/QueryCompilationContext.cs
index 0d2f34eb960..7ad70bafbdf 100644
--- a/src/EFCore/Query/Pipeline/QueryCompilationContext.cs
+++ b/src/EFCore/Query/Pipeline/QueryCompilationContext.cs
@@ -3,7 +3,9 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Linq.Expressions;
+using System.Reflection;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
@@ -20,6 +22,13 @@ public class QueryCompilationContext
private readonly IShapedQueryOptimizerFactory _shapedQueryOptimizerFactory;
private readonly IShapedQueryCompilingExpressionVisitorFactory _shapedQueryCompilingExpressionVisitorFactory;
+ ///
+ /// A dictionary mapping parameter names to lambdas that, given a QueryContext, can extract that parameter's value.
+ /// This is needed for cases where we need to introduce a parameter during the compilation phase (e.g. entity equality rewrites
+ /// a parameter to an ID property on that parameter).
+ ///
+ private Dictionary _runtimeParameters;
+
public QueryCompilationContext(
IModel model,
IQueryOptimizerFactory queryOptimizerFactory,
@@ -42,7 +51,6 @@ public QueryCompilationContext(
_queryableMethodTranslatingExpressionVisitorFactory = queryableMethodTranslatingExpressionVisitorFactory;
_shapedQueryOptimizerFactory = shapedQueryOptimizerFactory;
_shapedQueryCompilingExpressionVisitorFactory = shapedQueryCompilingExpressionVisitorFactory;
-
}
public bool Async { get; }
@@ -69,6 +77,10 @@ public virtual Func CreateQueryExecutor(Expressi
// Inject tracking
query = _shapedQueryCompilingExpressionVisitorFactory.Create(this).Visit(query);
+ // If any additional parameters were added during the compilation phase (e.g. entity equality ID expression),
+ // wrap the query with code adding those parameters to the query context
+ query = InsertRuntimeParameters(query);
+
var queryExecutorExpression = Expression.Lambda>(
query,
QueryContextParameter);
@@ -82,5 +94,45 @@ public virtual Func CreateQueryExecutor(Expressi
Logger.QueryExecutionPlanned(new ExpressionPrinter(), queryExecutorExpression);
}
}
+
+ ///
+ /// Registers a runtime parameter that is being added at some point during the compilation phase.
+ /// A lambda must be provided, which will extract the parameter's value from the QueryContext every time
+ /// the query is executed.
+ ///
+ public void RegisterRuntimeParameter(string name, LambdaExpression valueExtractor)
+ {
+ if (valueExtractor.Parameters.Count != 1
+ || valueExtractor.Parameters[0] != QueryContextParameter
+ || valueExtractor.ReturnType != typeof(object))
+ {
+ throw new ArgumentException("Runtime parameter extraction lambda must have one QueryContext parameter and return an object",
+ nameof(valueExtractor));
+ }
+
+ if (_runtimeParameters == null)
+ {
+ _runtimeParameters = new Dictionary();
+ }
+
+ _runtimeParameters[name] = valueExtractor;
+ }
+
+ private Expression InsertRuntimeParameters(Expression query)
+ => _runtimeParameters == null
+ ? query
+ : Expression.Block(_runtimeParameters
+ .Select(kv =>
+ Expression.Call(
+ QueryContextParameter,
+ _queryContextAddParameterMethodInfo,
+ Expression.Constant(kv.Key),
+ Expression.Invoke(kv.Value, QueryContextParameter)))
+ .Append(query));
+
+ private static readonly MethodInfo _queryContextAddParameterMethodInfo
+ = typeof(QueryContext)
+ .GetTypeInfo()
+ .GetDeclaredMethod(nameof(QueryContext.AddParameter));
}
}
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.ResultOperators.cs b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.ResultOperators.cs
index 6b18edd80a9..e1c88e99cdc 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.ResultOperators.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.ResultOperators.cs
@@ -1196,6 +1196,7 @@ FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
}
+ [ConditionalTheory(Skip = "Issue#14935 (Contains not implemented)")]
public override void Contains_over_entityType_should_rewrite_to_identity_equality()
{
base.Contains_over_entityType_should_rewrite_to_identity_equality();
@@ -1206,6 +1207,17 @@ FROM root c
WHERE ((c[""Discriminator""] = ""Order"") AND (c[""OrderID""] = 10248))");
}
+ [ConditionalTheory(Skip = "Issue#14935 (Contains not implemented)")]
+ public override void Contains_over_entityType_with_null_should_rewrite_to_identity_equality()
+ {
+ base.Contains_over_entityType_with_null_should_rewrite_to_identity_equality();
+
+ AssertSql(
+ @"SELECT c
+FROM root c
+WHERE ((c[""Discriminator""] = ""Order"") AND (c[""OrderID""] = 10248))");
+ }
+
public override void Contains_over_entityType_should_materialize_when_composite()
{
base.Contains_over_entityType_should_materialize_when_composite();
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs
index ab77aac8522..1391bf9b408 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs
@@ -116,12 +116,26 @@ public override async Task Entity_equality_local(bool isAsync)
{
await base.Entity_equality_local(isAsync);
+ AssertSql(
+ @"@__entity_equality_local_0_CustomerID='ANATR'
+
+SELECT c[""CustomerID""]
+FROM root c
+WHERE ((c[""Discriminator""] = ""Customer"") AND (c[""CustomerID""] = @__entity_equality_local_0_CustomerID))");
+ }
+
+ [ConditionalTheory(Skip = "Issue#14935")]
+ public override async Task Entity_equality_local_composite_key(bool isAsync)
+ {
+ await base.Entity_equality_local_composite_key(isAsync);
+
AssertSql(
@"SELECT c
FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
}
+ [ConditionalTheory(Skip = "Issue#14935")]
public override async Task Join_with_entity_equality_local_on_both_sources(bool isAsync)
{
await base.Join_with_entity_equality_local_on_both_sources(isAsync);
@@ -136,6 +150,17 @@ public override async Task Entity_equality_local_inline(bool isAsync)
{
await base.Entity_equality_local_inline(isAsync);
+ AssertSql(
+ @"SELECT c[""CustomerID""]
+FROM root c
+WHERE ((c[""Discriminator""] = ""Customer"") AND (c[""CustomerID""] = ""ANATR""))");
+ }
+
+ [ConditionalTheory(Skip = "Issue#14935")]
+ public override async Task Entity_equality_local_inline_composite_key(bool isAsync)
+ {
+ await base.Entity_equality_local_inline_composite_key(isAsync);
+
AssertSql(
@"SELECT c
FROM root c
@@ -321,6 +346,7 @@ FROM root c
WHERE (c[""Discriminator""] = ""Employee"")");
}
+ [ConditionalTheory(Skip = "Issue#14935")]
public override async Task Where_query_composition_entity_equality_one_element_FirstOrDefault(bool isAsync)
{
await base.Where_query_composition_entity_equality_one_element_FirstOrDefault(isAsync);
@@ -341,6 +367,7 @@ FROM root c
WHERE (c[""Discriminator""] = ""Employee"")");
}
+ [ConditionalTheory(Skip = "Issue#14935")]
public override async Task Where_query_composition_entity_equality_no_elements_FirstOrDefault(bool isAsync)
{
await base.Where_query_composition_entity_equality_no_elements_FirstOrDefault(isAsync);
@@ -351,6 +378,7 @@ FROM root c
WHERE (c[""Discriminator""] = ""Employee"")");
}
+ [ConditionalTheory(Skip = "Issue#14935")]
public override async Task Where_query_composition_entity_equality_multiple_elements_FirstOrDefault(bool isAsync)
{
await base.Where_query_composition_entity_equality_multiple_elements_FirstOrDefault(isAsync);
@@ -3721,6 +3749,7 @@ FROM root c
WHERE (c[""Discriminator""] = ""Customer"")");
}
+ [ConditionalTheory(Skip = "Issue#14935")]
public override async Task Let_entity_equality_to_other_entity(bool isAsync)
{
await base.Let_entity_equality_to_other_entity(isAsync);
diff --git a/test/EFCore.Relational.Specification.Tests/Query/FromSqlQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/FromSqlQueryTestBase.cs
index 7a51000b1d3..7c848694364 100644
--- a/test/EFCore.Relational.Specification.Tests/Query/FromSqlQueryTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/Query/FromSqlQueryTestBase.cs
@@ -989,7 +989,7 @@ public virtual void FromSqlRaw_does_not_parameterize_interpolated_string()
}
}
- [ConditionalFact(Skip = "#15855")]
+ [ConditionalFact]
public virtual void Entity_equality_through_fromsql()
{
using (var context = CreateContext())
@@ -1002,7 +1002,7 @@ public virtual void Entity_equality_through_fromsql()
})
.ToArray();
- Assert.Equal(1, actual.Length);
+ Assert.Equal(5, actual.Length);
}
}
diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs
index 663da301d0e..d0141b7ab81 100644
--- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs
@@ -39,7 +39,7 @@ protected ComplexNavigationsQueryTestBase(TFixture fixture)
{
}
- [ConditionalTheory(Skip = "Issue#15855")]
+ [ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Entity_equality_empty(bool isAsync)
{
@@ -146,7 +146,7 @@ public virtual Task Key_equality_using_property_method_and_member_expression3(bo
(e, a) => Assert.Equal(e.Id, a.Id));
}
- [ConditionalTheory(Skip = "Issue#15855")]
+ [ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Key_equality_navigation_converted_to_FK(bool isAsync)
{
@@ -163,7 +163,7 @@ public virtual Task Key_equality_navigation_converted_to_FK(bool isAsync)
(e, a) => Assert.Equal(e.Id, a.Id));
}
- [ConditionalTheory(Skip = "Issue#15855")]
+ [ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Key_equality_two_conditions_on_same_navigation(bool isAsync)
{
@@ -185,7 +185,7 @@ public virtual Task Key_equality_two_conditions_on_same_navigation(bool isAsync)
(e, a) => Assert.Equal(e.Id, a.Id));
}
- [ConditionalTheory(Skip = "Issue#15855")]
+ [ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Key_equality_two_conditions_on_same_navigation2(bool isAsync)
{
diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs
index 8638482a53a..b567a4d5ea6 100644
--- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs
+++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs
@@ -1497,7 +1497,7 @@ public virtual Task OrderBy_Skip_Last_gives_correct_result(bool isAsync)
entryCount: 1);
}
- [ConditionalFact(Skip = "#15855")]
+ [ConditionalFact]
public virtual void Contains_over_entityType_should_rewrite_to_identity_equality()
{
using (var context = CreateContext())
@@ -1510,7 +1510,7 @@ var query
}
}
- [ConditionalFact(Skip = "#15855")]
+ [ConditionalFact]
public virtual void Contains_over_entityType_with_null_should_rewrite_to_identity_equality()
{
using (var context = CreateContext())
@@ -1519,7 +1519,7 @@ var query
= context.Orders.Where(o => o.CustomerID == "VINET")
.Contains(null);
- Assert.True(query);
+ Assert.False(query);
}
}
diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Where.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Where.cs
index d66f0ff3115..92727b0148c 100644
--- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Where.cs
+++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Where.cs
@@ -1517,7 +1517,7 @@ await AssertQuery(
entryCount: 1);
}
- [ConditionalTheory(Skip = "#15855")]
+ [ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Where_poco_closure(bool isAsync)
{
@@ -1970,7 +1970,7 @@ public virtual Task Where_subquery_FirstOrDefault_is_null(bool isAsync)
entryCount: 2);
}
- [ConditionalTheory(Skip = "Issue#15855")]
+ [ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_subquery_FirstOrDefault_compared_to_entity(bool isAsync)
{
diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs
index f5c5bdd0d1e..e008446554b 100644
--- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs
@@ -364,7 +364,7 @@ from c in cs
select c.CustomerID);
}
- [ConditionalTheory(Skip = "#15855")]
+ [ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Entity_equality_local(bool isAsync)
{
@@ -381,9 +381,44 @@ from c in cs
select c.CustomerID);
}
- // issue #12871
- //[ConditionalTheory]
- //[MemberData(nameof(IsAsyncData))]
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Entity_equality_local_composite_key(bool isAsync)
+ {
+ var local = new OrderDetail
+ {
+ OrderID = 10248,
+ ProductID = 11
+ };
+
+ return AssertQuery(
+ isAsync,
+ odt =>
+ from od in odt
+ where od.Equals(local)
+ select od,
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Entity_equality_local_double_check(bool isAsync)
+ {
+ var local = new Customer
+ {
+ CustomerID = "ANATR"
+ };
+
+ return AssertQuery(
+ isAsync,
+ cs =>
+ from c in cs
+ where c == local && local == c
+ select c.CustomerID);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
public virtual Task Join_with_entity_equality_local_on_both_sources(bool isAsync)
{
var local = new Customer
@@ -402,7 +437,7 @@ from c2 in cs
select c2, o => o, i => i, (o, i) => o).Select(e => e.CustomerID));
}
- [ConditionalTheory(Skip = "#15855")]
+ [ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Entity_equality_local_inline(bool isAsync)
{
@@ -417,18 +452,18 @@ from c in cs
select c.CustomerID);
}
- [ConditionalTheory(Skip = "#15855")]
+ [ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Entity_equality_local_inline_composite_key(bool isAsync)
=> AssertQuery(
isAsync,
odt =>
from od in odt
- where od == new OrderDetail
+ where od.Equals(new OrderDetail
{
OrderID = 10248,
ProductID = 11
- }
+ })
select od,
entryCount: 1);
@@ -1970,7 +2005,7 @@ from e1 in es.Take(3)
select e1);
}
- [ConditionalTheory(Skip = "#15855")]
+ [ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_query_composition_entity_equality_one_element_FirstOrDefault(bool isAsync)
{
@@ -1994,7 +2029,7 @@ from e1 in es.Take(3)
select e1);
}
- [ConditionalFact]
+ [ConditionalFact(Skip = "#15559")]
public virtual void Where_query_composition_entity_equality_no_elements_Single()
{
using (var ctx = CreateContext())
@@ -2007,7 +2042,7 @@ public virtual void Where_query_composition_entity_equality_no_elements_Single()
}
}
- [ConditionalTheory(Skip = "#15855")]
+ [ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_query_composition_entity_equality_no_elements_FirstOrDefault(bool isAsync)
{
@@ -2019,7 +2054,7 @@ from e1 in es
select e1);
}
- [ConditionalFact]
+ [ConditionalFact(Skip = "#15559")]
public virtual void Where_query_composition_entity_equality_multiple_elements_SingleOrDefault()
{
using (var ctx = CreateContext())
@@ -2032,7 +2067,7 @@ public virtual void Where_query_composition_entity_equality_multiple_elements_Si
}
}
- [ConditionalTheory(Skip = "#15855")]
+ [ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_query_composition_entity_equality_multiple_elements_FirstOrDefault(bool isAsync)
{
@@ -5821,7 +5856,7 @@ public virtual Task Let_entity_equality_to_null(bool isAsync)
});
}
- [ConditionalTheory(Skip = "#15855")]
+ [ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Let_entity_equality_to_other_entity(bool isAsync)
{
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs
index 8c5e0885f24..54e64a2c4e3 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs
@@ -1183,15 +1183,16 @@ public override void Contains_over_entityType_should_rewrite_to_identity_equalit
FROM [Orders] AS [o]
WHERE [o].[OrderID] = 10248",
//
- @"@__p_0_OrderID='10248'
+ @"@__entity_equality_p_0_OrderID='10248'
SELECT CASE
- WHEN @__p_0_OrderID IN (
+ WHEN @__entity_equality_p_0_OrderID IN (
SELECT [o].[OrderID]
FROM [Orders] AS [o]
- WHERE [o].[CustomerID] = N'VINET'
+ WHERE ([o].[CustomerID] = N'VINET') AND [o].[CustomerID] IS NOT NULL
)
- THEN CAST(1 AS bit) ELSE CAST(0 AS bit)
+ THEN CAST(1 AS bit)
+ ELSE CAST(0 AS bit)
END");
}
@@ -1200,7 +1201,17 @@ public override void Contains_over_entityType_with_null_should_rewrite_to_identi
base.Contains_over_entityType_with_null_should_rewrite_to_identity_equality();
AssertSql(
- @"TODO");
+ @"@__entity_equality_p_0_OrderID='' (Nullable = false) (DbType = Int32)
+
+SELECT CASE
+ WHEN @__entity_equality_p_0_OrderID IN (
+ SELECT [o].[OrderID]
+ FROM [Orders] AS [o]
+ WHERE ([o].[CustomerID] = N'VINET') AND [o].[CustomerID] IS NOT NULL
+ )
+ THEN CAST(1 AS bit)
+ ELSE CAST(0 AS bit)
+END");
}
public override void Contains_over_entityType_should_materialize_when_composite()
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Where.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Where.cs
index 5d70cd50ec7..812bdaee31e 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Where.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Where.cs
@@ -1744,12 +1744,15 @@ public override async Task Where_subquery_FirstOrDefault_compared_to_entity(bool
AssertSql(
@"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region]
FROM [Customers] AS [c]
-WHERE (
+WHERE ((
SELECT TOP(1) [o].[OrderID]
FROM [Orders] AS [o]
- WHERE [c].[CustomerID] = [o].[CustomerID]
- ORDER BY [o].[OrderID]
-) = 10243");
+ WHERE ([c].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL
+ ORDER BY [o].[OrderID]) = 10243) AND (
+ SELECT TOP(1) [o].[OrderID]
+ FROM [Orders] AS [o]
+ WHERE ([c].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL
+ ORDER BY [o].[OrderID]) IS NOT NULL");
}
public override async Task Time_of_day_datetime(bool isAsync)
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs
index 25884ae75ed..ab11ec00507 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs
@@ -175,11 +175,36 @@ public override async Task Entity_equality_local(bool isAsync)
await base.Entity_equality_local(isAsync);
AssertSql(
- @"@__local_0_CustomerID='ANATR' (Nullable = false) (Size = 5)
+ @"@__entity_equality_local_0_CustomerID='ANATR' (Size = 5)
SELECT [c].[CustomerID]
FROM [Customers] AS [c]
-WHERE [c].[CustomerID] = @__local_0_CustomerID");
+WHERE ([c].[CustomerID] = @__entity_equality_local_0_CustomerID) AND @__entity_equality_local_0_CustomerID IS NOT NULL");
+ }
+
+ public override async Task Entity_equality_local_composite_key(bool isAsync)
+ {
+ await base.Entity_equality_local_composite_key(isAsync);
+
+ AssertSql(
+ @"@__entity_equality_local_0_OrderID='10248'
+@__entity_equality_local_0_ProductID='11'
+
+SELECT [o].[OrderID], [o].[ProductID], [o].[Discount], [o].[Quantity], [o].[UnitPrice]
+FROM [Order Details] AS [o]
+WHERE (([o].[OrderID] = @__entity_equality_local_0_OrderID) AND @__entity_equality_local_0_OrderID IS NOT NULL) AND (([o].[ProductID] = @__entity_equality_local_0_ProductID) AND @__entity_equality_local_0_ProductID IS NOT NULL)");
+ }
+
+ public override async Task Entity_equality_local_double_check(bool isAsync)
+ {
+ await base.Entity_equality_local_double_check(isAsync);
+
+ AssertSql(
+ @"@__entity_equality_local_0_CustomerID='ANATR' (Size = 5)
+
+SELECT [c].[CustomerID]
+FROM [Customers] AS [c]
+WHERE (([c].[CustomerID] = @__entity_equality_local_0_CustomerID) AND @__entity_equality_local_0_CustomerID IS NOT NULL) AND ((@__entity_equality_local_0_CustomerID = [c].[CustomerID]) AND @__entity_equality_local_0_CustomerID IS NOT NULL)");
}
public override async Task Join_with_entity_equality_local_on_both_sources(bool isAsync)
@@ -187,7 +212,16 @@ public override async Task Join_with_entity_equality_local_on_both_sources(bool
await base.Join_with_entity_equality_local_on_both_sources(isAsync);
AssertSql(
- "");
+ @"@__entity_equality_local_0_CustomerID='ANATR' (Size = 5)
+
+SELECT [c].[CustomerID]
+FROM [Customers] AS [c]
+INNER JOIN (
+ SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region]
+ FROM [Customers] AS [c0]
+ WHERE ([c0].[CustomerID] = @__entity_equality_local_0_CustomerID) AND @__entity_equality_local_0_CustomerID IS NOT NULL
+) AS [t] ON [c].[CustomerID] = [t].[CustomerID]
+WHERE ([c].[CustomerID] = @__entity_equality_local_0_CustomerID) AND @__entity_equality_local_0_CustomerID IS NOT NULL");
}
public override async Task Entity_equality_local_inline(bool isAsync)
@@ -204,7 +238,10 @@ public override async Task Entity_equality_local_inline_composite_key(bool isAsy
{
await base.Entity_equality_local_inline_composite_key(isAsync);
- // TODO: AssertSql
+ AssertSql(
+ @"SELECT [o].[OrderID], [o].[ProductID], [o].[Discount], [o].[Quantity], [o].[UnitPrice]
+FROM [Order Details] AS [o]
+WHERE ([o].[OrderID] = 10248) AND ([o].[ProductID] = 11)");
}
public override async Task Entity_equality_null(bool isAsync)
@@ -506,13 +543,15 @@ public override async Task Where_query_composition_entity_equality_one_element_F
await base.Where_query_composition_entity_equality_one_element_FirstOrDefault(isAsync);
AssertSql(
- @"SELECT [e1].[EmployeeID], [e1].[City], [e1].[Country], [e1].[FirstName], [e1].[ReportsTo], [e1].[Title]
-FROM [Employees] AS [e1]
-WHERE (
- SELECT TOP(1) [e2].[EmployeeID]
- FROM [Employees] AS [e2]
- WHERE [e2].[EmployeeID] = [e1].[ReportsTo]
-) = CAST(0 AS bigint)");
+ @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title]
+FROM [Employees] AS [e]
+WHERE ((
+ SELECT TOP(1) [e0].[EmployeeID]
+ FROM [Employees] AS [e0]
+ WHERE ([e0].[EmployeeID] = [e].[ReportsTo]) AND [e].[ReportsTo] IS NOT NULL) = 0) AND (
+ SELECT TOP(1) [e0].[EmployeeID]
+ FROM [Employees] AS [e0]
+ WHERE ([e0].[EmployeeID] = [e].[ReportsTo]) AND [e].[ReportsTo] IS NOT NULL) IS NOT NULL");
}
public override async Task Where_query_composition_entity_equality_no_elements_SingleOrDefault(bool isAsync)
@@ -546,13 +585,15 @@ public override async Task Where_query_composition_entity_equality_no_elements_F
await base.Where_query_composition_entity_equality_no_elements_FirstOrDefault(isAsync);
AssertSql(
- @"SELECT [e1].[EmployeeID], [e1].[City], [e1].[Country], [e1].[FirstName], [e1].[ReportsTo], [e1].[Title]
-FROM [Employees] AS [e1]
-WHERE (
- SELECT TOP(1) [e2].[EmployeeID]
- FROM [Employees] AS [e2]
- WHERE [e2].[EmployeeID] = 42
-) = CAST(0 AS bigint)");
+ @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title]
+FROM [Employees] AS [e]
+WHERE ((
+ SELECT TOP(1) [e0].[EmployeeID]
+ FROM [Employees] AS [e0]
+ WHERE [e0].[EmployeeID] = 42) = 0) AND (
+ SELECT TOP(1) [e0].[EmployeeID]
+ FROM [Employees] AS [e0]
+ WHERE [e0].[EmployeeID] = 42) IS NOT NULL");
}
public override async Task Where_query_composition_entity_equality_multiple_elements_FirstOrDefault(bool isAsync)
@@ -560,13 +601,15 @@ public override async Task Where_query_composition_entity_equality_multiple_elem
await base.Where_query_composition_entity_equality_multiple_elements_FirstOrDefault(isAsync);
AssertSql(
- @"SELECT [e1].[EmployeeID], [e1].[City], [e1].[Country], [e1].[FirstName], [e1].[ReportsTo], [e1].[Title]
-FROM [Employees] AS [e1]
-WHERE (
- SELECT TOP(1) [e2].[EmployeeID]
- FROM [Employees] AS [e2]
- WHERE ([e2].[EmployeeID] <> [e1].[ReportsTo]) OR [e1].[ReportsTo] IS NULL
-) = CAST(0 AS bigint)");
+ @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title]
+FROM [Employees] AS [e]
+WHERE ((
+ SELECT TOP(1) [e0].[EmployeeID]
+ FROM [Employees] AS [e0]
+ WHERE ([e0].[EmployeeID] <> [e].[ReportsTo]) OR [e].[ReportsTo] IS NULL) = 0) AND (
+ SELECT TOP(1) [e0].[EmployeeID]
+ FROM [Employees] AS [e0]
+ WHERE ([e0].[EmployeeID] <> [e].[ReportsTo]) OR [e].[ReportsTo] IS NULL) IS NOT NULL");
}
public override async Task Where_query_composition2(bool isAsync)
@@ -4740,78 +4783,21 @@ public override async Task Let_entity_equality_to_other_entity(bool isAsync)
await base.Let_entity_equality_to_other_entity(isAsync);
AssertSql(
- @"SELECT [c].[CustomerID], CASE
- WHEN (
- SELECT TOP(1) [o0].[OrderID]
- FROM [Orders] AS [o0]
- WHERE [c].[CustomerID] = [o0].[CustomerID]
- ORDER BY [o0].[OrderDate]
- ) IS NOT NULL
- THEN (
- SELECT TOP(1) [o1].[OrderDate]
- FROM [Orders] AS [o1]
- WHERE [c].[CustomerID] = [o1].[CustomerID]
- ORDER BY [o1].[OrderDate]
- ) ELSE NULL
-END AS [A]
+ @"SELECT [c].[CustomerID], (
+ SELECT TOP(1) [o].[OrderDate]
+ FROM [Orders] AS [o]
+ WHERE ([c].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL
+ ORDER BY [o].[OrderDate]) AS [A]
FROM [Customers] AS [c]
-WHERE [c].[CustomerID] LIKE N'A%'",
- //
- @"@_outer_CustomerID='ALFKI' (Size = 5)
-
-SELECT TOP(1) [e].[OrderID], [e].[CustomerID], [e].[EmployeeID], [e].[OrderDate]
-FROM [Orders] AS [e]
-WHERE @_outer_CustomerID = [e].[CustomerID]
-ORDER BY [e].[OrderDate]",
- //
- @"@_outer_CustomerID1='ALFKI' (Size = 5)
-
-SELECT TOP(1) [e1].[OrderID], [e1].[CustomerID], [e1].[EmployeeID], [e1].[OrderDate]
-FROM [Orders] AS [e1]
-WHERE @_outer_CustomerID1 = [e1].[CustomerID]
-ORDER BY [e1].[OrderDate]",
- //
- @"@_outer_CustomerID='ANATR' (Size = 5)
-
-SELECT TOP(1) [e].[OrderID], [e].[CustomerID], [e].[EmployeeID], [e].[OrderDate]
-FROM [Orders] AS [e]
-WHERE @_outer_CustomerID = [e].[CustomerID]
-ORDER BY [e].[OrderDate]",
- //
- @"@_outer_CustomerID1='ANATR' (Size = 5)
-
-SELECT TOP(1) [e1].[OrderID], [e1].[CustomerID], [e1].[EmployeeID], [e1].[OrderDate]
-FROM [Orders] AS [e1]
-WHERE @_outer_CustomerID1 = [e1].[CustomerID]
-ORDER BY [e1].[OrderDate]",
- //
- @"@_outer_CustomerID='ANTON' (Size = 5)
-
-SELECT TOP(1) [e].[OrderID], [e].[CustomerID], [e].[EmployeeID], [e].[OrderDate]
-FROM [Orders] AS [e]
-WHERE @_outer_CustomerID = [e].[CustomerID]
-ORDER BY [e].[OrderDate]",
- //
- @"@_outer_CustomerID1='ANTON' (Size = 5)
-
-SELECT TOP(1) [e1].[OrderID], [e1].[CustomerID], [e1].[EmployeeID], [e1].[OrderDate]
-FROM [Orders] AS [e1]
-WHERE @_outer_CustomerID1 = [e1].[CustomerID]
-ORDER BY [e1].[OrderDate]",
- //
- @"@_outer_CustomerID='AROUT' (Size = 5)
-
-SELECT TOP(1) [e].[OrderID], [e].[CustomerID], [e].[EmployeeID], [e].[OrderDate]
-FROM [Orders] AS [e]
-WHERE @_outer_CustomerID = [e].[CustomerID]
-ORDER BY [e].[OrderDate]",
- //
- @"@_outer_CustomerID1='AROUT' (Size = 5)
-
-SELECT TOP(1) [e1].[OrderID], [e1].[CustomerID], [e1].[EmployeeID], [e1].[OrderDate]
-FROM [Orders] AS [e1]
-WHERE @_outer_CustomerID1 = [e1].[CustomerID]
-ORDER BY [e1].[OrderDate]");
+WHERE ([c].[CustomerID] LIKE N'A%') AND (((
+ SELECT TOP(1) [o0].[OrderID]
+ FROM [Orders] AS [o0]
+ WHERE ([c].[CustomerID] = [o0].[CustomerID]) AND [o0].[CustomerID] IS NOT NULL
+ ORDER BY [o0].[OrderDate]) <> 0) OR (
+ SELECT TOP(1) [o0].[OrderID]
+ FROM [Orders] AS [o0]
+ WHERE ([c].[CustomerID] = [o0].[CustomerID]) AND [o0].[CustomerID] IS NOT NULL
+ ORDER BY [o0].[OrderDate]) IS NULL)");
}
// public override async Task SelectMany_after_client_method(bool isAsync)