diff --git a/src/EFCore.Cosmos/Extensions/CosmosQueryableExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosQueryableExtensions.cs
index 7def403562a..68417190ef9 100644
--- a/src/EFCore.Cosmos/Extensions/CosmosQueryableExtensions.cs
+++ b/src/EFCore.Cosmos/Extensions/CosmosQueryableExtensions.cs
@@ -35,9 +35,7 @@ internal static readonly MethodInfo WithPartitionKeyMethodInfo
/// The source query.
/// The partition key value.
/// A new query with the set partition key.
- public static IQueryable WithPartitionKey(
- this IQueryable source,
- [NotParameterized] string partitionKey)
+ public static IQueryable WithPartitionKey(this IQueryable source, string partitionKey)
where TEntity : class
=> WithPartitionKey(source, partitionKey, []);
@@ -56,8 +54,8 @@ public static IQueryable WithPartitionKey(
/// A new query with the set partition key.
public static IQueryable WithPartitionKey(
this IQueryable source,
- [NotParameterized] object partitionKeyValue,
- [NotParameterized] params object[] additionalPartitionKeyValues)
+ object partitionKeyValue,
+ params object[] additionalPartitionKeyValues)
where TEntity : class
{
Check.NotNull(partitionKeyValue, nameof(partitionKeyValue));
diff --git a/src/EFCore.Cosmos/Extensions/Internal/PartitionKeyBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/Internal/PartitionKeyBuilderExtensions.cs
index 38370530003..a7775eb0247 100644
--- a/src/EFCore.Cosmos/Extensions/Internal/PartitionKeyBuilderExtensions.cs
+++ b/src/EFCore.Cosmos/Extensions/Internal/PartitionKeyBuilderExtensions.cs
@@ -35,36 +35,37 @@ public static PartitionKeyBuilder Add(this PartitionKeyBuilder builder, object?
else
{
var expectedType = (converter?.ProviderClrType ?? property?.ClrType)?.UnwrapNullableType();
- if (value is string stringValue)
+ switch (value)
{
- if (expectedType != null && expectedType != typeof(string))
- {
- CheckType(typeof(string));
- }
+ case string stringValue:
+ if (expectedType != null && expectedType != typeof(string))
+ {
+ CheckType(typeof(string));
+ }
- builder.Add(stringValue);
- }
- else if (value is bool boolValue)
- {
- if (expectedType != null && expectedType != typeof(bool))
- {
- CheckType(typeof(bool));
- }
+ builder.Add(stringValue);
+ break;
- builder.Add(boolValue);
- }
- else if (value.GetType().IsNumeric())
- {
- if (expectedType != null && !expectedType.IsNumeric())
- {
- CheckType(value.GetType());
- }
+ case bool boolValue:
+ if (expectedType != null && expectedType != typeof(bool))
+ {
+ CheckType(typeof(bool));
+ }
- builder.Add(Convert.ToDouble(value));
- }
- else
- {
- throw new InvalidOperationException(CosmosStrings.PartitionKeyBadValue(value.GetType()));
+ builder.Add(boolValue);
+ break;
+
+ case var _ when value.GetType().IsNumeric():
+ if (expectedType != null && !expectedType.IsNumeric())
+ {
+ CheckType(value.GetType());
+ }
+
+ builder.Add(Convert.ToDouble(value));
+ break;
+
+ default:
+ throw new InvalidOperationException(CosmosStrings.PartitionKeyBadValue(value.GetType()));
}
void CheckType(Type actualType)
diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs
index 4402ce74916..de52cff2d77 100644
--- a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs
+++ b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs
@@ -107,6 +107,14 @@ public static string IdNonStringStoreType(object? idProperty, object? entityType
GetString("IdNonStringStoreType", nameof(idProperty), nameof(entityType), nameof(propertyType)),
idProperty, entityType, propertyType);
+ ///
+ /// {actual} partition key values were provided, but the entity type '{entityType}' has {expected} partition key values defined.
+ ///
+ public static string IncorrectPartitionKeyNumber(object? entityType, object? actual, object? expected)
+ => string.Format(
+ GetString("IncorrectPartitionKeyNumber", nameof(entityType), nameof(actual), nameof(expected)),
+ entityType, actual, expected);
+
///
/// The entity type '{entityType}' has an index defined over properties '{properties}'. The Azure Cosmos DB provider for EF Core currently does not support index definitions.
///
@@ -166,12 +174,12 @@ public static string MissingOrderingInSelectExpression
=> GetString("MissingOrderingInSelectExpression");
///
- /// Cosmos container '{container1}' is referenced by the query, but '{container2}' is already being referenced. A query can only reference a single Cosmos container.
+ /// Root entity type '{entityType1}' is referenced by the query, but '{entityType2}' is already being referenced. A query can only reference a single root entity type.
///
- public static string MultipleContainersReferencedInQuery(object? container1, object? container2)
+ public static string MultipleRootEntityTypesReferencedInQuery(object? entityType1, object? entityType2)
=> string.Format(
- GetString("MultipleContainersReferencedInQuery", nameof(container1), nameof(container2)),
- container1, container2);
+ GetString("MultipleRootEntityTypesReferencedInQuery", nameof(entityType1), nameof(entityType2)),
+ entityType1, entityType2);
///
/// Navigation '{entityType}.{navigationName}' doesn't point to an embedded entity.
@@ -335,14 +343,6 @@ public static string PartitionKeyBadValueType(object? propertyType, object? enti
GetString("PartitionKeyBadValueType", nameof(propertyType), nameof(entityType), nameof(property), nameof(valueType)),
propertyType, entityType, property, valueType);
- ///
- /// The partition key specified in the 'WithPartitionKey' call '{partitionKey1}' and the partition key specified in the 'Where' predicate '{partitionKey2}' must be identical to return any results. Remove one of them.
- ///
- public static string PartitionKeyMismatch(object? partitionKey1, object? partitionKey2)
- => string.Format(
- GetString("PartitionKeyMismatch", nameof(partitionKey1), nameof(partitionKey2)),
- partitionKey1, partitionKey2);
-
///
/// Unable to execute a 'ReadItem' query since the partition key value is missing. Consider using the 'WithPartitionKey' method on the query to specify partition key to use.
///
@@ -444,11 +444,23 @@ public static string VisitChildrenMustBeOverridden
=> GetString("VisitChildrenMustBeOverridden");
///
- /// 'WithPartitionKeyMethodInfo' can only be called on a entity query root. See https://aka.ms/efdocs-cosmos-partition-keys for more information.
+ /// 'WithPartitionKey' can only be called once in a query. See https://aka.ms/efdocs-cosmos-partition-keys for more information.
+ ///
+ public static string WithPartitionKeyAlreadyCalled
+ => GetString("WithPartitionKeyAlreadyCalled");
+
+ ///
+ /// 'WithPartitionKey' can only be called on a entity query root. See https://aka.ms/efdocs-cosmos-partition-keys for more information.
///
public static string WithPartitionKeyBadNode
=> GetString("WithPartitionKeyBadNode");
+ ///
+ /// 'WithPartitionKey' only accepts simple constant or parameter arguments. See https://aka.ms/efdocs-cosmos-partition-keys for more information.
+ ///
+ public static string WithPartitionKeyNotConstantOrParameter
+ => GetString("WithPartitionKeyNotConstantOrParameter");
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name)!;
diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.resx b/src/EFCore.Cosmos/Properties/CosmosStrings.resx
index 34887367904..e4ad7e2a6ab 100644
--- a/src/EFCore.Cosmos/Properties/CosmosStrings.resx
+++ b/src/EFCore.Cosmos/Properties/CosmosStrings.resx
@@ -153,6 +153,9 @@
The type of the '{idProperty}' property on '{entityType}' is '{propertyType}'. All 'id' properties must be strings or have a string value converter.
+
+ {actual} partition key values were provided, but the entity type '{entityType}' has {expected} partition key values defined.
+
The entity type '{entityType}' has an index defined over properties '{properties}'. The Azure Cosmos DB provider for EF Core currently does not support index definitions.
@@ -213,8 +216,8 @@
'Reverse' could not be translated to the server because there is no ordering on the server side.
-
- Cosmos container '{container1}' is referenced by the query, but '{container2}' is already being referenced. A query can only reference a single Cosmos container.
+
+ Root entity type '{entityType1}' is referenced by the query, but '{entityType2}' is already being referenced. A query can only reference a single root entity type.
Navigation '{entityType}.{navigationName}' doesn't point to an embedded entity.
@@ -282,9 +285,6 @@
The partition key value supplied for '{propertyType}' property '{entityType}.{property}' is of type '{valueType}'. Partition key values must be of a type assignable to the property.
-
- The partition key specified in the 'WithPartitionKey' call '{partitionKey1}' and the partition key specified in the 'Where' predicate '{partitionKey2}' must be identical to return any results. Remove one of them.
-
Unable to execute a 'ReadItem' query since the partition key value is missing. Consider using the 'WithPartitionKey' method on the query to specify partition key to use.
@@ -327,7 +327,13 @@
'VisitChildren' must be overridden in the class deriving from 'SqlExpression'.
+
+ 'WithPartitionKey' can only be called once in a query. See https://aka.ms/efdocs-cosmos-partition-keys for more information.
+
- 'WithPartitionKeyMethodInfo' can only be called on a entity query root. See https://aka.ms/efdocs-cosmos-partition-keys for more information.
+ 'WithPartitionKey' can only be called on a entity query root. See https://aka.ms/efdocs-cosmos-partition-keys for more information.
+
+
+ 'WithPartitionKey' only accepts simple constant or parameter arguments. See https://aka.ms/efdocs-cosmos-partition-keys for more information.
\ No newline at end of file
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryCompilationContext.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryCompilationContext.cs
index bee3bdb6b5b..2673f87de42 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryCompilationContext.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryCompilationContext.cs
@@ -13,7 +13,7 @@ public class CosmosQueryCompilationContext(QueryCompilationContextDependencies d
: QueryCompilationContext(dependencies, async)
{
///
- /// The name of the Cosmos container against which this query will be executed.
+ /// The root entity type being queried.
///
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -21,7 +21,7 @@ public class CosmosQueryCompilationContext(QueryCompilationContextDependencies d
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual string? CosmosContainer { get; internal set; }
+ public virtual IEntityType? RootEntityType { get; internal set; }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -29,7 +29,7 @@ public class CosmosQueryCompilationContext(QueryCompilationContextDependencies d
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual PartitionKey? PartitionKeyValueFromExtension { get; internal set; }
+ public virtual List PartitionKeyPropertyValues { get; internal set; } = new();
///
/// A manager for aliases, capable of generate uniquified source aliases.
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryMetadataExtractingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryMetadataExtractingExpressionVisitor.cs
deleted file mode 100644
index 4e0dd73b3a9..00000000000
--- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryMetadataExtractingExpressionVisitor.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.EntityFrameworkCore.Cosmos.Internal;
-using Microsoft.EntityFrameworkCore.Internal;
-
-namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
-
-///
-/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
-/// the same compatibility standards as public APIs. It may be changed or removed without notice in
-/// any release. You should only use it directly in your code with extreme caution and knowing that
-/// doing so can result in application failures when updating to a new Entity Framework Core release.
-///
-public class CosmosQueryMetadataExtractingExpressionVisitor(CosmosQueryCompilationContext cosmosQueryCompilationContext)
- : ExpressionVisitor
-{
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
- {
- if (methodCallExpression.Method.IsGenericMethod
- && methodCallExpression.Method.GetGenericMethodDefinition() == CosmosQueryableExtensions.WithPartitionKeyMethodInfo)
- {
- var innerQueryable = Visit(methodCallExpression.Arguments[0]);
-
- var firstValue = methodCallExpression.Arguments[1].GetConstantValue
protected override ShapedQueryExpression? TranslateWhere(ShapedQueryExpression source, LambdaExpression predicate)
- {
- var select = (SelectExpression)source.QueryExpression;
-
- if (source.ShaperExpression is StructuralTypeShaperExpression { StructuralType: IEntityType entityType }
- && entityType.GetPartitionKeyPropertyNames().FirstOrDefault() != null)
- {
- List<(Expression Expression, IProperty Property)?> partitionKeyValues = new();
- if (TryExtractPartitionKey(predicate.Body, entityType, out var newPredicate, partitionKeyValues))
- {
- foreach (var propertyName in entityType.GetPartitionKeyPropertyNames())
- {
- var partitionKeyValue = partitionKeyValues.FirstOrDefault(p => p!.Value.Property.Name == propertyName);
- if (partitionKeyValue == null)
- {
- newPredicate = null;
- break;
- }
-
- ((SelectExpression)source.QueryExpression).AddPartitionKey(
- partitionKeyValue.Value.Property, partitionKeyValue.Value.Expression);
- }
-
- if (newPredicate == null)
- {
- return source;
- }
-
- predicate = Expression.Lambda(newPredicate, predicate.Parameters);
- }
- }
-
- return TryApplyPredicate(source, predicate) ? source : null;
-
- bool TryExtractPartitionKey(
- Expression expression,
- IEntityType entityType,
- out Expression? updatedPredicate,
- List<(Expression, IProperty)?> partitionKeyValues)
- {
- updatedPredicate = null;
- if (expression is BinaryExpression binaryExpression)
- {
- if (TryGetPartitionKeyValue(binaryExpression, entityType, out var valueExpression, out var property))
- {
- partitionKeyValues.Add((valueExpression!, property!));
- return true;
- }
-
- if (binaryExpression.NodeType == ExpressionType.AndAlso)
- {
- var foundInRight = TryExtractPartitionKey(binaryExpression.Left, entityType, out var leftPredicate, partitionKeyValues);
-
- var foundInLeft = TryExtractPartitionKey(
- binaryExpression.Right,
- entityType,
- out var rightPredicate,
- partitionKeyValues);
-
- if (foundInLeft && foundInRight)
- {
- return true;
- }
-
- if (foundInLeft || foundInRight)
- {
- updatedPredicate = leftPredicate != null
- ? rightPredicate != null
- ? binaryExpression.Update(leftPredicate, binaryExpression.Conversion, rightPredicate)
- : leftPredicate
- : rightPredicate;
-
- return true;
- }
- }
- }
- else if (expression.NodeType == ExpressionType.MemberAccess
- && expression.Type == typeof(bool))
- {
- if (IsPartitionKeyPropertyAccess(expression, entityType, out var property))
- {
- partitionKeyValues.Add((Expression.Constant(true), property!));
- return true;
- }
- }
- else if (expression.NodeType == ExpressionType.Not)
- {
- if (IsPartitionKeyPropertyAccess(((UnaryExpression)expression).Operand, entityType, out var property))
- {
- partitionKeyValues.Add((Expression.Constant(false), property!));
- return true;
- }
- }
-
- updatedPredicate = expression;
- return false;
- }
-
- bool TryGetPartitionKeyValue(
- BinaryExpression binaryExpression,
- IEntityType entityType,
- out Expression? expression,
- out IProperty? property)
- {
- if (binaryExpression.NodeType == ExpressionType.Equal)
- {
- expression = IsPartitionKeyPropertyAccess(binaryExpression.Left, entityType, out property)
- ? binaryExpression.Right
- : IsPartitionKeyPropertyAccess(binaryExpression.Right, entityType, out property)
- ? binaryExpression.Left
- : null;
-
- if (expression is ConstantExpression
- || (expression is ParameterExpression valueParameterExpression
- && valueParameterExpression.Name?
- .StartsWith(QueryCompilationContext.QueryParameterPrefix, StringComparison.Ordinal)
- == true))
- {
- return true;
- }
- }
-
- expression = null;
- property = null;
- return false;
- }
-
- bool IsPartitionKeyPropertyAccess(Expression expression, IEntityType entityType, out IProperty? property)
- {
- property = expression switch
- {
- MemberExpression memberExpression
- => entityType.FindProperty(memberExpression.Member.GetSimpleMemberName()),
- MethodCallExpression methodCallExpression when methodCallExpression.TryGetEFPropertyArguments(out _, out var propertyName)
- => entityType.FindProperty(propertyName),
- MethodCallExpression methodCallExpression
- when methodCallExpression.TryGetIndexerArguments(_queryCompilationContext.Model, out _, out var propertyName)
- => entityType.FindProperty(propertyName),
- _ => null
- };
-
- return property != null && entityType.GetPartitionKeyPropertyNames().Contains(property.Name);
- }
- }
+ => TryApplyPredicate(source, predicate) ? source : null;
#region Queryable collection support
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs
new file mode 100644
index 00000000000..130fbe68d6d
--- /dev/null
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs
@@ -0,0 +1,281 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.EntityFrameworkCore.Cosmos.Internal;
+using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
+
+///
+/// Identifies Cosmos queries that can be transformed to optimized ReadItem form and performs the transformation.
+///
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+public class CosmosReadItemAndPartitionKeysExtractor : ExpressionVisitor
+{
+ private ISqlExpressionFactory _sqlExpressionFactory = null!;
+ private IEntityType _entityType = null!;
+ private string _rootAlias = null!;
+ private bool _isPredicateCompatibleWithReadItem;
+ private string? _discriminatorJsonPropertyName;
+ private Dictionary _jsonIdPropertyValues = null!;
+ private Dictionary _partitionKeyPropertyValues = null!;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual Expression ExtractPartitionKeysAndId(
+ CosmosQueryCompilationContext queryCompilationContext,
+ ISqlExpressionFactory sqlExpressionFactory,
+ Expression expression)
+ {
+ _entityType = queryCompilationContext.RootEntityType
+ ?? throw new UnreachableException("No root entity type was set during query processing.");
+ _sqlExpressionFactory = sqlExpressionFactory;
+
+ if (expression is not ShapedQueryExpression
+ {
+ QueryExpression: SelectExpression
+ {
+ Sources: [{ Expression: ObjectReferenceExpression } rootSource, ..],
+ Predicate: SqlExpression predicate
+ } select
+ } shapedQuery)
+ {
+ return expression;
+ }
+
+ _rootAlias = rootSource.Alias;
+
+ // We're going to be looking for equality comparisons on the JSON id definition properties and the partition key properties of the
+ // entity type; build a dictionary where the properties are the keys, and where the values are expressions that will get populated
+ // from the tree (either constants or parameters).
+ // We also want to ignore the discriminator property if it's compared to our entity type's discriminator value (see below).
+ _isPredicateCompatibleWithReadItem = true;
+ var jsonIdProperties = _entityType.GetJsonIdDefinition()?.Properties ?? [];
+ if (jsonIdProperties.Count == 0)
+ {
+ // No JSON ID definition - no ReadItem
+ _isPredicateCompatibleWithReadItem = false;
+ }
+
+ _jsonIdPropertyValues = jsonIdProperties.ToDictionary(p => p, _ => (Expression?)null);
+
+ var partitionKeyProperties = _entityType.GetPartitionKeyProperties();
+ _partitionKeyPropertyValues = partitionKeyProperties.ToDictionary(p => p, _ => (Expression?)null);
+
+ var discriminatorProperty = _entityType.FindDiscriminatorProperty();
+ _discriminatorJsonPropertyName = discriminatorProperty?.GetJsonPropertyName();
+
+ // Visit the predicate.
+ // This will populate _jsonIdPropertyValues and _partitionKeyPropertyValues with comparisons found in the predicate, and return
+ // a rewritten predicate where the partition key comparisons have been removed.
+ var predicateWithoutPartitionKeyComparisons = (SqlExpression)Visit(predicate);
+
+ // If the discriminator is part of the JSON id definition, a comparison may be missing from the predicate, since we don't add one
+ // if it's not needed (e.g. only one entity type mapped to the container). For that case, add the entity type's discriminator value.
+ if (discriminatorProperty is not null
+ && _jsonIdPropertyValues.TryGetValue(discriminatorProperty, out var discriminatorValue)
+ && discriminatorValue is null)
+ {
+ _jsonIdPropertyValues[discriminatorProperty] = _sqlExpressionFactory.Constant(
+ _entityType.GetDiscriminatorValue(), discriminatorProperty.ClrType);
+ }
+
+ var allIdPropertiesSpecified =
+ _jsonIdPropertyValues.Values.All(p => p is not null) && _jsonIdPropertyValues.Count > 0;
+ var allPartitionKeyPropertiesSpecified = _partitionKeyPropertyValues.Values.All(p => p is not null);
+
+ // First, take care of the partition key properties; if the visitation above returned a different predicate, that means that some
+ // partition key comparisons were extracted (and therefore found). Lift these up to the query compilation context and rewrite
+ // the SelectExpression with the new, reduced predicate.
+ // Note that if the user called WithPartitionKey(), we'll have already populated the partition key property values from there, and
+ // we skip lifting the predicate comparisons.
+ if (allPartitionKeyPropertiesSpecified
+ && queryCompilationContext.PartitionKeyPropertyValues.Count == 0)
+ {
+ foreach (var partitionKeyProperty in partitionKeyProperties)
+ {
+ queryCompilationContext.PartitionKeyPropertyValues.Add(_partitionKeyPropertyValues[partitionKeyProperty]!);
+ }
+
+ select = select.Update(
+ select.Sources.ToList(),
+ predicateWithoutPartitionKeyComparisons is SqlConstantExpression { Value: true }
+ ? null
+ : predicateWithoutPartitionKeyComparisons,
+ select.Projection.ToList(),
+ select.Orderings.ToList(),
+ select.Offset,
+ select.Limit);
+
+ shapedQuery = shapedQuery.UpdateQueryExpression(select);
+ }
+
+ // Now, attempt to also transform the query to ReadItem form if possible.
+ if (_isPredicateCompatibleWithReadItem
+ && allIdPropertiesSpecified
+ // Note that queryCompilationContext.PartitionKeyPropertyValues may have been populated with WithPartitionKey(), which has
+ // a params object[] argument that gets parameterized as a single array. So the number of property values may not match the
+ // number of partition key properties.
+ && (partitionKeyProperties.Count == 0 || queryCompilationContext.PartitionKeyPropertyValues.Count > 0)
+ // If the entity type being queried has derived types and the discriminator is part of the JSON id, we can't reliably use
+ // ReadItem, since we don't know in advance which derived type the document represents.
+ && (!jsonIdProperties.Contains(discriminatorProperty) || !_entityType.GetDerivedTypes().Any())
+ && select is
+ {
+ Offset: null or SqlConstantExpression { Value: 0 },
+ Limit: null or SqlConstantExpression { Value: > 0 }
+ }
+ // We only transform to ReadItem if the entire document (i.e. root entity type) is being projected out.
+ // Using ReadItem even when a projection is present is tracked by #34163.
+ && Unwrap(shapedQuery.ShaperExpression) is StructuralTypeShaperExpression { StructuralType: var projectedStructuralType }
+ && projectedStructuralType == _entityType)
+ {
+ return shapedQuery.UpdateQueryExpression(select.WithReadItemInfo(new ReadItemInfo(_jsonIdPropertyValues!)));
+ }
+
+ return shapedQuery;
+
+ Expression Unwrap(Expression shaper)
+ {
+ if (shaper is UnaryExpression { NodeType: ExpressionType.Convert } convert
+ && convert.Type == typeof(object))
+ {
+ shaper = convert.Operand;
+ }
+
+ while (shaper is IncludeExpression { EntityExpression: var nested })
+ {
+ shaper = nested;
+ }
+
+ return shaper;
+ }
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override Expression VisitExtension(Expression node)
+ {
+ switch (node)
+ {
+ case SqlBinaryExpression { OperatorType: ExpressionType.Equal, Left: var left, Right: var right } binary:
+ {
+ // TODO: Handle property accesses into complex types/owned entity types, #25548
+ var (scalarAccess, propertyValue) =
+ left is ScalarAccessExpression leftScalarAccess
+ && right is SqlParameterExpression or SqlConstantExpression
+ ? (leftScalarAccess, right)
+ : right is ScalarAccessExpression rightScalarAccess
+ && left is SqlParameterExpression or SqlConstantExpression
+ ? (rightScalarAccess, left)
+ : (null, null);
+
+ if (scalarAccess?.Object is ObjectReferenceExpression { Name: var referencedSourceAlias }
+ && referencedSourceAlias == _rootAlias)
+ {
+ return ProcessPropertyComparison(scalarAccess.PropertyName, propertyValue!, binary);
+ }
+
+ _isPredicateCompatibleWithReadItem = false;
+ return binary;
+ }
+
+ // Bool property access (e.g. Where(b => b.BoolPartitionKey))
+ case ScalarAccessExpression { PropertyName: var propertyName } scalarAccess:
+ return ProcessPropertyComparison(propertyName, _sqlExpressionFactory.Constant(true), scalarAccess);
+
+ // Negated bool property access (e.g. Where(b => !b.BoolPartitionKey))
+ case SqlUnaryExpression
+ {
+ OperatorType: ExpressionType.Not,
+ Operand: ScalarAccessExpression { PropertyName: var propertyName }
+ } unary:
+ return ProcessPropertyComparison(propertyName, _sqlExpressionFactory.Constant(false), unary);
+
+ case SqlBinaryExpression { OperatorType: ExpressionType.AndAlso } binary:
+ return _sqlExpressionFactory.MakeBinary(
+ ExpressionType.AndAlso,
+ (SqlExpression)Visit(binary.Left),
+ (SqlExpression)Visit(binary.Right),
+ binary.TypeMapping,
+ binary)!;
+
+ default:
+ // Anything else in the predicate, e.g. an OR, immediately disqualifies it from being a ReadItem query, and means we
+ // can't extract partition key properties.
+ _isPredicateCompatibleWithReadItem = false;
+ return node;
+ }
+
+ SqlExpression ProcessPropertyComparison(string propertyName, SqlExpression propertyValue, SqlExpression originalExpression)
+ {
+ // We assume that the comparison is incompatible with ReadItem until proven otherwise, i.e. the comparison is for a JSON ID
+ // property, a partition key property, or certain cases involving the discriminator property.
+ var isCompatibleComparisonForReadItem = false;
+
+ foreach (var property in _jsonIdPropertyValues.Keys)
+ {
+ if (propertyName == property.GetJsonPropertyName())
+ {
+ if (_jsonIdPropertyValues.TryGetValue(property, out var previousValue)
+ && (previousValue is null || previousValue.Equals(propertyValue)))
+ {
+ _jsonIdPropertyValues[property] = propertyValue;
+ isCompatibleComparisonForReadItem = true;
+ }
+ break;
+ }
+ }
+
+ foreach (var property in _partitionKeyPropertyValues.Keys)
+ {
+ // We found a comparison for a partition key property.
+ // Extract its value expression and elide the comparison from the predicate - it'll be lifted out to the Cosmos SDK
+ // call. Note that this is always considered a compatible comparison for ReadItem.
+ if (propertyName == property.GetJsonPropertyName()
+ && _partitionKeyPropertyValues.TryGetValue(property, out var previousValue)
+ && (previousValue is null || previousValue.Equals(propertyValue)))
+ {
+ _partitionKeyPropertyValues[property] = propertyValue;
+ return _sqlExpressionFactory.Constant(true);
+ }
+ }
+
+ // The query contains a comparison on the discriminator property.
+ // If the discriminator is part of the JSON ID property, it'll be handled below like any other JSON ID property.
+ // However, if it isn't, we may need to ignore the comparison, and allow transforming to ReadItem. For example, when
+ // multiple entity types are mapped to the same container, EF adds a discriminator comparison; but we want to use ReadItem
+ // for these (common) cases - so we ignore the comparison for the purpose of ReadItem transformation, and validate the
+ // discriminator coming back from Cosmos in the shaper, to ensure throwing for an incorrect type.
+ if (isCompatibleComparisonForReadItem
+ && propertyName == _discriminatorJsonPropertyName
+ && propertyValue is SqlConstantExpression { Value: object specifiedDiscriminatorValue }
+ && _entityType.FindDiscriminatorProperty() is IProperty discriminatorProperty
+ && _entityType.GetDiscriminatorValue() is object entityDiscriminatorValue
+ && discriminatorProperty.GetProviderValueComparer().Equals(specifiedDiscriminatorValue, entityDiscriminatorValue))
+ {
+ isCompatibleComparisonForReadItem = true;
+ }
+
+ if (!isCompatibleComparisonForReadItem)
+ {
+ _isPredicateCompatibleWithReadItem = false;
+ }
+
+ return originalExpression;
+ }
+ }
+}
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.PagingQueryingEnumerable.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.PagingQueryingEnumerable.cs
index 1e5c16966f5..5add7ca24cd 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.PagingQueryingEnumerable.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.PagingQueryingEnumerable.cs
@@ -28,7 +28,7 @@ private sealed class PagingQueryingEnumerable : IAsyncEnumerable _queryLogger;
private readonly IDiagnosticsLogger _commandLogger;
private readonly bool _standAloneStateManager;
@@ -44,8 +44,8 @@ public PagingQueryingEnumerable(
SelectExpression selectExpression,
Func shaper,
Type contextType,
- string cosmosContainer,
- PartitionKey partitionKeyValueFromExtension,
+ IEntityType rootEntityType,
+ List partitionKeyPropertyValues,
bool standAloneStateManager,
bool threadSafetyChecksEnabled,
string maxItemCountParameterName,
@@ -66,16 +66,10 @@ public PagingQueryingEnumerable(
_continuationTokenParameterName = continuationTokenParameterName;
_responseContinuationTokenLimitInKbParameterName = responseContinuationTokenLimitInKbParameterName;
- var partitionKey = selectExpression.GetPartitionKeyValue(cosmosQueryContext.ParameterValues);
- if (partitionKey != PartitionKey.None
- && partitionKeyValueFromExtension != PartitionKey.None
- && !partitionKeyValueFromExtension.Equals(partitionKey))
- {
- throw new InvalidOperationException(CosmosStrings.PartitionKeyMismatch(partitionKeyValueFromExtension, partitionKey));
- }
-
- _cosmosPartitionKeyValue = partitionKey != PartitionKey.None ? partitionKey : partitionKeyValueFromExtension;
- _cosmosContainer = cosmosContainer;
+ _cosmosContainer = rootEntityType.GetContainer()
+ ?? throw new UnreachableException("Root entity type without a Cosmos container.");
+ _cosmosPartitionKey = GeneratePartitionKey(
+ rootEntityType, partitionKeyPropertyValues, _cosmosQueryContext.ParameterValues);
}
public IAsyncEnumerator> GetAsyncEnumerator(CancellationToken cancellationToken = default)
@@ -96,7 +90,7 @@ private sealed class AsyncEnumerator : IAsyncEnumerator>
private readonly Func _shaper;
private readonly Type _contextType;
private readonly string _cosmosContainer;
- private readonly PartitionKey _cosmosPartitionKeyValue;
+ private readonly PartitionKey _cosmosPartitionKey;
private readonly IDiagnosticsLogger _queryLogger;
private readonly IDiagnosticsLogger _commandLogger;
private readonly bool _standAloneStateManager;
@@ -114,7 +108,7 @@ public AsyncEnumerator(PagingQueryingEnumerable queryingEnumerable, Cancellat
_shaper = queryingEnumerable._shaper;
_contextType = queryingEnumerable._contextType;
_cosmosContainer = queryingEnumerable._cosmosContainer;
- _cosmosPartitionKeyValue = queryingEnumerable._cosmosPartitionKeyValue;
+ _cosmosPartitionKey = queryingEnumerable._cosmosPartitionKey;
_queryLogger = queryingEnumerable._queryLogger;
_commandLogger = queryingEnumerable._commandLogger;
_standAloneStateManager = queryingEnumerable._standAloneStateManager;
@@ -158,13 +152,13 @@ public async ValueTask MoveNextAsync()
ResponseContinuationTokenLimitInKb = responseContinuationTokenLimitInKb
};
- if (_cosmosPartitionKeyValue != PartitionKey.None)
+ if (_cosmosPartitionKey != PartitionKey.None)
{
- queryRequestOptions.PartitionKey = _cosmosPartitionKeyValue;
+ queryRequestOptions.PartitionKey = _cosmosPartitionKey;
}
var cosmosClient = _cosmosQueryContext.CosmosClient;
- _commandLogger.ExecutingSqlQuery(_cosmosContainer, _cosmosPartitionKeyValue, sqlQuery);
+ _commandLogger.ExecutingSqlQuery(_cosmosContainer, _cosmosPartitionKey, sqlQuery);
_cosmosQueryContext.InitializeStateManager(_standAloneStateManager);
var results = new List(maxItemCount);
@@ -182,7 +176,7 @@ public async ValueTask MoveNextAsync()
responseMessage.Headers.RequestCharge,
responseMessage.Headers.ActivityId,
_cosmosContainer,
- _cosmosPartitionKeyValue,
+ _cosmosPartitionKey,
sqlQuery);
responseMessage.EnsureSuccessStatusCode();
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs
index 705ef00ea79..24e7d4fba29 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs
@@ -5,7 +5,6 @@
using System.Collections;
using System.Text;
-using Microsoft.EntityFrameworkCore.Cosmos.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
using Newtonsoft.Json.Linq;
@@ -28,7 +27,7 @@ private sealed class QueryingEnumerable : IEnumerable, IAsyncEnumerable
private readonly IQuerySqlGeneratorFactory _querySqlGeneratorFactory;
private readonly Type _contextType;
private readonly string _cosmosContainer;
- private readonly PartitionKey _cosmosPartitionKeyValue;
+ private readonly PartitionKey _cosmosPartitionKey;
private readonly IDiagnosticsLogger _queryLogger;
private readonly bool _standAloneStateManager;
private readonly bool _threadSafetyChecksEnabled;
@@ -40,8 +39,8 @@ public QueryingEnumerable(
SelectExpression selectExpression,
Func shaper,
Type contextType,
- string cosmosContainer,
- PartitionKey partitionKeyValueFromExtension,
+ IEntityType rootEntityType,
+ List partitionKeyPropertyValues,
bool standAloneStateManager,
bool threadSafetyChecksEnabled)
{
@@ -55,16 +54,10 @@ public QueryingEnumerable(
_standAloneStateManager = standAloneStateManager;
_threadSafetyChecksEnabled = threadSafetyChecksEnabled;
- var partitionKey = selectExpression.GetPartitionKeyValue(cosmosQueryContext.ParameterValues);
- if (partitionKey != PartitionKey.None
- && partitionKeyValueFromExtension != PartitionKey.None
- && !partitionKeyValueFromExtension.Equals(partitionKey))
- {
- throw new InvalidOperationException(CosmosStrings.PartitionKeyMismatch(partitionKeyValueFromExtension, partitionKey));
- }
-
- _cosmosPartitionKeyValue = partitionKey != PartitionKey.None ? partitionKey : partitionKeyValueFromExtension;
- _cosmosContainer = cosmosContainer;
+ _cosmosContainer = rootEntityType.GetContainer()
+ ?? throw new UnreachableException("Root entity type without a Cosmos container.");
+ _cosmosPartitionKey = GeneratePartitionKey(
+ rootEntityType, partitionKeyPropertyValues, _cosmosQueryContext.ParameterValues);
}
public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
@@ -113,7 +106,7 @@ private sealed class Enumerator : IEnumerator
private readonly Func _shaper;
private readonly Type _contextType;
private readonly string _cosmosContainer;
- private readonly PartitionKey _cosmosPartitionKeyValue;
+ private readonly PartitionKey _cosmosPartitionKey;
private readonly IDiagnosticsLogger _queryLogger;
private readonly bool _standAloneStateManager;
private readonly IConcurrencyDetector _concurrencyDetector;
@@ -128,7 +121,7 @@ public Enumerator(QueryingEnumerable queryingEnumerable)
_shaper = queryingEnumerable._shaper;
_contextType = queryingEnumerable._contextType;
_cosmosContainer = queryingEnumerable._cosmosContainer;
- _cosmosPartitionKeyValue = queryingEnumerable._cosmosPartitionKeyValue;
+ _cosmosPartitionKey = queryingEnumerable._cosmosPartitionKey;
_queryLogger = queryingEnumerable._queryLogger;
_standAloneStateManager = queryingEnumerable._standAloneStateManager;
_exceptionDetector = _cosmosQueryContext.ExceptionDetector;
@@ -156,7 +149,7 @@ public bool MoveNext()
EntityFrameworkMetricsData.ReportQueryExecuting();
_enumerator = _cosmosQueryContext.CosmosClient
- .ExecuteSqlQuery(_cosmosContainer, _cosmosPartitionKeyValue, sqlQuery)
+ .ExecuteSqlQuery(_cosmosContainer, _cosmosPartitionKey, sqlQuery)
.GetEnumerator();
_cosmosQueryContext.InitializeStateManager(_standAloneStateManager);
}
@@ -202,7 +195,7 @@ private sealed class AsyncEnumerator : IAsyncEnumerator
private readonly Func _shaper;
private readonly Type _contextType;
private readonly string _cosmosContainer;
- private readonly PartitionKey _cosmosPartitionKeyValue;
+ private readonly PartitionKey _cosmosPartitionKey;
private readonly IDiagnosticsLogger _queryLogger;
private readonly bool _standAloneStateManager;
private readonly CancellationToken _cancellationToken;
@@ -218,7 +211,7 @@ public AsyncEnumerator(QueryingEnumerable queryingEnumerable, CancellationTok
_shaper = queryingEnumerable._shaper;
_contextType = queryingEnumerable._contextType;
_cosmosContainer = queryingEnumerable._cosmosContainer;
- _cosmosPartitionKeyValue = queryingEnumerable._cosmosPartitionKeyValue;
+ _cosmosPartitionKey = queryingEnumerable._cosmosPartitionKey;
_queryLogger = queryingEnumerable._queryLogger;
_standAloneStateManager = queryingEnumerable._standAloneStateManager;
_exceptionDetector = _cosmosQueryContext.ExceptionDetector;
@@ -244,7 +237,7 @@ public async ValueTask MoveNextAsync()
EntityFrameworkMetricsData.ReportQueryExecuting();
_enumerator = _cosmosQueryContext.CosmosClient
- .ExecuteSqlQueryAsync(_cosmosContainer, _cosmosPartitionKeyValue, sqlQuery)
+ .ExecuteSqlQueryAsync(_cosmosContainer, _cosmosPartitionKey, sqlQuery)
.GetAsyncEnumerator(_cancellationToken);
_cosmosQueryContext.InitializeStateManager(_standAloneStateManager);
}
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ReadItemQueryingEnumerable.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ReadItemQueryingEnumerable.cs
index de9dff32bdf..2aa09cd6614 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ReadItemQueryingEnumerable.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ReadItemQueryingEnumerable.cs
@@ -6,7 +6,6 @@
using System.Collections;
using Microsoft.EntityFrameworkCore.Cosmos.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
-using Microsoft.EntityFrameworkCore.Internal;
using Newtonsoft.Json.Linq;
namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
@@ -22,8 +21,10 @@ public partial class CosmosShapedQueryCompilingExpressionVisitor
private sealed class ReadItemQueryingEnumerable : IEnumerable, IAsyncEnumerable, IQueryingEnumerable
{
private readonly CosmosQueryContext _cosmosQueryContext;
+ private readonly IEntityType _rootEntityType;
private readonly string _cosmosContainer;
private readonly ReadItemInfo _readItemInfo;
+ private readonly PartitionKey _cosmosPartitionKey;
private readonly Func _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger _queryLogger;
@@ -32,7 +33,8 @@ private sealed class ReadItemQueryingEnumerable : IEnumerable, IAsyncEnume
public ReadItemQueryingEnumerable(
CosmosQueryContext cosmosQueryContext,
- string cosmosContainer,
+ IEntityType rootEntityType,
+ List partitionKeyPropertyValues,
ReadItemInfo readItemInfo,
Func shaper,
Type contextType,
@@ -40,13 +42,18 @@ public ReadItemQueryingEnumerable(
bool threadSafetyChecksEnabled)
{
_cosmosQueryContext = cosmosQueryContext;
- _cosmosContainer = cosmosContainer;
+ _rootEntityType = rootEntityType;
_readItemInfo = readItemInfo;
_shaper = shaper;
_contextType = contextType;
_queryLogger = _cosmosQueryContext.QueryLogger;
_standAloneStateManager = standAloneStateManager;
_threadSafetyChecksEnabled = threadSafetyChecksEnabled;
+
+ _cosmosContainer = rootEntityType.GetContainer()
+ ?? throw new UnreachableException("Root entity type without a Cosmos container.");
+ _cosmosPartitionKey = GeneratePartitionKey(
+ rootEntityType, partitionKeyPropertyValues, _cosmosQueryContext.ParameterValues);
}
public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
@@ -61,60 +68,24 @@ IEnumerator IEnumerable.GetEnumerator()
public string ToQueryString()
{
TryGetResourceId(out var resourceId);
- TryGetPartitionKey(out var partitionKey);
- return CosmosStrings.NoReadItemQueryString(resourceId, partitionKey);
- }
-
- private bool TryGetPartitionKey(out PartitionKey partitionKeyValue)
- {
- var properties = _readItemInfo.EntityType.GetPartitionKeyProperties();
- if (!properties.Any())
- {
- partitionKeyValue = PartitionKey.None;
- return true;
- }
-
- var builder = new PartitionKeyBuilder();
- foreach (var property in properties)
- {
- if (TryGetParameterValue(property, out var value))
- {
- if (value == null)
- {
- partitionKeyValue = PartitionKey.Null;
- return false;
- }
- builder.Add(value, property);
- }
- }
-
- partitionKeyValue = builder.Build();
-
- return true;
+ return CosmosStrings.NoReadItemQueryString(resourceId, _cosmosPartitionKey);
}
private bool TryGetResourceId(out string resourceId)
{
- var entityType = _readItemInfo.EntityType;
- var jsonIdDefinition = entityType.GetJsonIdDefinition();
+ var jsonIdDefinition = _rootEntityType.GetJsonIdDefinition();
Check.DebugAssert(jsonIdDefinition != null,
"Should not be using this enumerable if not using ReadItem, which needs an id definition.");
var values = new List(jsonIdDefinition.Properties.Count);
foreach (var property in jsonIdDefinition.Properties)
{
- if (!TryGetParameterValue(property, out var value))
+ var value = _readItemInfo.PropertyValues[property] switch
{
- var discriminatorProperty = entityType.FindDiscriminatorProperty();
- if (discriminatorProperty == property)
- {
- value = entityType.GetDiscriminatorValue();
- }
- else
- {
- Check.DebugFail("Parameters should cover all properties or we should not be using ReadItem.");
- }
- }
+ SqlParameterExpression { Name: var parameterName } => _cosmosQueryContext.ParameterValues[parameterName],
+ SqlConstantExpression { Value: var constantValue } => constantValue,
+ _ => throw new UnreachableException()
+ };
values.Add(value);
}
@@ -128,17 +99,11 @@ private bool TryGetResourceId(out string resourceId)
return true;
}
- private bool TryGetParameterValue(IProperty property, out object value)
- {
- value = null;
- return _readItemInfo.PropertyParameters.TryGetValue(property, out var parameterName)
- && _cosmosQueryContext.ParameterValues.TryGetValue(parameterName, out value);
- }
-
private sealed class Enumerator : IEnumerator, IAsyncEnumerator
{
private readonly CosmosQueryContext _cosmosQueryContext;
private readonly string _cosmosContainer;
+ private readonly PartitionKey _cosmosPartitionKey;
private readonly Func _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger _queryLogger;
@@ -155,6 +120,7 @@ public Enumerator(ReadItemQueryingEnumerable readItemEnumerable, Cancellation
{
_cosmosQueryContext = readItemEnumerable._cosmosQueryContext;
_cosmosContainer = readItemEnumerable._cosmosContainer;
+ _cosmosPartitionKey = readItemEnumerable._cosmosPartitionKey;
_shaper = readItemEnumerable._shaper;
_contextType = readItemEnumerable._contextType;
_queryLogger = readItemEnumerable._queryLogger;
@@ -189,16 +155,11 @@ public bool MoveNext()
throw new InvalidOperationException(CosmosStrings.ResourceIdMissing);
}
- if (!_readItemEnumerable.TryGetPartitionKey(out var partitionKeyValue))
- {
- throw new InvalidOperationException(CosmosStrings.PartitionKeyMissing);
- }
-
EntityFrameworkMetricsData.ReportQueryExecuting();
_item = _cosmosQueryContext.CosmosClient.ExecuteReadItem(
_cosmosContainer,
- partitionKeyValue,
+ _cosmosPartitionKey,
resourceId);
return ShapeResult();
@@ -234,16 +195,11 @@ public async ValueTask MoveNextAsync()
throw new InvalidOperationException(CosmosStrings.ResourceIdMissing);
}
- if (!_readItemEnumerable.TryGetPartitionKey(out var partitionKeyValue))
- {
- throw new InvalidOperationException(CosmosStrings.PartitionKeyMissing);
- }
-
EntityFrameworkMetricsData.ReportQueryExecuting();
_item = await _cosmosQueryContext.CosmosClient.ExecuteReadItemAsync(
_cosmosContainer,
- partitionKeyValue,
+ _cosmosPartitionKey,
resourceId,
_cancellationToken)
.ConfigureAwait(false);
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
index 54dc03462c9..9270ae3687b 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
@@ -3,7 +3,9 @@
#nullable disable
+using Microsoft.EntityFrameworkCore.Cosmos.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Query.Internal.Expressions;
+using Microsoft.EntityFrameworkCore.Internal;
using Newtonsoft.Json.Linq;
using static System.Linq.Expressions.Expression;
@@ -25,9 +27,6 @@ public partial class CosmosShapedQueryCompilingExpressionVisitor(
private readonly Type _contextType = cosmosQueryCompilationContext.ContextType;
private readonly bool _threadSafetyChecksEnabled = dependencies.CoreSingletonOptions.AreThreadSafetyChecksEnabled;
- private readonly PartitionKey _partitionKeyValueFromExtension = cosmosQueryCompilationContext.PartitionKeyValueFromExtension
- ?? PartitionKey.None;
-
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -36,9 +35,9 @@ public partial class CosmosShapedQueryCompilingExpressionVisitor(
///
protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQueryExpression)
{
- if (cosmosQueryCompilationContext.CosmosContainer is null)
+ if (cosmosQueryCompilationContext.RootEntityType is not IEntityType rootEntityType)
{
- throw new UnreachableException("No Cosmos container was set during query processing.");
+ throw new UnreachableException("No root entity type was set during query processing.");
}
var jObjectParameter = Parameter(typeof(JObject), "jObject");
@@ -82,7 +81,7 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery
var cosmosQueryContextConstant = Convert(QueryCompilationContext.QueryContextParameter, typeof(CosmosQueryContext));
var shaperConstant = Constant(shaperLambda.Compile());
var contextTypeConstant = Constant(_contextType);
- var containerConstant = Constant(cosmosQueryCompilationContext.CosmosContainer);
+ var rootEntityTypeConstant = Constant(rootEntityType);
var threadSafetyConstant = Constant(_threadSafetyChecksEnabled);
var standAloneStateManagerConstant = Constant(
QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution);
@@ -92,9 +91,10 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery
return selectExpression switch
{
{ ReadItemInfo: ReadItemInfo readItemInfo } => New(
- typeof(ReadItemQueryingEnumerable<>).MakeGenericType(readItemInfo.Type).GetConstructors()[0],
+ typeof(ReadItemQueryingEnumerable<>).MakeGenericType(shaperLambda.ReturnType).GetConstructors()[0],
cosmosQueryContextConstant,
- containerConstant,
+ rootEntityTypeConstant,
+ Constant(cosmosQueryCompilationContext.PartitionKeyPropertyValues),
Constant(readItemInfo),
shaperConstant,
contextTypeConstant,
@@ -109,8 +109,8 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery
Constant(selectExpression),
shaperConstant,
contextTypeConstant,
- containerConstant,
- Constant(_partitionKeyValueFromExtension, typeof(PartitionKey)),
+ rootEntityTypeConstant,
+ Constant(cosmosQueryCompilationContext.PartitionKeyPropertyValues),
standAloneStateManagerConstant,
threadSafetyConstant,
Constant(maxItemCount.Name),
@@ -124,10 +124,79 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery
Constant(selectExpression),
shaperConstant,
contextTypeConstant,
- containerConstant,
- Constant(_partitionKeyValueFromExtension, typeof(PartitionKey)),
+ rootEntityTypeConstant,
+ Constant(cosmosQueryCompilationContext.PartitionKeyPropertyValues),
standAloneStateManagerConstant,
threadSafetyConstant)
};
}
+
+ private static PartitionKey GeneratePartitionKey(
+ IEntityType rootEntityType,
+ List partitionKeyPropertyValues,
+ IReadOnlyDictionary parameterValues)
+ {
+ if (partitionKeyPropertyValues.Count == 0)
+ {
+ return PartitionKey.None;
+ }
+
+ var builder = new PartitionKeyBuilder();
+
+ var partitionKeyProperties = rootEntityType.GetPartitionKeyProperties();
+
+ int i;
+ for (i = 0; i < partitionKeyPropertyValues.Count && i < partitionKeyProperties.Count; i++)
+ {
+ var property = partitionKeyProperties[i];
+
+ switch (partitionKeyPropertyValues[i])
+ {
+ case SqlConstantExpression constant:
+ builder.Add(constant.Value, property);
+ continue;
+
+ // If WithPartitionKey() was used, its second argument is a params object[] array, which gets parameterized as a single
+ // parameter. Extract the object[] and iterate over the values within here.
+ case SqlParameterExpression parameter when parameter.Type == typeof(object[]):
+ {
+ if (!parameterValues.TryGetValue(parameter.Name, out var value)
+ || value is not object[] remainingValuesArray
+ || i != 1)
+ {
+ throw new UnreachableException("Couldn't find partition key parameter value");
+ }
+
+ for (var j = 0; j < remainingValuesArray.Length; j++, i++)
+ {
+ builder.Add(remainingValuesArray[j], partitionKeyProperties[i]);
+ }
+
+ goto End;
+ }
+
+ case SqlParameterExpression parameter:
+ {
+ builder.Add(
+ parameterValues.TryGetValue(parameter.Name, out var value)
+ ? value
+ : throw new UnreachableException("Couldn't find partition key parameter value"),
+ property);
+ continue;
+ }
+
+ default:
+ throw new UnreachableException();
+ }
+ }
+
+ End:
+ if (i != partitionKeyProperties.Count)
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.IncorrectPartitionKeyNumber(rootEntityType.DisplayName(), i, partitionKeyProperties.Count));
+ }
+
+ return builder.Build();
+ }
}
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs
index d3303c9d3cf..4882efcd955 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs
@@ -77,28 +77,31 @@ protected virtual void AddTranslationErrorDetails(string details)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual SqlExpression? Translate(Expression expression)
+ public virtual SqlExpression? Translate(Expression expression, bool applyDefaultTypeMapping = true)
{
TranslationErrorDetails = null;
- return TranslateInternal(expression);
+ return TranslateInternal(expression, applyDefaultTypeMapping);
}
- private SqlExpression? TranslateInternal(Expression expression)
+ private SqlExpression? TranslateInternal(Expression expression, bool applyDefaultTypeMapping = true)
{
var result = Visit(expression);
if (result is SqlExpression translation)
{
- translation = sqlExpressionFactory.ApplyDefaultTypeMapping(translation);
-
- if (translation.TypeMapping == null)
+ if (applyDefaultTypeMapping)
{
- // The return type is not-mappable hence return null
- return null;
- }
+ translation = sqlExpressionFactory.ApplyDefaultTypeMapping(translation);
+
+ if (translation.TypeMapping == null)
+ {
+ // The return type is not-mappable hence return null
+ return null;
+ }
- _sqlVerifyingExpressionVisitor.Visit(translation);
+ _sqlVerifyingExpressionVisitor.Visit(translation);
+ }
return translation;
}
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosValueConverterCompensatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosValueConverterCompensatingExpressionVisitor.cs
index 2b516cdd4ce..5ed43e4dacb 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosValueConverterCompensatingExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosValueConverterCompensatingExpressionVisitor.cs
@@ -67,7 +67,7 @@ private Expression VisitSelect(SelectExpression selectExpression)
var offset = (SqlExpression?)Visit(selectExpression.Offset);
return changed
- ? selectExpression.Update(projections, sources, predicate, orderings, limit, offset)
+ ? selectExpression.Update(sources, predicate, projections, orderings, offset, limit)
: selectExpression;
}
diff --git a/src/EFCore.Cosmos/Query/Internal/Expressions/ReadItemInfo.cs b/src/EFCore.Cosmos/Query/Internal/Expressions/ReadItemInfo.cs
index f6e8178fdd4..9a93540830f 100644
--- a/src/EFCore.Cosmos/Query/Internal/Expressions/ReadItemInfo.cs
+++ b/src/EFCore.Cosmos/Query/Internal/Expressions/ReadItemInfo.cs
@@ -18,7 +18,7 @@ public class ReadItemInfo
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual Type Type { get; }
+ public virtual IDictionary PropertyValues { get; }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -26,29 +26,6 @@ public class ReadItemInfo
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual IEntityType EntityType { get; }
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public virtual IDictionary PropertyParameters { get; }
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public ReadItemInfo(
- IEntityType entityType,
- IDictionary propertyParameters,
- Type type)
- {
- Type = type;
- EntityType = entityType;
- PropertyParameters = propertyParameters;
- }
+ public ReadItemInfo(IDictionary propertyValues)
+ => PropertyValues = propertyValues;
}
diff --git a/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs b/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs
index 3eea7d26e9e..fc4a2cd3c19 100644
--- a/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs
+++ b/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs
@@ -39,13 +39,21 @@ public sealed class SelectExpression : Expression, IPrintableExpression
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public SelectExpression(
- List projections,
List sources,
- List orderings)
+ SqlExpression? predicate,
+ List projections,
+ bool distinct,
+ List orderings,
+ SqlExpression? offset,
+ SqlExpression? limit)
{
- _projection = projections;
_sources = sources;
+ Predicate = predicate;
+ _projection = projections;
+ IsDistinct = distinct;
_orderings = orderings;
+ Offset = offset;
+ Limit = limit;
}
///
@@ -63,11 +71,10 @@ public SelectExpression(Expression projection)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public SelectExpression(SourceExpression source, Expression projection, ReadItemInfo? readItemInfo = null)
+ public SelectExpression(SourceExpression source, Expression projection)
{
_sources.Add(source);
_projectionMapping[new ProjectionMember()] = projection;
- ReadItemInfo = readItemInfo;
}
///
@@ -87,9 +94,13 @@ public static SelectExpression CreateForCollection(Expression sourceExpression,
if (!SourceExpression.IsCompatible(sourceExpression))
{
sourceExpression = new SelectExpression(
- [new ProjectionExpression(sourceExpression, null!)],
sources: [],
- orderings: [])
+ predicate: null,
+ [new ProjectionExpression(sourceExpression, null!)],
+ distinct: false,
+ orderings: [],
+ offset: null,
+ limit: null)
{
UsesSingleValueProjection = true
};
@@ -225,6 +236,7 @@ ParameterExpression parameterExpression when parameterValues.TryGetValue(paramet
=> value,
_ => null
};
+
builder.Add(rawKeyValue, tuple.Property);
}
@@ -589,13 +601,9 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
if (changed)
{
- var newSelectExpression = new SelectExpression(projections, sources, orderings)
+ var newSelectExpression = new SelectExpression(sources, predicate, projections, IsDistinct, orderings, offset, limit)
{
_projectionMapping = projectionMapping,
- Predicate = predicate,
- Offset = offset,
- Limit = limit,
- IsDistinct = IsDistinct,
UsesSingleValueProjection = UsesSingleValueProjection
};
@@ -612,12 +620,12 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public SelectExpression Update(
- List projections,
List sources,
SqlExpression? predicate,
+ List projections,
List orderings,
- SqlExpression? limit,
- SqlExpression? offset)
+ SqlExpression? offset,
+ SqlExpression? limit)
{
var projectionMapping = new Dictionary();
foreach (var (projectionMember, expression) in _projectionMapping)
@@ -625,18 +633,28 @@ public SelectExpression Update(
projectionMapping[projectionMember] = expression;
}
- return new SelectExpression(projections, sources, orderings)
+ return new SelectExpression(sources, predicate, projections, IsDistinct, orderings, offset, limit)
{
_projectionMapping = projectionMapping,
- Predicate = predicate,
- Offset = offset,
- Limit = limit,
- IsDistinct = IsDistinct,
UsesSingleValueProjection = UsesSingleValueProjection,
ReadItemInfo = ReadItemInfo
};
}
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public SelectExpression WithReadItemInfo(ReadItemInfo readItemInfo)
+ => new(Sources.ToList(), Predicate, Projection.ToList(), IsDistinct, Orderings.ToList(), Offset, Limit)
+ {
+ _projectionMapping = _projectionMapping,
+ UsesSingleValueProjection = UsesSingleValueProjection,
+ ReadItemInfo = readItemInfo
+ };
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -651,13 +669,9 @@ public SelectExpression WithSingleValueProjection()
projectionMapping[projectionMember] = expression;
}
- return new SelectExpression(Projection.ToList(), Sources.ToList(), Orderings.ToList())
+ return new SelectExpression(Sources.ToList(), Predicate, Projection.ToList(), IsDistinct, Orderings.ToList(), Offset, Limit)
{
_projectionMapping = projectionMapping,
- Predicate = Predicate,
- Offset = Offset,
- Limit = Limit,
- IsDistinct = IsDistinct,
UsesSingleValueProjection = true
};
}
diff --git a/src/EFCore.Cosmos/Query/Internal/Expressions/SqlParameterExpression.cs b/src/EFCore.Cosmos/Query/Internal/Expressions/SqlParameterExpression.cs
index c10c4725f8c..f8f7dfa640a 100644
--- a/src/EFCore.Cosmos/Query/Internal/Expressions/SqlParameterExpression.cs
+++ b/src/EFCore.Cosmos/Query/Internal/Expressions/SqlParameterExpression.cs
@@ -61,7 +61,7 @@ public override bool Equals(object? obj)
&& Equals(sqlParameterExpression));
private bool Equals(SqlParameterExpression sqlParameterExpression)
- => base.Equals(sqlParameterExpression) && Name != sqlParameterExpression.Name;
+ => base.Equals(sqlParameterExpression) && Name == sqlParameterExpression.Name;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs
index b1abb161f11..f427a381f8a 100644
--- a/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs
+++ b/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs
@@ -45,7 +45,12 @@ public interface ISqlExpressionFactory
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- SqlExpression? MakeBinary(ExpressionType operatorType, SqlExpression left, SqlExpression right, CoreTypeMapping? typeMapping);
+ SqlExpression? MakeBinary(
+ ExpressionType operatorType,
+ SqlExpression left,
+ SqlExpression right,
+ CoreTypeMapping? typeMapping,
+ SqlExpression? existingExpr = null);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
index ba466916174..34618bfa658 100644
--- a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
+++ b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
@@ -319,8 +319,17 @@ var t when t.TryGetSequenceType() != typeof(object) => t,
ExpressionType operatorType,
SqlExpression left,
SqlExpression right,
- CoreTypeMapping? typeMapping)
+ CoreTypeMapping? typeMapping,
+ SqlExpression? existingExpr = null)
{
+ switch (operatorType)
+ {
+ case ExpressionType.AndAlso:
+ return ApplyTypeMapping(AndAlso(left, right, existingExpr), typeMapping);
+ case ExpressionType.OrElse:
+ return ApplyTypeMapping(OrElse(left, right, existingExpr), typeMapping);
+ }
+
if (!SqlBinaryExpression.IsValidOperator(operatorType))
{
return null;
@@ -335,8 +344,6 @@ var t when t.TryGetSequenceType() != typeof(object) => t,
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.NotEqual:
- case ExpressionType.AndAlso:
- case ExpressionType.OrElse:
returnType = typeof(bool);
break;
}
@@ -417,6 +424,42 @@ public virtual SqlExpression LessThanOrEqual(SqlExpression left, SqlExpression r
public virtual SqlExpression AndAlso(SqlExpression left, SqlExpression right)
=> MakeBinary(ExpressionType.AndAlso, left, right, null)!;
+ private SqlExpression AndAlso(SqlExpression left, SqlExpression right, SqlExpression? existingExpr)
+ {
+ // false && x -> false
+ // x && true -> x
+ // x && x -> x
+ if (left is SqlConstantExpression { Value: false }
+ || right is SqlConstantExpression { Value: true }
+ || left.Equals(right))
+ {
+ return left;
+ }
+ // true && x -> x
+ // x && false -> false
+ if (left is SqlConstantExpression { Value: true } || right is SqlConstantExpression { Value: false })
+ {
+ return right;
+ }
+ // x is null && x is not null -> false
+ // x is not null && x is null -> false
+ if (left is SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } leftUnary
+ && right is SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } rightUnary
+ && leftUnary.Operand.Equals(rightUnary.Operand))
+ {
+ // the case in which left and right are the same expression is handled above
+ return Constant(false);
+ }
+ if (existingExpr is SqlBinaryExpression { OperatorType: ExpressionType.AndAlso } binaryExpr
+ && left == binaryExpr.Left
+ && right == binaryExpr.Right)
+ {
+ return existingExpr;
+ }
+
+ return new SqlBinaryExpression(ExpressionType.AndAlso, left, right, typeof(bool), null);
+ }
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -426,6 +469,43 @@ public virtual SqlExpression AndAlso(SqlExpression left, SqlExpression right)
public virtual SqlExpression OrElse(SqlExpression left, SqlExpression right)
=> MakeBinary(ExpressionType.OrElse, left, right, null)!;
+ private SqlExpression OrElse(SqlExpression left, SqlExpression right, SqlExpression? existingExpr)
+ {
+ // true || x -> true
+ // x || false -> x
+ // x || x -> x
+ if (left is SqlConstantExpression { Value: true }
+ || right is SqlConstantExpression { Value: false }
+ || left.Equals(right))
+ {
+ return left;
+ }
+ // false || x -> x
+ // x || true -> true
+ if (left is SqlConstantExpression { Value: false }
+ || right is SqlConstantExpression { Value: true })
+ {
+ return right;
+ }
+ // x is null || x is not null -> true
+ // x is not null || x is null -> true
+ if (left is SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } leftUnary
+ && right is SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } rightUnary
+ && leftUnary.Operand.Equals(rightUnary.Operand))
+ {
+ // the case in which left and right are the same expression is handled above
+ return Constant(true);
+ }
+ if (existingExpr is SqlBinaryExpression { OperatorType: ExpressionType.OrElse } binaryExpr
+ && left == binaryExpr.Left
+ && right == binaryExpr.Right)
+ {
+ return existingExpr;
+ }
+
+ return new SqlBinaryExpression(ExpressionType.OrElse, left, right, typeof(bool), null);
+ }
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs
index d580a748e1f..993803f8c54 100644
--- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs
+++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs
@@ -469,36 +469,34 @@ private SqlExpression AndAlso(SqlExpression left, SqlExpression right, SqlExpres
}
// true && x -> x
// x && false -> false
- else if (left is SqlConstantExpression { Value: true } || right is SqlConstantExpression { Value: false })
+ if (left is SqlConstantExpression { Value: true } || right is SqlConstantExpression { Value: false })
{
return right;
}
// x is null && x is not null -> false
// x is not null && x is null -> false
- else if (left is SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } leftUnary
+ if (left is SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } leftUnary
&& right is SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } rightUnary
&& leftUnary.Operand.Equals(rightUnary.Operand))
{
// the case in which left and right are the same expression is handled above
return Constant(false);
}
- else if (existingExpr is SqlBinaryExpression { OperatorType: ExpressionType.AndAlso } binaryExpr
+ if (existingExpr is SqlBinaryExpression { OperatorType: ExpressionType.AndAlso } binaryExpr
&& left == binaryExpr.Left
&& right == binaryExpr.Right)
{
return existingExpr;
}
- else
- {
- return new SqlBinaryExpression(ExpressionType.AndAlso, left, right, typeof(bool), null);
- }
+
+ return new SqlBinaryExpression(ExpressionType.AndAlso, left, right, typeof(bool), null);
}
///
public virtual SqlExpression OrElse(SqlExpression left, SqlExpression right)
=> MakeBinary(ExpressionType.OrElse, left, right, null)!;
- private SqlExpression OrElse(SqlExpression left, SqlExpression right, SqlExpression? existingExpr = null)
+ private SqlExpression OrElse(SqlExpression left, SqlExpression right, SqlExpression? existingExpr)
{
// true || x -> true
// x || false -> x
@@ -511,30 +509,28 @@ private SqlExpression OrElse(SqlExpression left, SqlExpression right, SqlExpress
}
// false || x -> x
// x || true -> true
- else if (left is SqlConstantExpression { Value: false }
+ if (left is SqlConstantExpression { Value: false }
|| right is SqlConstantExpression { Value: true })
{
return right;
}
// x is null || x is not null -> true
// x is not null || x is null -> true
- else if (left is SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } leftUnary
+ if (left is SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } leftUnary
&& right is SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } rightUnary
&& leftUnary.Operand.Equals(rightUnary.Operand))
{
// the case in which left and right are the same expression is handled above
return Constant(true);
}
- else if (existingExpr is SqlBinaryExpression { OperatorType: ExpressionType.OrElse } binaryExpr
+ if (existingExpr is SqlBinaryExpression { OperatorType: ExpressionType.OrElse } binaryExpr
&& left == binaryExpr.Left
&& right == binaryExpr.Right)
{
return existingExpr;
}
- else
- {
- return new SqlBinaryExpression(ExpressionType.OrElse, left, right, typeof(bool), null);
- }
+
+ return new SqlBinaryExpression(ExpressionType.OrElse, left, right, typeof(bool), null);
}
///
diff --git a/src/EFCore/Query/QueryCompilationContext.cs b/src/EFCore/Query/QueryCompilationContext.cs
index 286dc70c201..c30e04ff448 100644
--- a/src/EFCore/Query/QueryCompilationContext.cs
+++ b/src/EFCore/Query/QueryCompilationContext.cs
@@ -301,7 +301,7 @@ void IPrintableExpression.Print(ExpressionPrinter expressionPrinter)
private sealed class RuntimeParameterConstantLifter(ILiftableConstantFactory liftableConstantFactory) : ExpressionVisitor
{
- private readonly static MethodInfo ComplexPropertyListElementAddMethod = typeof(List).GetMethod(nameof(List.Add))!;
+ private static readonly MethodInfo ComplexPropertyListElementAddMethod = typeof(List).GetMethod(nameof(List.Add))!;
protected override Expression VisitConstant(ConstantExpression constantExpression)
{
diff --git a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs
index f5322a31a1f..7f48d4d6559 100644
--- a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs
@@ -119,7 +119,11 @@ protected override Expression VisitExtension(Expression extensionExpression)
// SQL Server TemporalQueryRootExpression.
if (queryRootExpression.GetType() == typeof(EntityQueryRootExpression))
{
- return CreateShapedQueryExpression(((EntityQueryRootExpression)extensionExpression).EntityType);
+ var shapedQuery = CreateShapedQueryExpression(((EntityQueryRootExpression)extensionExpression).EntityType);
+ if (shapedQuery is not null)
+ {
+ return shapedQuery;
+ }
}
_untranslatedExpression = queryRootExpression;
@@ -586,7 +590,7 @@ protected virtual Expression MarkShaperNullable(Expression shaperExpression)
///
/// The entity type.
/// A shaped query expression for the given entity type.
- protected abstract ShapedQueryExpression CreateShapedQueryExpression(IEntityType entityType);
+ protected abstract ShapedQueryExpression? CreateShapedQueryExpression(IEntityType entityType);
///
/// Translates method over the given source.
diff --git a/test/EFCore.Cosmos.FunctionalTests/EmbeddedDocumentsTest.cs b/test/EFCore.Cosmos.FunctionalTests/EmbeddedDocumentsTest.cs
index 110aadaa4d7..03669349a81 100644
--- a/test/EFCore.Cosmos.FunctionalTests/EmbeddedDocumentsTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/EmbeddedDocumentsTest.cs
@@ -531,6 +531,13 @@ public virtual async Task Can_query_and_modify_nested_embedded_types()
{
var missile = await context.Set().FirstAsync(v => v.Name == "AIM-9M Sidewinder");
+ AssertSql(
+ """
+SELECT c
+FROM root c
+WHERE (c["Discriminator"] IN ("Vehicle", "PoweredVehicle") AND (c["Name"] = "AIM-9M Sidewinder"))
+OFFSET 0 LIMIT 1
+""");
Assert.Equal("Heat-seeking", missile.Operator.Details.Type);
missile.Operator.Details.Type = "IR";
diff --git a/test/EFCore.Cosmos.FunctionalTests/FindCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/FindCosmosTest.cs
index bf0e875145a..bc3d0a2f63e 100644
--- a/test/EFCore.Cosmos.FunctionalTests/FindCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/FindCosmosTest.cs
@@ -209,20 +209,14 @@ public override async Task Find_int_key_from_store_async(CancellationType cancel
{
await base.Find_int_key_from_store_async(cancellationType);
- AssertSql(
- """
-ReadItem(None, IntKey|77)
-""");
+ AssertSql("ReadItem(None, IntKey|77)");
}
public override async Task Returns_null_for_int_key_not_in_store_async(CancellationType cancellationType)
{
await base.Returns_null_for_int_key_not_in_store_async(cancellationType);
- AssertSql(
- """
-ReadItem(None, IntKey|99)
-""");
+ AssertSql("ReadItem(None, IntKey|99)");
}
public override async Task Find_nullable_int_key_tracked_async(CancellationType cancellationType)
@@ -236,20 +230,14 @@ public override async Task Find_nullable_int_key_from_store_async(CancellationTy
{
await base.Find_nullable_int_key_from_store_async(cancellationType);
- AssertSql(
- """
-ReadItem(None, NullableIntKey|77)
-""");
+ AssertSql("ReadItem(None, NullableIntKey|77)");
}
public override async Task Returns_null_for_nullable_int_key_not_in_store_async(CancellationType cancellationType)
{
await base.Returns_null_for_nullable_int_key_not_in_store_async(cancellationType);
- AssertSql(
- """
-ReadItem(None, NullableIntKey|99)
-""");
+ AssertSql("ReadItem(None, NullableIntKey|99)");
}
public override async Task Find_string_key_tracked_async(CancellationType cancellationType)
@@ -263,20 +251,14 @@ public override async Task Find_string_key_from_store_async(CancellationType can
{
await base.Find_string_key_from_store_async(cancellationType);
- AssertSql(
- """
-ReadItem(None, StringKey|Cat)
-""");
+ AssertSql("ReadItem(None, StringKey|Cat)");
}
public override async Task Returns_null_for_string_key_not_in_store_async(CancellationType cancellationType)
{
await base.Returns_null_for_string_key_not_in_store_async(cancellationType);
- AssertSql(
- """
-ReadItem(None, StringKey|Fox)
-""");
+ AssertSql("ReadItem(None, StringKey|Fox)");
}
public override async Task Find_composite_key_tracked_async(CancellationType cancellationType)
@@ -290,20 +272,14 @@ public override async Task Find_composite_key_from_store_async(CancellationType
{
await base.Find_composite_key_from_store_async(cancellationType);
- AssertSql(
- """
-ReadItem(None, CompositeKey|77|Dog)
-""");
+ AssertSql("ReadItem(None, CompositeKey|77|Dog)");
}
public override async Task Returns_null_for_composite_key_not_in_store_async(CancellationType cancellationType)
{
await base.Returns_null_for_composite_key_not_in_store_async(cancellationType);
- AssertSql(
- """
-ReadItem(None, CompositeKey|77|Fox)
-""");
+ AssertSql("ReadItem(None, CompositeKey|77|Fox)");
}
public override async Task Find_base_type_tracked_async(CancellationType cancellationType)
@@ -319,7 +295,12 @@ public override async Task Find_base_type_from_store_async(CancellationType canc
AssertSql(
"""
-ReadItem(None, BaseType|77)
+@__p_0='77'
+
+SELECT c
+FROM root c
+WHERE (c["Discriminator"] IN ("BaseType", "DerivedType") AND (c["Id"] = @__p_0))
+OFFSET 0 LIMIT 1
""");
}
@@ -327,9 +308,13 @@ public override async Task Returns_null_for_base_type_not_in_store_async(Cancell
{
await base.Returns_null_for_base_type_not_in_store_async(cancellationType);
- AssertSql(
- """
-ReadItem(None, BaseType|99)
+ AssertSql("""
+@__p_0='99'
+
+SELECT c
+FROM root c
+WHERE (c["Discriminator"] IN ("BaseType", "DerivedType") AND (c["Id"] = @__p_0))
+OFFSET 0 LIMIT 1
""");
}
@@ -344,30 +329,21 @@ public override async Task Find_derived_type_from_store_async(CancellationType c
{
await base.Find_derived_type_from_store_async(cancellationType);
- AssertSql(
- """
-ReadItem(None, DerivedType|78)
-""");
+ AssertSql("ReadItem(None, DerivedType|78)");
}
public override async Task Returns_null_for_derived_type_not_in_store_async(CancellationType cancellationType)
{
await base.Returns_null_for_derived_type_not_in_store_async(cancellationType);
- AssertSql(
- """
-ReadItem(None, DerivedType|99)
-""");
+ AssertSql("ReadItem(None, DerivedType|99)");
}
public override async Task Find_base_type_using_derived_set_from_store_async(CancellationType cancellationType)
{
await base.Find_base_type_using_derived_set_from_store_async(cancellationType);
- AssertSql(
- """
-ReadItem(None, DerivedType|77)
-""");
+ AssertSql("ReadItem(None, DerivedType|77)");
}
public override async Task Find_derived_type_using_base_set_tracked_async(CancellationType cancellationType)
@@ -388,20 +364,14 @@ public override async Task Find_shadow_key_from_store_async(CancellationType can
{
await base.Find_shadow_key_from_store_async(cancellationType);
- AssertSql(
- """
-ReadItem(None, ShadowKey|77)
-""");
+ AssertSql("ReadItem(None, ShadowKey|77)");
}
public override async Task Returns_null_for_shadow_key_not_in_store_async(CancellationType cancellationType)
{
await base.Returns_null_for_shadow_key_not_in_store_async(cancellationType);
- AssertSql(
- """
-ReadItem(None, ShadowKey|99)
-""");
+ AssertSql("ReadItem(None, ShadowKey|99)");
}
public override async Task Returns_null_for_null_key_values_array_async(CancellationType cancellationType)
diff --git a/test/EFCore.Cosmos.FunctionalTests/HierarchicalPartitionKeyTest.cs b/test/EFCore.Cosmos.FunctionalTests/HierarchicalPartitionKeyTest.cs
index b068d7f40dc..c6d0b31e748 100644
--- a/test/EFCore.Cosmos.FunctionalTests/HierarchicalPartitionKeyTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/HierarchicalPartitionKeyTest.cs
@@ -3,6 +3,7 @@
namespace Microsoft.EntityFrameworkCore;
+// TODO: Consider removing these in favor of ReadItemPartitionKeyQueryTest
public class HierarchicalPartitionKeyTest : IClassFixture
{
private const string DatabaseName = nameof(HierarchicalPartitionKeyTest);
@@ -79,6 +80,7 @@ public async Task Can_query_with_implicit_partition_key_filter()
"""
SELECT c
FROM root c
+WHERE ((c["Id"] = 42) OR (c["Name"] = "John Snow"))
OFFSET 0 LIMIT 2
""";
diff --git a/test/EFCore.Cosmos.FunctionalTests/PartitionKeyTest.cs b/test/EFCore.Cosmos.FunctionalTests/PartitionKeyTest.cs
index 2e027542742..5af5a4b0dde 100644
--- a/test/EFCore.Cosmos.FunctionalTests/PartitionKeyTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/PartitionKeyTest.cs
@@ -5,6 +5,7 @@ namespace Microsoft.EntityFrameworkCore;
#nullable disable
+// TODO: Consider removing these in favor of ReadItemPartitionKeyQueryTest
public class PartitionKeyTest : IClassFixture
{
private const string DatabaseName = nameof(PartitionKeyTest);
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/InheritanceQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/InheritanceQueryCosmosTest.cs
index 559016ca746..6b468bf6e68 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/InheritanceQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/InheritanceQueryCosmosTest.cs
@@ -155,7 +155,7 @@ public override Task Can_use_of_type_bird(bool async)
"""
SELECT c
FROM root c
-WHERE (c["Discriminator"] IN ("Eagle", "Kiwi") AND c["Discriminator"] IN ("Eagle", "Kiwi"))
+WHERE c["Discriminator"] IN ("Eagle", "Kiwi")
ORDER BY c["Species"]
""");
});
@@ -185,7 +185,7 @@ public override Task Can_use_of_type_bird_with_projection(bool async)
"""
SELECT c["EagleId"]
FROM root c
-WHERE (c["Discriminator"] IN ("Eagle", "Kiwi") AND c["Discriminator"] IN ("Eagle", "Kiwi"))
+WHERE c["Discriminator"] IN ("Eagle", "Kiwi")
""");
});
@@ -199,7 +199,7 @@ public override Task Can_use_of_type_bird_first(bool async)
"""
SELECT c
FROM root c
-WHERE (c["Discriminator"] IN ("Eagle", "Kiwi") AND c["Discriminator"] IN ("Eagle", "Kiwi"))
+WHERE c["Discriminator"] IN ("Eagle", "Kiwi")
ORDER BY c["Species"]
OFFSET 0 LIMIT 1
""");
@@ -585,7 +585,7 @@ public override Task GetType_in_hierarchy_in_abstract_base_type(bool async)
"""
SELECT c
FROM root c
-WHERE (c["Discriminator"] IN ("Eagle", "Kiwi") AND false)
+WHERE false
""");
});
@@ -599,7 +599,7 @@ public override Task GetType_in_hierarchy_in_intermediate_type(bool async)
"""
SELECT c
FROM root c
-WHERE (c["Discriminator"] IN ("Eagle", "Kiwi") AND false)
+WHERE false
""");
});
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs
index 7b12caaaca7..8d6afb928ae 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs
@@ -232,13 +232,7 @@ public override Task Where_Single(bool async)
{
await base.Where_Single(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
-OFFSET 0 LIMIT 2
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override Task FirstOrDefault(bool async)
@@ -293,13 +287,7 @@ public override Task SingleOrDefault_Predicate(bool async)
{
await base.SingleOrDefault_Predicate(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
-OFFSET 0 LIMIT 2
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override async Task SingleOrDefault_Throws(bool async)
@@ -341,13 +329,7 @@ public override Task Where_SingleOrDefault(bool async)
{
await base.Where_SingleOrDefault(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
-OFFSET 0 LIMIT 2
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override async Task Select_All(bool async)
@@ -1245,13 +1227,7 @@ public override Task Single_Predicate(bool async)
{
await base.Single_Predicate(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
-OFFSET 0 LIMIT 2
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override async Task FirstOrDefault_inside_subquery_gets_server_evaluated(bool async)
@@ -1300,13 +1276,7 @@ public override Task Last_when_no_order_by(bool async)
{
await base.Last_when_no_order_by(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
-OFFSET 0 LIMIT 1
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override Task LastOrDefault_when_no_order_by(bool async)
@@ -1315,13 +1285,7 @@ public override Task LastOrDefault_when_no_order_by(bool async)
{
await base.LastOrDefault_when_no_order_by(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
-OFFSET 0 LIMIT 1
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override Task Last_Predicate(bool async)
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs
index 8b94e8ddd97..0492b5947c3 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs
@@ -1621,12 +1621,7 @@ public override Task Static_string_equals_in_predicate(bool async)
{
await base.Static_string_equals_in_predicate(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ANATR"))
-""");
+ AssertSql("ReadItem(None, Customer|ANATR)");
});
public override Task Static_equals_nullable_datetime_compared_to_non_nullable(bool async)
@@ -1655,7 +1650,7 @@ public override Task Static_equals_int_compared_to_long(bool async)
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Order") AND false)
+WHERE false
""");
});
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs
index 4fa47fe12e4..bb174e94fb5 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs
@@ -87,15 +87,7 @@ public override Task Local_dictionary(bool async)
{
await base.Local_dictionary(a);
- AssertSql(
- """
-@__p_0='ALFKI'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = @__p_0))
-OFFSET 0 LIMIT 2
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override Task Entity_equality_self(bool async)
@@ -134,15 +126,7 @@ public override Task Entity_equality_local_composite_key(bool async)
{
await base.Entity_equality_local_composite_key(a);
- AssertSql(
- """
-@__entity_equality_local_0_OrderID='10248'
-@__entity_equality_local_0_ProductID='11'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "OrderDetail") AND ((c["OrderID"] = @__entity_equality_local_0_OrderID) AND (c["ProductID"] = @__entity_equality_local_0_ProductID)))
-""");
+ AssertSql("ReadItem(None, OrderDetail|10248|11)");
});
public override async Task Join_with_entity_equality_local_on_both_sources(bool async)
@@ -173,12 +157,7 @@ public override Task Entity_equality_local_inline_composite_key(bool async)
{
await base.Entity_equality_local_inline_composite_key(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "OrderDetail") AND ((c["OrderID"] = 10248) AND (c["ProductID"] = 11)))
-""");
+ AssertSql("ReadItem(None, OrderDetail|10248|11)");
});
public override Task Entity_equality_null(bool async)
@@ -1138,104 +1117,117 @@ public override async Task Where_Join_Not_Exists(bool async)
public override async Task Join_OrderBy_Count(bool async)
{
- // Cosmos client evaluation. Issue #17246.
- await AssertTranslationFailed(() => base.Join_OrderBy_Count(async));
+ await AssertTranslationFailedWithDetails(
+ () => base.Join_OrderBy_Count(async),
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Order), nameof(Customer)));
AssertSql();
}
public override async Task Multiple_joins_Where_Order_Any(bool async)
{
- // Cosmos client evaluation. Issue #17246.
- await AssertTranslationFailed(() => base.Multiple_joins_Where_Order_Any(async));
+ await AssertTranslationFailedWithDetails(
+ () => base.Multiple_joins_Where_Order_Any(async),
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Order), nameof(Customer)));
AssertSql();
}
public override async Task Where_join_select(bool async)
{
- // Cosmos client evaluation. Issue #17246.
- await AssertTranslationFailed(() => base.Where_join_select(async));
+ await AssertTranslationFailedWithDetails(
+ () => base.Where_join_select(async),
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Order), nameof(Customer)));
AssertSql();
}
public override async Task Where_orderby_join_select(bool async)
{
- // Cosmos client evaluation. Issue #17246.
- await AssertTranslationFailed(() => base.Where_orderby_join_select(async));
+ await AssertTranslationFailedWithDetails(
+ () => base.Where_orderby_join_select(async),
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Order), nameof(Customer)));
AssertSql();
}
public override async Task Where_join_orderby_join_select(bool async)
{
- // Cosmos client evaluation. Issue #17246.
- await AssertTranslationFailed(() => base.Where_join_orderby_join_select(async));
+ await AssertTranslationFailedWithDetails(
+ () => base.Where_join_orderby_join_select(async),
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Order), nameof(Customer)));
AssertSql();
}
public override async Task Where_select_many(bool async)
{
- // Cosmos client evaluation. Issue #17246.
- await AssertTranslationFailed(() => base.Where_select_many(async));
+ await AssertTranslationFailedWithDetails(
+ () => base.Where_select_many(async),
+ CosmosStrings.NonCorrelatedSubqueriesNotSupported);
AssertSql();
}
public override async Task Where_orderby_select_many(bool async)
{
- // Cosmos client evaluation. Issue #17246.
- await AssertTranslationFailed(() => base.Where_orderby_select_many(async));
+ await AssertTranslationFailedWithDetails(
+ () => base.Where_orderby_select_many(async),
+ CosmosStrings.NonCorrelatedSubqueriesNotSupported);
AssertSql();
}
public override async Task SelectMany_cartesian_product_with_ordering(bool async)
{
- // Cosmos client evaluation. Issue #17246.
- await AssertTranslationFailed(() => base.SelectMany_cartesian_product_with_ordering(async));
+ await AssertTranslationFailedWithDetails(
+ () => base.SelectMany_cartesian_product_with_ordering(async),
+ CosmosStrings.NonCorrelatedSubqueriesNotSupported);
AssertSql();
}
public override async Task SelectMany_Joined_DefaultIfEmpty(bool async)
{
- // Cosmos client evaluation. Issue #17246.
- await AssertTranslationFailed(() => base.SelectMany_Joined_DefaultIfEmpty(async));
+ await AssertTranslationFailedWithDetails(
+ () => base.SelectMany_Joined_DefaultIfEmpty(async),
+ CosmosStrings.NonCorrelatedSubqueriesNotSupported);
AssertSql();
}
public override async Task SelectMany_Joined_DefaultIfEmpty2(bool async)
{
- // Cosmos client evaluation. Issue #17246.
- await AssertTranslationFailed(() => base.SelectMany_Joined_DefaultIfEmpty2(async));
+ await AssertTranslationFailedWithDetails(
+ () => base.SelectMany_Joined_DefaultIfEmpty2(async),
+ CosmosStrings.NonCorrelatedSubqueriesNotSupported);
AssertSql();
}
public override async Task SelectMany_Joined_DefaultIfEmpty3(bool async)
{
- // Cosmos client evaluation. Issue #17246.
- await AssertTranslationFailed(() => base.SelectMany_Joined_DefaultIfEmpty3(async));
+ await AssertTranslationFailedWithDetails(
+ () => base.SelectMany_Joined_DefaultIfEmpty3(async),
+ CosmosStrings.NonCorrelatedSubqueriesNotSupported);
AssertSql();
}
public override async Task SelectMany_Joined(bool async)
{
- // Cosmos client evaluation. Issue #17246.
- await AssertTranslationFailed(() => base.SelectMany_Joined(async));
+ await AssertTranslationFailedWithDetails(
+ () => base.SelectMany_Joined(async),
+ CosmosStrings.NonCorrelatedSubqueriesNotSupported);
AssertSql();
}
public override async Task SelectMany_Joined_Take(bool async)
{
- // Cosmos client evaluation. Issue #17246.
- await AssertTranslationFailed(() => base.SelectMany_Joined_Take(async));
+ await AssertTranslationFailedWithDetails(
+ () => base.SelectMany_Joined_Take(async),
+ CosmosStrings.NonCorrelatedSubqueriesNotSupported);
AssertSql();
}
@@ -2443,7 +2435,7 @@ public override Task Parameter_extraction_short_circuits_1(bool async)
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Order") AND ((c["OrderID"] < 10400) AND (false OR (((c["OrderDate"] != null) AND (DateTimePart("mm", c["OrderDate"]) = @__dateFilter_Value_Month_0)) AND (DateTimePart("yyyy", c["OrderDate"]) = @__dateFilter_Value_Year_1)))))
+WHERE ((c["Discriminator"] = "Order") AND ((c["OrderID"] < 10400) AND (((c["OrderDate"] != null) AND (DateTimePart("mm", c["OrderDate"]) = @__dateFilter_Value_Month_0)) AND (DateTimePart("yyyy", c["OrderDate"]) = @__dateFilter_Value_Year_1))))
""",
//
"""
@@ -2473,7 +2465,7 @@ FROM root c
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Order") AND false)
+WHERE false
""");
});
@@ -3177,12 +3169,7 @@ public override Task Int16_parameter_can_be_used_for_int_column(bool async)
{
await base.Int16_parameter_can_be_used_for_int_column(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Order") AND (c["OrderID"] = 10300))
-""");
+ AssertSql("ReadItem(None, Order|10300)");
});
public override async Task Subquery_is_null_translated_correctly(bool async)
@@ -4194,14 +4181,7 @@ public override Task Entity_equality_with_null_coalesce_client_side(bool async)
{
await base.Entity_equality_with_null_coalesce_client_side(a);
- AssertSql(
- """
-@__entity_equality_a_0_CustomerID='ALFKI'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = @__entity_equality_a_0_CustomerID))
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override Task Entity_equality_contains_with_list_of_null(bool async)
@@ -4557,12 +4537,7 @@ public override Task Where_Property_when_non_shadow(bool async)
{
await base.Where_Property_when_non_shadow(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Order") AND (c["OrderID"] = 10248))
-""");
+ AssertSql("ReadItem(None, Order|10248)");
});
public override Task OrderBy_Select(bool async)
@@ -4713,12 +4688,7 @@ public override Task Null_parameter_name_works(bool async)
{
await base.Null_parameter_name_works(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = null))
-""");
+ AssertSql("ReadItem(None, Customer|null)");
});
public override Task Where_Property_shadow_closure(bool async)
@@ -5299,14 +5269,7 @@ public override Task Static_member_access_gets_parameterized_within_larger_evalu
{
await base.Static_member_access_gets_parameterized_within_larger_evaluatable(a);
- AssertSql(
- """
-@__p_0='ALFKI'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = @__p_0))
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
[ConditionalFact]
@@ -5398,6 +5361,26 @@ ORDER BY c["CustomerID"]
""");
}
+ [ConditionalFact]
+ public virtual async Task ToPageAsync_does_not_use_ReadItem()
+ {
+ await using var context = CreateContext();
+
+ var onlyPage = await context.Set()
+ .Where(c => c.CustomerID == "ALFKI")
+ .ToPageAsync(pageSize: 10, continuationToken: null);
+
+ Assert.Equal("ALFKI", onlyPage.Values[0].CustomerID);
+ Assert.Null(onlyPage.ContinuationToken);
+
+ AssertSql(
+ """
+SELECT c
+FROM root c
+WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
+""");
+ }
+
[ConditionalFact]
public virtual async Task ToPageAsync_in_subquery_throws()
=> await AssertTranslationFailedWithDetails(
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs
index db461fc5360..e1a28039882 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs
@@ -1638,10 +1638,9 @@ public override async Task Reverse_in_join_inner(bool async)
public override async Task Reverse_in_join_inner_with_skip(bool async)
{
- Assert.Equal(
- CosmosStrings.ReverseAfterSkipTakeNotSupported,
- (await Assert.ThrowsAsync(
- () => base.Reverse_in_join_inner_with_skip(async))).Message);
+ await AssertTranslationFailedWithDetails(
+ () => base.Reverse_in_join_inner_with_skip(async),
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Order), nameof(Customer)));
AssertSql();
}
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs
index d48b83e5b54..94ae70d42f3 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs
@@ -1000,12 +1000,7 @@ public override Task Where_equals_method_int(bool async)
{
await base.Where_equals_method_int(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Employee") AND (c["EmployeeID"] = 1))
-""");
+ AssertSql("ReadItem(None, Employee|1)");
});
public override Task Where_equals_using_object_overload_on_mismatched_types(bool async)
@@ -1018,7 +1013,7 @@ public override Task Where_equals_using_object_overload_on_mismatched_types(bool
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Employee") AND false)
+WHERE false
""");
});
@@ -1028,14 +1023,7 @@ public override Task Where_equals_using_int_overload_on_mismatched_types(bool as
{
await base.Where_equals_using_int_overload_on_mismatched_types(a);
- AssertSql(
- """
-@__p_0='1'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Employee") AND (c["EmployeeID"] = @__p_0))
-""");
+ AssertSql("ReadItem(None, Employee|1)");
});
public override Task Where_equals_on_mismatched_types_nullable_int_long(bool async)
@@ -1048,13 +1036,13 @@ public override Task Where_equals_on_mismatched_types_nullable_int_long(bool asy
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Employee") AND false)
+WHERE false
""",
//
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Employee") AND false)
+WHERE false
""");
});
@@ -1068,13 +1056,13 @@ public override Task Where_equals_on_mismatched_types_nullable_long_nullable_int
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Employee") AND false)
+WHERE false
""",
//
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Employee") AND false)
+WHERE false
""");
});
@@ -1478,7 +1466,7 @@ public override Task Where_constant_is_null(bool async)
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND false)
+WHERE false
""");
});
@@ -1506,7 +1494,7 @@ public override Task Where_null_is_not_null(bool async)
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND false)
+WHERE false
""");
});
@@ -1907,7 +1895,7 @@ public override Task Where_false(bool async)
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND false)
+WHERE false
""");
});
@@ -1921,20 +1909,12 @@ public override Task Where_bool_closure(bool async)
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND false)
+WHERE false
""",
//
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
-""",
+ "ReadItem(None, Customer|ALFKI)",
//
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
-""",
+ "ReadItem(None, Customer|ALFKI)",
//
"""
SELECT c
@@ -1963,12 +1943,7 @@ public override Task Where_expression_invoke_1(bool async)
{
await base.Where_expression_invoke_1(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override async Task Where_expression_invoke_2(bool async)
@@ -1985,12 +1960,7 @@ public override Task Where_expression_invoke_3(bool async)
{
await base.Where_expression_invoke_3(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override async Task Where_concat_string_int_comparison1(bool async)
@@ -2144,7 +2114,7 @@ public override Task Where_ternary_boolean_condition_with_false_as_result_false(
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Product") AND false)
+WHERE false
""");
});
@@ -2295,14 +2265,7 @@ public override Task Where_array_index(bool async)
{
await base.Where_array_index(a);
- AssertSql(
- """
-@__p_0='ALFKI'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = @__p_0))
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override async Task Where_multiple_contains_in_subquery_with_or(bool async)
@@ -2603,13 +2566,8 @@ public override Task Filter_with_EF_Property_using_closure_for_property_name(boo
{
await base.Filter_with_EF_Property_using_closure_for_property_name(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
-""");
- });
+ AssertSql("ReadItem(None, Customer|ALFKI)");
+ });
public override Task Filter_with_EF_Property_using_function_for_property_name(bool async)
=> Fixture.NoSyncTest(
@@ -2617,12 +2575,7 @@ public override Task Filter_with_EF_Property_using_function_for_property_name(bo
{
await base.Filter_with_EF_Property_using_function_for_property_name(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override async Task FirstOrDefault_over_scalar_projection_compared_to_null(bool async)
@@ -2823,7 +2776,7 @@ public override Task GetType_on_non_hierarchy2(bool async)
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND false)
+WHERE false
""");
});
@@ -2837,7 +2790,7 @@ public override Task GetType_on_non_hierarchy3(bool async)
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND false)
+WHERE false
""");
});
@@ -2918,21 +2871,9 @@ public override Task Enclosing_class_settable_member_generates_parameter(bool as
await base.Enclosing_class_settable_member_generates_parameter(a);
AssertSql(
- """
-@__SettableProperty_0='10274'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Order") AND (c["OrderID"] = @__SettableProperty_0))
-""",
+ "ReadItem(None, Order|10274)",
//
- """
-@__SettableProperty_0='10275'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Order") AND (c["OrderID"] = @__SettableProperty_0))
-""");
+ "ReadItem(None, Order|10275)");
});
public override Task Enclosing_class_readonly_member_generates_parameter(bool async)
@@ -2941,14 +2882,7 @@ public override Task Enclosing_class_readonly_member_generates_parameter(bool as
{
await base.Enclosing_class_readonly_member_generates_parameter(a);
- AssertSql(
- """
-@__ReadOnlyProperty_0='10275'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Order") AND (c["OrderID"] = @__ReadOnlyProperty_0))
-""");
+ AssertSql("ReadItem(None, Order|10275)");
});
public override Task Enclosing_class_const_member_does_not_generate_parameter(bool async)
@@ -2957,12 +2891,7 @@ public override Task Enclosing_class_const_member_does_not_generate_parameter(bo
{
await base.Enclosing_class_const_member_does_not_generate_parameter(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Order") AND (c["OrderID"] = 10274))
-""");
+ AssertSql("ReadItem(None, Order|10274)");
});
public override Task Generic_Ilist_contains_translates_to_server(bool async)
@@ -3304,12 +3233,7 @@ public override Task EF_Constant(bool async)
{
await base.EF_Constant(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override Task EF_Constant_with_subtree(bool async)
@@ -3318,12 +3242,7 @@ public override Task EF_Constant_with_subtree(bool async)
{
await base.EF_Constant_with_subtree(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override Task EF_Constant_does_not_parameterized_as_part_of_bigger_subtree(bool async)
@@ -3353,14 +3272,7 @@ public override Task EF_Parameter(bool async)
{
await base.EF_Parameter(a);
- AssertSql(
- """
-@__p_0='ALFKI'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = @__p_0))
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override Task EF_Parameter_with_subtree(bool async)
@@ -3369,14 +3281,7 @@ public override Task EF_Parameter_with_subtree(bool async)
{
await base.EF_Parameter_with_subtree(a);
- AssertSql(
- """
-@__p_0='ALFKI'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = @__p_0))
-""");
+ AssertSql("ReadItem(None, Customer|ALFKI)");
});
public override Task EF_Parameter_does_not_parameterized_as_part_of_bigger_subtree(bool async)
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs
index f5537bf5faa..0f9bd9e7ca3 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs
@@ -18,15 +18,24 @@ public OwnedQueryCosmosTest(OwnedQueryCosmosFixture fixture, ITestOutputHelper t
Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
- // Fink.Barton is a non-owned navigation, cross-document join
- public override Task Query_loads_reference_nav_automatically_in_projection(bool async)
- => AssertTranslationFailedWithDetails(
- () => base.Query_loads_reference_nav_automatically_in_projection(async),
- CosmosStrings.CrossDocumentJoinNotSupported);
+ public override async Task Query_loads_reference_nav_automatically_in_projection(bool async)
+ {
+ // Fink.Barton is a non-owned navigation, cross-document join
+ await AssertTranslationFailedWithDetails(
+ () => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference(async),
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery("Planet", "OwnedPerson"));
- // Non-correlated queries not supported by Cosmos
- public override Task Query_with_owned_entity_equality_operator(bool async)
- => AssertTranslationFailed(() => base.Query_with_owned_entity_equality_operator(async));
+ AssertSql();
+ }
+
+ public override async Task Query_with_owned_entity_equality_operator(bool async)
+ {
+ await AssertTranslationFailedWithDetails(
+ () => base.Query_with_owned_entity_equality_operator(async),
+ CosmosStrings.NonCorrelatedSubqueriesNotSupported);
+
+ AssertSql();
+ }
[ConditionalTheory]
public override Task Navigation_rewrite_on_owned_collection(bool async)
@@ -164,11 +173,15 @@ FROM root c
""");
});
- // Address.Planet is a non-owned navigation, cross-document join
- public override Task Filter_owned_entity_chained_with_regular_entity_followed_by_projecting_owned_collection(bool async)
- => AssertTranslationFailedWithDetails(
+ public override async Task Filter_owned_entity_chained_with_regular_entity_followed_by_projecting_owned_collection(bool async)
+ {
+ // Address.Planet is a non-owned navigation, cross-document join
+ await AssertTranslationFailedWithDetails(
() => base.Filter_owned_entity_chained_with_regular_entity_followed_by_projecting_owned_collection(async),
- CosmosStrings.CrossDocumentJoinNotSupported);
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery("Planet", "OwnedPerson"));
+
+ AssertSql();
+ }
public override async Task Set_throws_for_owned_type(bool async)
{
@@ -177,65 +190,106 @@ public override async Task Set_throws_for_owned_type(bool async)
AssertSql();
}
- // Address.Planet is a non-owned navigation, cross-document join
- public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity(bool async)
- => AssertTranslationFailedWithDetails(
+ public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity(bool async)
+ {
+ // Address.Planet is a non-owned navigation, cross-document join
+ await AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity(async),
- CosmosStrings.CrossDocumentJoinNotSupported);
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery("Planet", "OwnedPerson"));
- // Address.Planet is a non-owned navigation, cross-document join
- public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_filter(bool async)
- => AssertTranslationFailedWithDetails(
- () => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity(async),
- CosmosStrings.CrossDocumentJoinNotSupported);
+ AssertSql();
+ }
- // Address.Planet is a non-owned navigation, cross-document join
- public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference(bool async)
- => AssertTranslationFailedWithDetails(
+ public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_filter(bool async)
+ {
+ // Address.Planet is a non-owned navigation, cross-document join
+ await AssertTranslationFailedWithDetails(
+ () => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_filter(async),
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery("Planet", "OwnedPerson"));
+
+ AssertSql();
+ }
+
+ public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference(bool async)
+ {
+ // Address.Planet is a non-owned navigation, cross-document join
+ await AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference(async),
- CosmosStrings.CrossDocumentJoinNotSupported);
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery("Planet", "OwnedPerson"));
- // Address.Planet is a non-owned navigation, cross-document join
- public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_and_scalar(bool async)
- => AssertTranslationFailedWithDetails(
+ AssertSql();
+ }
+
+ public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_and_scalar(bool async)
+ {
+ // Address.Planet is a non-owned navigation, cross-document join
+ await AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_and_scalar(async),
- CosmosStrings.CrossDocumentJoinNotSupported);
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery("Planet", "OwnedPerson"));
- // Address.Planet is a non-owned navigation, cross-document join
- public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection(bool async)
- => AssertTranslationFailedWithDetails(
+ AssertSql();
+ }
+
+ public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection(bool async)
+ {
+ // Address.Planet is a non-owned navigation, cross-document join
+ await AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection(async),
- CosmosStrings.CrossDocumentJoinNotSupported);
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery("Planet", "OwnedPerson"));
- // Address.Planet is a non-owned navigation, cross-document join
- public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection_count(bool async)
- => AssertTranslationFailedWithDetails(
+ AssertSql();
+ }
+
+ public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection_count(bool async)
+ {
+ // Address.Planet is a non-owned navigation, cross-document join
+ await AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection_count(async),
- CosmosStrings.CrossDocumentJoinNotSupported);
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery("Planet", "OwnedPerson"));
- // Address.Planet is a non-owned navigation, cross-document join
- public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_property(bool async)
- => AssertTranslationFailedWithDetails(
+ AssertSql();
+ }
+
+ public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_property(bool async)
+ {
+ // Address.Planet is a non-owned navigation, cross-document join
+ await AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_property(async),
- CosmosStrings.CrossDocumentJoinNotSupported);
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery("Planet", "OwnedPerson"));
- // Address.Planet is a non-owned navigation, cross-document join
- public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_in_predicate_and_projection(bool async)
- => AssertTranslationFailedWithDetails(
+ AssertSql();
+ }
+
+ public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_in_predicate_and_projection(
+ bool async)
+ {
+ // Address.Planet is a non-owned navigation, cross-document join
+ await AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_in_predicate_and_projection(async),
- CosmosStrings.CrossDocumentJoinNotSupported);
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery("Planet", "OwnedPerson"));
- // Address.Planet is a non-owned navigation, cross-document join
- public override Task Project_multiple_owned_navigations(bool async)
- => AssertTranslationFailedWithDetails(
+ AssertSql();
+ }
+
+ public override async Task Project_multiple_owned_navigations(bool async)
+ {
+ // Address.Planet is a non-owned navigation, cross-document join
+ await AssertTranslationFailedWithDetails(
() => base.Project_multiple_owned_navigations(async),
- CosmosStrings.CrossDocumentJoinNotSupported);
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery("Planet", "OwnedPerson"));
- // Address.Planet is a non-owned navigation, cross-document join
- public override Task Project_multiple_owned_navigations_with_expansion_on_owned_collections(bool async)
- => AssertTranslationFailedWithDetails(
+ AssertSql();
+ }
+
+ public override async Task Project_multiple_owned_navigations_with_expansion_on_owned_collections(bool async)
+ {
+ // Address.Planet is a non-owned navigation, cross-document join
+ await AssertTranslationFailedWithDetails(
() => base.Project_multiple_owned_navigations_with_expansion_on_owned_collections(async),
- CosmosStrings.CrossDocumentJoinNotSupported);
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery("Planet", "OwnedPerson"));
+
+ AssertSql();
+ }
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
@@ -275,17 +329,26 @@ JOIN o IN c["Orders"]
""");
});
- // Address.Planet is a non-owned navigation, cross-document join
- public override Task SelectMany_on_owned_reference_followed_by_regular_entity_and_collection(bool async)
- => AssertTranslationFailedWithDetails(
+ public override async Task SelectMany_on_owned_reference_followed_by_regular_entity_and_collection(bool async)
+ {
+ // Address.Planet is a non-owned navigation, cross-document join
+ await AssertTranslationFailedWithDetails(
() => base.SelectMany_on_owned_reference_followed_by_regular_entity_and_collection(async),
- CosmosStrings.CrossDocumentJoinNotSupported);
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Planet), nameof(OwnedPerson)));
+
+ AssertSql();
+ }
// Address.Planet is a non-owned navigation, cross-document join
- public override Task SelectMany_on_owned_reference_with_entity_in_between_ending_in_owned_collection(bool async)
- => AssertTranslationFailedWithDetails(
+ public override async Task SelectMany_on_owned_reference_with_entity_in_between_ending_in_owned_collection(bool async)
+ {
+ // Address.Planet is a non-owned navigation, cross-document join
+ await AssertTranslationFailedWithDetails(
() => base.SelectMany_on_owned_reference_with_entity_in_between_ending_in_owned_collection(async),
- CosmosStrings.CrossDocumentJoinNotSupported);
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Planet), nameof(OwnedPerson)));
+
+ AssertSql();
+ }
// Non-correlated queries not supported by Cosmos
public override Task Query_with_owned_entity_equality_method(bool async)
@@ -813,21 +876,34 @@ JOIN o IN c["Orders"]
""");
});
- // Non-correlated queries not supported by Cosmos
- public override Task Projecting_collection_correlated_with_keyless_entity_after_navigation_works_using_parent_identifiers(
+ public override async Task Projecting_collection_correlated_with_keyless_entity_after_navigation_works_using_parent_identifiers(
bool async)
- => AssertTranslationFailed(
- () => base.Projecting_collection_correlated_with_keyless_entity_after_navigation_works_using_parent_identifiers(async));
+ {
+ await AssertTranslationFailedWithDetails(
+ () => base.Projecting_collection_correlated_with_keyless_entity_after_navigation_works_using_parent_identifiers(async),
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(Barton), nameof(Fink)));
- // Non-correlated queries not supported by Cosmos
- public override Task Left_join_on_entity_with_owned_navigations(bool async)
- => AssertTranslationFailed(
- () => base.Left_join_on_entity_with_owned_navigations(async));
+ AssertSql();
- // Non-correlated queries not supported by Cosmos
- public override Task Left_join_on_entity_with_owned_navigations_complex(bool async)
- => AssertTranslationFailed(
- () => base.Left_join_on_entity_with_owned_navigations_complex(async));
+ }
+
+ public override async Task Left_join_on_entity_with_owned_navigations(bool async)
+ {
+ await AssertTranslationFailedWithDetails(
+ () => base.Left_join_on_entity_with_owned_navigations(async),
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(OwnedPerson), nameof(Planet)));
+
+ AssertSql();
+ }
+
+ public override async Task Left_join_on_entity_with_owned_navigations_complex(bool async)
+ {
+ await AssertTranslationFailedWithDetails(
+ () => base.Left_join_on_entity_with_owned_navigations_complex(async),
+ CosmosStrings.MultipleRootEntityTypesReferencedInQuery(nameof(OwnedPerson), nameof(Planet)));
+
+ AssertSql();
+ }
// TODO: GroupBy, #17313
public override Task GroupBy_aggregate_on_owned_navigation_in_aggregate_selector(bool async)
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs
index de0e74fb1e9..eb9bfb4ca36 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs
@@ -149,12 +149,7 @@ public override Task Inline_collection_Contains_with_one_value(bool async)
{
await base.Inline_collection_Contains_with_one_value(a);
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE (c["Id"] = 2)
-""");
+ AssertSql("ReadItem(None, PrimitiveCollectionsEntity|2)");
});
public override Task Inline_collection_Contains_with_two_values(bool async)
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs
new file mode 100644
index 00000000000..6ddb72653f8
--- /dev/null
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs
@@ -0,0 +1,757 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.EntityFrameworkCore.Cosmos.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Query;
+
+public class ReadItemPartitionKeyQueryTest : QueryTestBase
+{
+ public ReadItemPartitionKeyQueryTest(ReadItemPartitionKeyQueryFixture fixture, ITestOutputHelper testOutputHelper)
+ : base(fixture)
+ {
+ Fixture.TestSqlLoggerFactory.Clear();
+ Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
+ }
+
+ [ConditionalFact]
+ public async Task Predicate_with_hierarchical_partition_key()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set()
+ .Where(e => e.PartitionKey1 == "PK1" && e.PartitionKey2 == 1 && e.PartitionKey3));
+
+ AssertSql(
+ """
+SELECT c
+FROM root c
+""");
+ }
+
+ [ConditionalFact]
+ public async Task Predicate_with_single_partition_key()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().Where(e => e.PartitionKey == "PK1"));
+
+ AssertSql(
+ """
+SELECT c
+FROM root c
+""");
+ }
+
+ [ConditionalFact]
+ public async Task Predicate_with_partial_values_in_hierarchical_partition_key()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set()
+ .Where(e => e.PartitionKey1 == "PK1" && e.PartitionKey2 == 1));
+
+ AssertSql(
+ """
+SELECT c
+FROM root c
+WHERE ((c["PartitionKey1"] = "PK1") AND (c["PartitionKey2"] = 1))
+""");
+ }
+
+ [ConditionalFact] // #33960
+ public async Task Predicate_with_hierarchical_partition_key_and_additional_things_in_predicate()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set()
+ .Where(e => e.Payload.Contains("3") && e.PartitionKey1 == "PK1" && e.PartitionKey2 == 1 && e.PartitionKey3));
+
+ AssertSql(
+ """
+SELECT c
+FROM root c
+WHERE CONTAINS(c["Payload"], "3")
+""");
+ }
+
+ [ConditionalFact]
+ public async Task WithPartitionKey_with_hierarchical_partition_key()
+ {
+ var partitionKey2 = 1;
+
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().WithPartitionKey("PK1", 1, true),
+ ss => ss.Set()
+ .Where(e => e.PartitionKey1 == "PK1" && e.PartitionKey2 == partitionKey2 && e.PartitionKey3));
+
+ AssertSql(
+ """
+SELECT c
+FROM root c
+""");
+ }
+
+ [ConditionalFact]
+ public async Task WithPartitionKey_with_single_partition_key()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().WithPartitionKey("PK1"),
+ ss => ss.Set().Where(e => e.PartitionKey == "PK1"));
+
+ AssertSql(
+ """
+SELECT c
+FROM root c
+""");
+ }
+
+ [ConditionalFact]
+ public async Task WithPartitionKey_with_missing_value_in_hierarchical_partition_key()
+ {
+ var message = await Assert.ThrowsAsync(
+ () => AssertQuery(
+ async: true,
+ ss => ss.Set().WithPartitionKey("PK1", 1),
+ ss => ss.Set()
+ .Where(e => e.PartitionKey1 == "PK1" && e.PartitionKey2 == 1 && e.PartitionKey3)));
+
+ Assert.Equal(CosmosStrings.IncorrectPartitionKeyNumber(nameof(HierarchicalPartitionKeyEntity), 2, 3), message.Message);
+ }
+
+ [ConditionalFact]
+ public async Task Both_WithPartitionKey_and_predicate_comparisons_with_different_values()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().WithPartitionKey("PK1").Where(e => e.PartitionKey == "PK2"),
+ ss => ss.Set().Where(e => e.PartitionKey == "PK1").Where(e => e.PartitionKey == "PK2"),
+ assertEmpty: true);
+
+ AssertSql(
+ """
+SELECT c
+FROM root c
+WHERE (c["PartitionKey"] = "PK2")
+""");
+ }
+
+ [ConditionalFact]
+ public async Task Both_WithPartitionKey_and_predicate_comparisons_with_same_values()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set()
+ .WithPartitionKey("PK1")
+ .Where(e => e.PartitionKey == "PK1"));
+
+ AssertSql(
+ """
+SELECT c
+FROM root c
+WHERE (c["PartitionKey"] = "PK1")
+""");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_with_hierarchical_partition_key()
+ {
+ var partitionKey2 = 1;
+
+ await AssertQuery(
+ async: true,
+ ss => ss.Set()
+ .Where(e => e.Id == 1 && e.PartitionKey1 == "PK1" && e.PartitionKey2 == partitionKey2 && e.PartitionKey3));
+
+ AssertSql("""ReadItem(["PK1",1.0,true], HierarchicalPartitionKeyEntity|1)""");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_with_single_partition_key_constant()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().Where(e => e.Id == 1 && e.PartitionKey == "PK1"));
+
+ AssertSql("""ReadItem(["PK1"], SinglePartitionKeyEntity|1)""");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_with_single_partition_key_parameter()
+ {
+ var partitionKey = "PK1";
+
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().Where(e => e.Id == 1 && e.PartitionKey == partitionKey));
+
+ AssertSql("""ReadItem(["PK1"], SinglePartitionKeyEntity|1)""");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_with_SingleAsync()
+ {
+ var partitionKey = "PK1";
+
+ await AssertSingle(
+ async: true,
+ ss => ss.Set().Where(e => e.Id == 1 && e.PartitionKey == partitionKey));
+
+ AssertSql("""ReadItem(["PK1"], SinglePartitionKeyEntity|1)""");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_with_inverse_comparison()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().Where(e => 1 == e.Id && "PK1" == e.PartitionKey));
+
+ AssertSql("""ReadItem(["PK1"], SinglePartitionKeyEntity|1)""");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_with_EF_Property()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().Where(
+ e => EF.Property(e, nameof(SinglePartitionKeyEntity.Id)) == 1
+ && EF.Property(e, nameof(SinglePartitionKeyEntity.PartitionKey)) == "PK1"));
+
+ AssertSql("""ReadItem(["PK1"], SinglePartitionKeyEntity|1)""");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_with_WithPartitionKey()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().WithPartitionKey("PK1").Where(e => e.Id == 1),
+ ss => ss.Set().Where(e => e.PartitionKey == "PK1").Where(e => e.Id == 1));
+
+ AssertSql("""ReadItem(["PK1"], SinglePartitionKeyEntity|1)""");
+ }
+
+ [ConditionalFact]
+ public async Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem()
+ {
+ var partitionKey = "PK1";
+
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().Where(e => e.Id == 1 && e.Id == 2 && e.PartitionKey == partitionKey),
+ assertEmpty: true);
+
+ AssertSql(
+ """
+SELECT c
+FROM root c
+WHERE ((c["Id"] = 1) AND (c["Id"] = 2))
+""");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_with_no_partition_key()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().Where(e => e.Id == 1));
+
+ AssertSql("ReadItem(None, NoPartitionKeyEntity|1)");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_is_not_used_without_partition_key()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().Where(e => e.Id == 1));
+
+ AssertSql(
+ """
+SELECT c
+FROM root c
+WHERE (c["Id"] = 1)
+""");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_with_non_existent_id()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().Where(e => e.Id == 999 && e.PartitionKey == "PK1"),
+ assertEmpty: true);
+
+ AssertSql("""ReadItem(["PK1"], SinglePartitionKeyEntity|999)""");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_with_AsNoTracking()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().AsNoTracking().Where(e => e.Id == 1 && e.PartitionKey == "PK1"));
+
+ AssertSql("""ReadItem(["PK1"], SinglePartitionKeyEntity|1)""");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_with_AsNoTrackingWithIdentityResolution()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().AsNoTrackingWithIdentityResolution().Where(e => e.Id == 1 && e.PartitionKey == "PK1"));
+
+ AssertSql("""ReadItem(["PK1"], SinglePartitionKeyEntity|1)""");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_with_shared_container()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().Where(e => e.Id == 1 && e.PartitionKey == "PK1"));
+
+ AssertSql("""ReadItem(["PK1"], SharedContainerEntity1|1)""");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_for_base_type_with_shared_container()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().Where(e => e.Id == 4 && e.PartitionKey == "PK2"));
+
+ AssertSql(
+ """
+SELECT c
+FROM root c
+WHERE (c["Discriminator"] IN ("SharedContainerEntity2", "SharedContainerEntity2Child") AND (c["Id"] = 4))
+""");
+ }
+
+ [ConditionalFact]
+ public async Task ReadItem_for_child_type_with_shared_container()
+ {
+ await AssertQuery(
+ async: true,
+ ss => ss.Set().Where(e => e.Id == 5 && e.PartitionKey == "PK2"));
+
+ AssertSql("""ReadItem(["PK2"], SharedContainerEntity2Child|5)""");
+ }
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+
+ public class PartitionKeyContext(DbContextOptions options) : PoolableDbContext(options);
+
+ public class ReadItemPartitionKeyQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase
+ {
+ private PartitionKeyData? _expectedData;
+
+ protected override string StoreName
+ => "PartitionKeyQueryTest";
+
+ protected override ITestStoreFactory TestStoreFactory
+ => CosmosTestStoreFactory.Instance;
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
+ {
+ modelBuilder.Entity()
+ .ToContainer(nameof(HierarchicalPartitionKeyEntity))
+ .HasPartitionKey(h => new { h.PartitionKey1, h.PartitionKey2, h.PartitionKey3 });
+ modelBuilder.Entity()
+ .ToContainer(nameof(SinglePartitionKeyEntity))
+ .HasPartitionKey(h => h.PartitionKey);
+ modelBuilder.Entity()
+ .ToContainer(nameof(NoPartitionKeyEntity));
+ modelBuilder.Entity()
+ .ToContainer("SharedContainer")
+ .HasPartitionKey(e => e.PartitionKey);
+ modelBuilder.Entity()
+ .ToContainer("SharedContainer")
+ .HasPartitionKey(e => e.PartitionKey);
+ modelBuilder.Entity()
+ .HasPartitionKey(e => e.PartitionKey);
+ }
+
+ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
+ => base.AddOptions(builder.ConfigureWarnings(
+ w => w.Ignore(CosmosEventId.NoPartitionKeyDefined)));
+
+ public TestSqlLoggerFactory TestSqlLoggerFactory
+ => (TestSqlLoggerFactory)ListLoggerFactory;
+
+ public Func GetContextCreator()
+ => () => CreateContext();
+
+ protected override Task SeedAsync(PartitionKeyContext context)
+ {
+ context.AddRange(new PartitionKeyData().HierarchicalPartitionKeyEntities);
+ context.AddRange(new PartitionKeyData().SinglePartitionKeyEntities);
+ context.AddRange(new PartitionKeyData().NoPartitionKeyEntities);
+ context.AddRange(new PartitionKeyData().SharedContainerEntities1);
+ context.AddRange(new PartitionKeyData().SharedContainerEntities2);
+ context.AddRange(new PartitionKeyData().SharedContainerEntities2Children);
+ return context.SaveChangesAsync();
+ }
+
+ public ISetSource GetExpectedData()
+ => _expectedData ??= new PartitionKeyData();
+
+ public IReadOnlyDictionary EntitySorters { get; } = new Dictionary>
+ {
+ { typeof(HierarchicalPartitionKeyEntity), e => ((HierarchicalPartitionKeyEntity?)e)?.Id },
+ { typeof(SinglePartitionKeyEntity), e => ((SinglePartitionKeyEntity?)e)?.Id },
+ { typeof(NoPartitionKeyEntity), e => ((NoPartitionKeyEntity?)e)?.Id },
+ { typeof(SharedContainerEntity1), e => ((SharedContainerEntity1?)e)?.Id },
+ { typeof(SharedContainerEntity2), e => ((SharedContainerEntity2?)e)?.Id }
+ }.ToDictionary(e => e.Key, e => (object)e.Value);
+
+ public IReadOnlyDictionary EntityAsserters { get; } = new Dictionary>
+ {
+ {
+ typeof(HierarchicalPartitionKeyEntity), (e, a) =>
+ {
+ Assert.Equal(e == null, a == null);
+
+ if (a != null)
+ {
+ var ee = (HierarchicalPartitionKeyEntity)e!;
+ var aa = (HierarchicalPartitionKeyEntity)a;
+
+ Assert.Equal(ee.Id, aa.Id);
+ Assert.Equal(ee.PartitionKey1, aa.PartitionKey1);
+ Assert.Equal(ee.PartitionKey2, aa.PartitionKey2);
+ Assert.Equal(ee.PartitionKey3, aa.PartitionKey3);
+ Assert.Equal(ee.Payload, aa.Payload);
+ }
+ }
+ },
+ {
+ typeof(SinglePartitionKeyEntity), (e, a) =>
+ {
+ Assert.Equal(e == null, a == null);
+
+ if (a != null)
+ {
+ var ee = (SinglePartitionKeyEntity)e!;
+ var aa = (SinglePartitionKeyEntity)a;
+
+ Assert.Equal(ee.Id, aa.Id);
+ Assert.Equal(ee.PartitionKey, aa.PartitionKey);
+ Assert.Equal(ee.Payload, aa.Payload);
+ }
+ }
+ },
+ {
+ typeof(NoPartitionKeyEntity), (e, a) =>
+ {
+ Assert.Equal(e == null, a == null);
+
+ if (a != null)
+ {
+ var ee = (NoPartitionKeyEntity)e!;
+ var aa = (NoPartitionKeyEntity)a;
+
+ Assert.Equal(ee.Id, aa.Id);
+ Assert.Equal(ee.Payload, aa.Payload);
+ }
+ }
+ },
+ {
+ typeof(SharedContainerEntity1), (e, a) =>
+ {
+ Assert.Equal(e == null, a == null);
+
+ if (a != null)
+ {
+ var ee = (SharedContainerEntity1)e!;
+ var aa = (SharedContainerEntity1)a;
+
+ Assert.Equal(ee.Id, aa.Id);
+ Assert.Equal(ee.PartitionKey, aa.PartitionKey);
+ Assert.Equal(ee.Payload1, aa.Payload1);
+ }
+ }
+ },
+ {
+ typeof(SharedContainerEntity2), (e, a) =>
+ {
+ Assert.Equal(e == null, a == null);
+
+ if (a != null)
+ {
+ var ee = (SharedContainerEntity2)e!;
+ var aa = (SharedContainerEntity2)a;
+
+ Assert.Equal(ee.Id, aa.Id);
+ Assert.Equal(ee.PartitionKey, aa.PartitionKey);
+ Assert.Equal(ee.Payload2, aa.Payload2);
+ }
+ }
+ },
+ {
+ typeof(SharedContainerEntity2Child), (e, a) =>
+ {
+ Assert.Equal(e == null, a == null);
+
+ if (a != null)
+ {
+ var ee = (SharedContainerEntity2Child)e!;
+ var aa = (SharedContainerEntity2Child)a;
+
+ Assert.Equal(ee.Id, aa.Id);
+ Assert.Equal(ee.PartitionKey, aa.PartitionKey);
+ Assert.Equal(ee.Payload2, aa.Payload2);
+ Assert.Equal(ee.ChildPayload, aa.ChildPayload);
+ }
+ }
+ }
+ }.ToDictionary(e => e.Key, e => (object)e.Value);
+ }
+
+ public class HierarchicalPartitionKeyEntity
+ {
+ public int Id { get; set; }
+
+ public required string PartitionKey1 { get; set; }
+ public int PartitionKey2 { get; set; }
+ public bool PartitionKey3 { get; set; }
+
+ public required string Payload { get; set; }
+ }
+
+ public class SinglePartitionKeyEntity
+ {
+ public int Id { get; set; }
+
+ public required string PartitionKey { get; set; }
+
+ public required string Payload { get; set; }
+ }
+
+ public class NoPartitionKeyEntity
+ {
+ public int Id { get; set; }
+
+ public required string Payload { get; set; }
+ }
+
+ public class SharedContainerEntity1
+ {
+ public int Id { get; set; }
+ public required string PartitionKey { get; set; }
+ public required string Payload1 { get; set; }
+ }
+
+ public class SharedContainerEntity2
+ {
+ public int Id { get; set; }
+ public required string PartitionKey { get; set; }
+ public required string Payload2 { get; set; }
+ }
+
+ public class SharedContainerEntity2Child : SharedContainerEntity2
+ {
+ public required string ChildPayload { get; set; }
+ }
+
+ public class PartitionKeyData : ISetSource
+ {
+ public IReadOnlyList HierarchicalPartitionKeyEntities { get; }
+ public IReadOnlyList SinglePartitionKeyEntities { get; }
+ public IReadOnlyList NoPartitionKeyEntities { get; }
+ public IReadOnlyList SharedContainerEntities1 { get; }
+ public IReadOnlyList SharedContainerEntities2 { get; }
+ public IReadOnlyList SharedContainerEntities2Children { get; }
+
+ public PartitionKeyData(PartitionKeyContext? context = null)
+ {
+ HierarchicalPartitionKeyEntities = CreateHierarchicalPartitionKeyEntities();
+ SinglePartitionKeyEntities = CreateSinglePartitionKeyEntities();
+ NoPartitionKeyEntities = CreateNoPartitionKeyEntities();
+ SharedContainerEntities1 = CreateSharedContainerEntities1();
+ SharedContainerEntities2 = CreateSharedContainerEntities2();
+ SharedContainerEntities2Children = CreateSharedContainerEntities2Children();
+ }
+
+ public IQueryable Set()
+ where TEntity : class
+ {
+ if (typeof(TEntity) == typeof(HierarchicalPartitionKeyEntity))
+ {
+ return (IQueryable)HierarchicalPartitionKeyEntities.AsQueryable();
+ }
+
+ if (typeof(TEntity) == typeof(SinglePartitionKeyEntity))
+ {
+ return (IQueryable)SinglePartitionKeyEntities.AsQueryable();
+ }
+
+ if (typeof(TEntity) == typeof(NoPartitionKeyEntity))
+ {
+ return (IQueryable)NoPartitionKeyEntities.AsQueryable();
+ }
+
+ if (typeof(TEntity) == typeof(SharedContainerEntity1))
+ {
+ return (IQueryable)SharedContainerEntities1.AsQueryable();
+ }
+
+ if (typeof(TEntity) == typeof(SharedContainerEntity2))
+ {
+ return (IQueryable)SharedContainerEntities2.AsQueryable();
+ }
+
+ if (typeof(TEntity) == typeof(SharedContainerEntity2Child))
+ {
+ return (IQueryable)SharedContainerEntities2Children.AsQueryable();
+ }
+
+ throw new InvalidOperationException("Invalid entity type: " + typeof(TEntity));
+ }
+
+ private static IReadOnlyList CreateHierarchicalPartitionKeyEntities()
+ => new List
+ {
+ new()
+ {
+ Id = 1,
+ PartitionKey1 = "PK1",
+ PartitionKey2 = 1,
+ PartitionKey3 = true,
+ Payload = "Payload1"
+ },
+ new()
+ {
+ Id = 1,
+ PartitionKey1 = "PK2",
+ PartitionKey2 = 2,
+ PartitionKey3 = false,
+ Payload = "Payload2"
+ },
+ new()
+ {
+ Id = 2,
+ PartitionKey1 = "PK1",
+ PartitionKey2 = 1,
+ PartitionKey3 = true,
+ Payload = "Payload3"
+ },
+ new()
+ {
+ Id = 2,
+ PartitionKey1 = "PK2",
+ PartitionKey2 = 2,
+ PartitionKey3 = false,
+ Payload = "Payload4"
+ }
+ };
+
+ private static IReadOnlyList CreateSinglePartitionKeyEntities()
+ => new List
+ {
+ new()
+ {
+ Id = 1,
+ PartitionKey = "PK1",
+ Payload = "Payload1"
+ },
+ new()
+ {
+ Id = 1,
+ PartitionKey = "PK2",
+ Payload = "Payload2"
+ },
+ new()
+ {
+ Id = 2,
+ PartitionKey = "PK1",
+ Payload = "Payload3"
+ },
+ new()
+ {
+ Id = 2,
+ PartitionKey = "PK2",
+ Payload = "Payload4"
+ }
+ };
+
+ private static IReadOnlyList CreateNoPartitionKeyEntities()
+ => new List
+ {
+ new() { Id = 1, Payload = "Payload1" },
+ new() { Id = 2, Payload = "Payload2" }
+ };
+
+ private static IReadOnlyList CreateSharedContainerEntities1()
+ => new List
+ {
+ new()
+ {
+ Id = 1,
+ PartitionKey = "PK1",
+ Payload1 = "Payload1"
+ },
+ new()
+ {
+ Id = 1,
+ PartitionKey = "PK2",
+ Payload1 = "Payload2"
+ },
+ new()
+ {
+ Id = 2,
+ PartitionKey = "PK1",
+ Payload1 = "Payload3"
+ },
+ new()
+ {
+ Id = 2,
+ PartitionKey = "PK2",
+ Payload1 = "Payload4"
+ }
+ };
+
+ private static IReadOnlyList CreateSharedContainerEntities2()
+ => new List
+ {
+ new()
+ {
+ Id = 4,
+ PartitionKey = "PK1",
+ Payload2 = "Payload4"
+ },
+ new()
+ {
+ Id = 4,
+ PartitionKey = "PK2",
+ Payload2 = "Payload5"
+ }
+ };
+
+ private static IReadOnlyList CreateSharedContainerEntities2Children()
+ => new List
+ {
+ new()
+ {
+ Id = 5,
+ PartitionKey = "PK1",
+ Payload2 = "Payload6",
+ ChildPayload = "Child1"
+ },
+ new()
+ {
+ Id = 5,
+ PartitionKey = "PK2",
+ Payload2 = "Payload7",
+ ChildPayload = "Child2"
+ }
+ };
+ }
+}
diff --git a/test/EFCore.Cosmos.FunctionalTests/ReadItemTest.cs b/test/EFCore.Cosmos.FunctionalTests/ReadItemTest.cs
deleted file mode 100644
index d4f4e682f38..00000000000
--- a/test/EFCore.Cosmos.FunctionalTests/ReadItemTest.cs
+++ /dev/null
@@ -1,1277 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Microsoft.EntityFrameworkCore;
-
-public class ReadItemTest : IClassFixture
-{
- public ReadItemTest(ReadItemFixture fixture)
- {
- Fixture = fixture;
- fixture.TestSqlLoggerFactory.Clear();
- }
-
- protected ReadItemFixture Fixture { get; }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task FirstOrDefault_int_key_constant_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).FirstOrDefaultAsync(e => e.Id == 77);
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = 77))
-OFFSET 0 LIMIT 1
-""");
-
- ValidateIntKeyValues(entity!);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task FirstOrDefault_int_key_variable_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).FirstOrDefaultAsync(e => e.Id == val);
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = @__val_0))
-OFFSET 0 LIMIT 1
-""");
-
- ValidateIntKeyValues(entity!);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task FirstOrDefault_int_key_constant_value_first_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).FirstOrDefaultAsync(e => 77 == e.Id);
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (77 = c["Id"]))
-OFFSET 0 LIMIT 1
-""");
-
- ValidateIntKeyValues(entity!);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task FirstOrDefault_int_key_variable_value_first_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).FirstOrDefaultAsync(e => val == e.Id);
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (@__val_0 = c["Id"]))
-OFFSET 0 LIMIT 1
-""");
-
- ValidateIntKeyValues(entity!);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task First_int_key_constant_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).FirstAsync(e => e.Id == 77);
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = 77))
-OFFSET 0 LIMIT 1
-""");
-
- AssertSql(
- );
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task First_int_key_variable_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).FirstAsync(e => e.Id == val);
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = @__val_0))
-OFFSET 0 LIMIT 1
-""");
-
- ValidateIntKeyValues(entity);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task First_int_key_constant_value_first_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).FirstAsync(e => 77 == e.Id);
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (77 = c["Id"]))
-OFFSET 0 LIMIT 1
-""");
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task First_int_key_variable_value_first_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).FirstAsync(e => val == e.Id);
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (@__val_0 = c["Id"]))
-OFFSET 0 LIMIT 1
-""");
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task SingleOrDefault_int_key_constant_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).SingleOrDefaultAsync(e => e.Id == 77);
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = 77))
-OFFSET 0 LIMIT 2
-""");
-
- AssertSql(
- );
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task SingleOrDefault_int_key_variable_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).SingleOrDefaultAsync(e => e.Id == val);
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = @__val_0))
-OFFSET 0 LIMIT 2
-""");
-
- ValidateIntKeyValues(entity!);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task SingleOrDefault_int_key_constant_value_first_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).SingleOrDefaultAsync(e => 77 == e.Id);
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (77 = c["Id"]))
-OFFSET 0 LIMIT 2
-""");
-
- ValidateIntKeyValues(entity!);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task SingleOrDefault_int_key_variable_value_first_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).SingleOrDefaultAsync(e => val == e.Id);
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (@__val_0 = c["Id"]))
-OFFSET 0 LIMIT 2
-""");
-
- ValidateIntKeyValues(entity!);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task Single_int_key_constant_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).SingleAsync(e => e.Id == 77);
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = 77))
-OFFSET 0 LIMIT 2
-""");
-
- ValidateIntKeyValues(entity);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task Single_int_key_variable_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).SingleAsync(e => e.Id == val);
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = @__val_0))
-OFFSET 0 LIMIT 2
-""");
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task Single_int_key_constant_value_first_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).SingleAsync(e => 77 == e.Id);
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (77 = c["Id"]))
-OFFSET 0 LIMIT 2
-""");
-
- ValidateIntKeyValues(entity);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task Single_int_key_variable_value_first_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior).SingleAsync(e => val == e.Id);
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (@__val_0 = c["Id"]))
-OFFSET 0 LIMIT 2
-""");
-
- ValidateIntKeyValues(entity);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task FirstOrDefault_int_key_constant_with_EF_Property_is_translated_to_ReadItem(
- QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .FirstOrDefaultAsync(e => EF.Property(e, nameof(IntKey.Id)) == 77);
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = 77))
-OFFSET 0 LIMIT 1
-""");
-
- ValidateIntKeyValues(entity!);
- }
-
- [ConditionalTheory]
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task FirstOrDefault_int_key_variable_with_EF_Property_is_translated_to_ReadItem(
- QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .FirstOrDefaultAsync(e => EF.Property(e, nameof(IntKey.Id)) == val);
-
- AssertSql(
- """
-ReadItem(None, IntKey|77)
-""");
-
- ValidateIntKeyValues(entity!);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task FirstOrDefault_int_key_constant_value_first_with_EF_Property_is_translated_to_ReadItem(
- QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .FirstOrDefaultAsync(e => 77 == EF.Property(e, nameof(IntKey.Id)));
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (77 = c["Id"]))
-OFFSET 0 LIMIT 1
-""");
-
- ValidateIntKeyValues(entity!);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task FirstOrDefault_int_key_variable_value_first_with_EF_Property_is_translated_to_ReadItem(
- QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .FirstOrDefaultAsync(e => val == EF.Property(e, nameof(IntKey.Id)));
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (@__val_0 = c["Id"]))
-OFFSET 0 LIMIT 1
-""");
-
- ValidateIntKeyValues(entity!);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task First_int_key_constant_with_EF_Property_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .FirstAsync(e => EF.Property(e, nameof(IntKey.Id)) == 77);
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = 77))
-OFFSET 0 LIMIT 1
-""");
-
- AssertSql(
- );
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task First_int_key_variable_with_EF_Property_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .FirstAsync(e => EF.Property(e, nameof(IntKey.Id)) == val);
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = @__val_0))
-OFFSET 0 LIMIT 1
-""");
-
- ValidateIntKeyValues(entity);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task First_int_key_constant_value_first_with_EF_Property_is_translated_to_ReadItem(
- QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .FirstAsync(e => 77 == EF.Property(e, nameof(IntKey.Id)));
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (77 = c["Id"]))
-OFFSET 0 LIMIT 1
-""");
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task First_int_key_variable_value_first_with_EF_Property_is_translated_to_ReadItem(
- QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .FirstAsync(e => val == EF.Property(e, nameof(IntKey.Id)));
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (@__val_0 = c["Id"]))
-OFFSET 0 LIMIT 1
-""");
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task SingleOrDefault_int_key_constant_with_EF_Property_is_translated_to_ReadItem(
- QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .SingleOrDefaultAsync(e => EF.Property(e, nameof(IntKey.Id)) == 77);
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = 77))
-OFFSET 0 LIMIT 2
-""");
-
- AssertSql(
- );
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task SingleOrDefault_int_key_variable_with_EF_Property_is_translated_to_ReadItem(
- QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .SingleOrDefaultAsync(e => EF.Property(e, nameof(IntKey.Id)) == val);
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = @__val_0))
-OFFSET 0 LIMIT 2
-""");
-
- ValidateIntKeyValues(entity!);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task SingleOrDefault_int_key_constant_value_first_with_EF_Property_is_translated_to_ReadItem(
- QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .SingleOrDefaultAsync(e => 77 == EF.Property(e, nameof(IntKey.Id)));
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (77 = c["Id"]))
-OFFSET 0 LIMIT 2
-""");
-
- ValidateIntKeyValues(entity!);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task SingleOrDefault_int_key_variable_value_first_with_EF_Property_is_translated_to_ReadItem(
- QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .SingleOrDefaultAsync(e => val == EF.Property(e, nameof(IntKey.Id)));
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (@__val_0 = c["Id"]))
-OFFSET 0 LIMIT 2
-""");
-
- ValidateIntKeyValues(entity!);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task Single_int_key_constant_with_EF_Property_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .SingleAsync(e => EF.Property(e, nameof(IntKey.Id)) == 77);
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = 77))
-OFFSET 0 LIMIT 2
-""");
-
- ValidateIntKeyValues(entity);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task Single_int_key_variable_with_EF_Property_is_translated_to_ReadItem(QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .SingleAsync(e => EF.Property(e, nameof(IntKey.Id)) == val);
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (c["Id"] = @__val_0))
-OFFSET 0 LIMIT 2
-""");
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task Single_int_key_constant_value_first_with_EF_Property_is_translated_to_ReadItem(
- QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .SingleAsync(e => 77 == EF.Property(e, nameof(IntKey.Id)));
-
- AssertSql(
- """
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (77 = c["Id"]))
-OFFSET 0 LIMIT 2
-""");
-
- ValidateIntKeyValues(entity);
- }
-
- [ConditionalTheory] // Issue #20693
- [InlineData(QueryTrackingBehavior.TrackAll)]
- [InlineData(QueryTrackingBehavior.NoTracking)]
- [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
- public virtual async Task Single_int_key_variable_value_first_with_EF_Property_is_translated_to_ReadItem(
- QueryTrackingBehavior trackingBehavior)
- {
- using var context = CreateContext();
-
- var val = 77;
- var entity = await ApplyTrackingBehavior(context.Set(), trackingBehavior)
- .SingleAsync(e => val == EF.Property(e, nameof(IntKey.Id)));
-
- AssertSql(
- """
-@__val_0='77'
-
-SELECT c
-FROM root c
-WHERE ((c["Discriminator"] = "IntKey") AND (@__val_0 = c["Id"]))
-OFFSET 0 LIMIT 2
-""");
-
- ValidateIntKeyValues(entity);
- }
-
- private static void ValidateIntKeyValues(IntKey entity)
- {
- Assert.Equal("Smokey", entity.Foo);
- Assert.Equal(7, entity.OwnedReference.Prop);
- Assert.Equal(2, entity.OwnedCollection.Count);
- Assert.Contains(71, entity.OwnedCollection.Select(e => e.Prop));
- Assert.Contains(72, entity.OwnedCollection.Select(e => e.Prop));
- Assert.Equal("7", entity.OwnedReference.NestedOwned.Prop);
- Assert.Equal(2, entity.OwnedReference.NestedOwnedCollection.Count);
- Assert.Contains("71", entity.OwnedReference.NestedOwnedCollection.Select(e => e.Prop));
- Assert.Contains("72", entity.OwnedReference.NestedOwnedCollection.Select(e => e.Prop));
- }
-
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Returns_null_for_int_key_not_in_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Null(await Finder.FindAsync(cancellationType, context, [99]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_nullable_int_key_tracked_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // var entity = context.Attach(
- // new NullableIntKey { Id = 88 }).Entity;
- //
- // Assert.Same(entity, await Finder.FindAsync(cancellationType, context, [88]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_nullable_int_key_from_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Equal("Smokey", (await Finder.FindAsync(cancellationType, context, [77])).Foo);
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Returns_null_for_nullable_int_key_not_in_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Null(await Finder.FindAsync(cancellationType, context, [99]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_string_key_tracked_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // var entity = context.Attach(
- // new StringKey { Id = "Rabbit" }).Entity;
- //
- // Assert.Same(entity, await Finder.FindAsync(cancellationType, context, ["Rabbit"]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_string_key_from_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Equal("Alice", (await Finder.FindAsync(cancellationType, context, ["Cat"])).Foo);
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Returns_null_for_string_key_not_in_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Null(await Finder.FindAsync(cancellationType, context, ["Fox"]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_composite_key_tracked_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // var entity = context.Attach(
- // new CompositeKey { Id1 = 88, Id2 = "Rabbit" }).Entity;
- //
- // Assert.Same(entity, await Finder.FindAsync(cancellationType, context, [88, "Rabbit"]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_composite_key_from_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Equal("Olive", (await Finder.FindAsync(cancellationType, context, [77, "Dog"])).Foo);
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Returns_null_for_composite_key_not_in_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Null(await Finder.FindAsync(cancellationType, context, [77, "Fox"]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_base_type_tracked_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // var entity = context.Attach(
- // new BaseType { Id = 88 }).Entity;
- //
- // Assert.Same(entity, await Finder.FindAsync(cancellationType, context, [88]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_base_type_from_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Equal("Baxter", (await Finder.FindAsync(cancellationType, context, [77])).Foo);
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Returns_null_for_base_type_not_in_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Null(await Finder.FindAsync(cancellationType, context, [99]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_derived_type_tracked_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // var entity = context.Attach(
- // new DerivedType { Id = 88 }).Entity;
- //
- // Assert.Same(entity, await Finder.FindAsync(cancellationType, context, [88]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_derived_type_from_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // var derivedType = await Finder.FindAsync(cancellationType, context, [78]);
- // Assert.Equal("Strawberry", derivedType.Foo);
- // Assert.Equal("Cheesecake", derivedType.Boo);
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Returns_null_for_derived_type_not_in_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Null(await Finder.FindAsync(cancellationType, context, [99]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_base_type_using_derived_set_tracked_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // context.Attach(
- // new BaseType { Id = 88 });
- //
- // Assert.Null(await Finder.FindAsync(cancellationType, context, [88]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_base_type_using_derived_set_from_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Null(await Finder.FindAsync(cancellationType, context, [77]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_derived_type_using_base_set_tracked_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // var entity = context.Attach(
- // new DerivedType { Id = 88 }).Entity;
- //
- // Assert.Same(entity, await Finder.FindAsync(cancellationType, context, [88]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_derived_using_base_set_type_from_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // var derivedType = await Finder.FindAsync(cancellationType, context, [78]);
- // Assert.Equal("Strawberry", derivedType.Foo);
- // Assert.Equal("Cheesecake", ((DerivedType)derivedType).Boo);
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_shadow_key_tracked_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // var entry = context.Entry(new ShadowKey());
- // entry.Property("Id").CurrentValue = 88;
- // entry.State = EntityState.Unchanged;
- //
- // Assert.Same(entry.Entity, await Finder.FindAsync(cancellationType, context, [88]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Find_shadow_key_from_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Equal("Clippy", (await Finder.FindAsync(cancellationType, context, [77])).Foo);
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Returns_null_for_shadow_key_not_in_store_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Null(await Finder.FindAsync(cancellationType, context, [99]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Returns_null_for_null_key_values_array_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Null(await Finder.FindAsync(cancellationType, context, null));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Returns_null_for_null_key_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Null(await Finder.FindAsync(cancellationType, context, [null]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Returns_null_for_null_in_composite_key_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Null(await Finder.FindAsync(cancellationType, context, [77, null]));
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Throws_for_multiple_values_passed_for_simple_key_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Equal(
- // CoreStrings.FindNotCompositeKey("IntKey", cancellationType == CancellationType.Wrong ? 3 : 2),
- // (await Assert.ThrowsAsync(
- // () => Finder.FindAsync(cancellationType, context, [77, 88]).AsTask())).Message);
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Throws_for_wrong_number_of_values_for_composite_key_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Equal(
- // cancellationType == CancellationType.Wrong
- // ? CoreStrings.FindValueTypeMismatch(1, "CompositeKey", "CancellationToken", "string")
- // : CoreStrings.FindValueCountMismatch("CompositeKey", 2, 1),
- // (await Assert.ThrowsAsync(
- // () => Finder.FindAsync(cancellationType, context, [77]).AsTask())).Message);
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Throws_for_bad_type_for_simple_key_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Equal(
- // CoreStrings.FindValueTypeMismatch(0, "IntKey", "string", "int"),
- // (await Assert.ThrowsAsync(
- // () => Finder.FindAsync(cancellationType, context, ["77"]).AsTask())).Message);
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Throws_for_bad_type_for_composite_key_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Equal(
- // CoreStrings.FindValueTypeMismatch(1, "CompositeKey", "int", "string"),
- // (await Assert.ThrowsAsync(
- // () => Finder.FindAsync(cancellationType, context, [77, 78]).AsTask())).Message);
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Throws_for_bad_entity_type_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- // Assert.Equal(
- // CoreStrings.InvalidSetType(nameof(Random)),
- // (await Assert.ThrowsAsync(
- // () => Finder.FindAsync(cancellationType, context, [77]).AsTask())).Message);
- // }
- //
- // [ConditionalTheory]
- // [InlineData((int)CancellationType.Right)]
- // [InlineData((int)CancellationType.Wrong)]
- // [InlineData((int)CancellationType.None)]
- // public virtual async Task Throws_for_bad_entity_type_with_different_namespace_async(CancellationType cancellationType)
- // {
- // using var context = CreateContext();
- //
- // Assert.Equal(
- // CoreStrings.InvalidSetSameTypeWithDifferentNamespace(
- // typeof(DifferentNamespace.ShadowKey).DisplayName(), typeof(ShadowKey).DisplayName()),
- // (await Assert.ThrowsAsync(
- // () => Finder.FindAsync(cancellationType, context, [77]).AsTask()))
- // .Message);
- // }
-
- public enum CancellationType
- {
- Right,
- Wrong,
- None
- }
-
- protected class BaseType
- {
- [DatabaseGenerated(DatabaseGeneratedOption.None)]
- public int Id { get; set; }
-
- public string? Foo { get; set; }
- }
-
- protected class DerivedType : BaseType
- {
- public string? Boo { get; set; }
- }
-
- protected class IntKey
- {
- [DatabaseGenerated(DatabaseGeneratedOption.None)]
- public int Id { get; set; }
-
- public string? Foo { get; set; }
-
- public Owned1 OwnedReference { get; set; } = null!;
- public List OwnedCollection { get; set; } = null!;
- }
-
- protected class NullableIntKey
- {
- [DatabaseGenerated(DatabaseGeneratedOption.None)]
- public int? Id { get; set; }
-
- public string? Foo { get; set; }
- }
-
- protected class StringKey
- {
- public string Id { get; set; } = null!;
-
- public string? Foo { get; set; }
- }
-
- protected class CompositeKey
- {
- public int Id1 { get; set; }
- public string Id2 { get; set; } = null!;
- public string? Foo { get; set; }
- }
-
- protected class ShadowKey
- {
- public string? Foo { get; set; }
- }
-
- [Owned]
- protected class Owned1
- {
- public int Prop { get; set; }
- public Owned2 NestedOwned { get; set; } = null!;
- public List NestedOwnedCollection { get; set; } = null!;
- }
-
- [Owned]
- protected class Owned2
- {
- public string Prop { get; set; } = null!;
- }
-
- protected DbContext CreateContext()
- => Fixture.CreateContext();
-
- private void AssertSql(params string[] expected)
- => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
-
- private static IQueryable ApplyTrackingBehavior(IQueryable query, QueryTrackingBehavior trackingBehavior)
- {
- query = trackingBehavior switch
- {
- QueryTrackingBehavior.TrackAll => query,
- QueryTrackingBehavior.NoTracking => query.AsNoTracking(),
- QueryTrackingBehavior.NoTrackingWithIdentityResolution => query.AsNoTrackingWithIdentityResolution(),
- _ => throw new ArgumentOutOfRangeException(nameof(trackingBehavior), trackingBehavior, null)
- };
- return query;
- }
-
- public class ReadItemFixture : SharedStoreFixtureBase
- {
- protected override string StoreName
- => "ReadItemTest";
-
- protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
- {
- modelBuilder.Entity();
- modelBuilder.Entity();
- modelBuilder.Entity();
- modelBuilder.Entity().HasKey(
- e => new { e.Id1, e.Id2 });
- modelBuilder.Entity();
- modelBuilder.Entity();
- modelBuilder.Entity().Property(typeof(int), "Id").ValueGeneratedNever();
- }
-
- protected override Task SeedAsync(PoolableDbContext context)
- {
- context.AddRange(
- new IntKey
- {
- Id = 77,
- Foo = "Smokey",
- OwnedReference = new()
- {
- Prop = 7,
- NestedOwned = new() { Prop = "7" },
- NestedOwnedCollection = new() { new() { Prop = "71" }, new() { Prop = "72" } }
- },
- OwnedCollection = new() { new() { Prop = 71 }, new() { Prop = 72 } }
- },
- new NullableIntKey { Id = 77, Foo = "Smokey" },
- new StringKey { Id = "Cat", Foo = "Alice" },
- new CompositeKey
- {
- Id1 = 77,
- Id2 = "Dog",
- Foo = "Olive"
- },
- new BaseType { Id = 77, Foo = "Baxter" },
- new DerivedType
- {
- Id = 78,
- Foo = "Strawberry",
- Boo = "Cheesecake"
- });
-
- var entry = context.Entry(
- new ShadowKey { Foo = "Clippy" });
- entry.Property("Id").CurrentValue = 77;
- entry.State = EntityState.Added;
-
- return context.SaveChangesAsync();
- }
-
- public TestSqlLoggerFactory TestSqlLoggerFactory
- => (TestSqlLoggerFactory)ListLoggerFactory;
-
- public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
- => base.AddOptions(builder).ConfigureWarnings(w => w.Ignore(CosmosEventId.NoPartitionKeyDefined));
-
- protected override ITestStoreFactory TestStoreFactory
- => CosmosTestStoreFactory.Instance;
- }
-}
diff --git a/test/EFCore.Cosmos.FunctionalTests/ReloadTest.cs b/test/EFCore.Cosmos.FunctionalTests/ReloadTest.cs
index 5965397444f..9efd9d08404 100644
--- a/test/EFCore.Cosmos.FunctionalTests/ReloadTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/ReloadTest.cs
@@ -5,8 +5,6 @@
namespace Microsoft.EntityFrameworkCore;
-#nullable disable
-
public class ReloadTest : IClassFixture
{
public static IEnumerable IsAsyncData = [[false], [true]];
@@ -30,8 +28,7 @@ public async Task Entity_reference_can_be_reloaded()
{
using var context = CreateContext();
- var entry = await context.AddAsync(new Item { Id = 1337 });
-
+ var entry = await context.AddAsync(new Item { Id = 1337, PartitionKey = "Foo" });
await context.SaveChangesAsync();
var itemJson = entry.Property("__jObject").CurrentValue;
@@ -39,6 +36,23 @@ public async Task Entity_reference_can_be_reloaded()
await entry.ReloadAsync();
+ AssertSql(
+ """
+@__p_0='1337'
+
+SELECT VALUE
+{
+ "Id" : c["Id"],
+ "PartitionKey" : c["PartitionKey"],
+ "Discriminator" : c["Discriminator"],
+ "id0" : c["id"],
+ "" : c
+}
+FROM root c
+WHERE (c["Id"] = @__p_0)
+OFFSET 0 LIMIT 1
+""");
+
itemJson = entry.Property("__jObject").CurrentValue;
Assert.Null(itemJson["unmapped"]);
}
@@ -64,7 +78,7 @@ public TestSqlLoggerFactory TestSqlLoggerFactory
public class ReloadTestContext(DbContextOptions dbContextOptions) : DbContext(dbContextOptions)
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
- => modelBuilder.Entity- (b => b.HasPartitionKey(e => e.Id));
+ => modelBuilder.Entity
- (b => b.HasPartitionKey(e => e.PartitionKey));
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
@@ -74,11 +88,12 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
optionsBuilder.ConfigureWarnings(w => w.Log(CoreEventId.FirstWithoutOrderByAndFilterWarning));
}
- public DbSet
- Items { get; set; }
+ public DbSet
- Items { get; set; } = null!;
}
public class Item
{
public int Id { get; set; }
+ public required string PartitionKey { get; set; }
}
}
diff --git a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs
index a50dd017052..f8865c6b9ef 100644
--- a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs
@@ -2065,9 +2065,7 @@ public virtual Task Constant_array_Contains_AndAlso_another_Contains_gets_combin
[MemberData(nameof(IsAsyncData))]
public virtual Task Multiple_AndAlso_on_same_column_converted_to_in_using_parameters(bool async)
{
- var prm1 = "ALFKI";
- var prm2 = "ANATR";
- var prm3 = "ANTON";
+ var (prm1, prm2, prm3) = ("ALFKI", "ANATR", "ANTON");
return AssertQuery(
async,
@@ -2078,8 +2076,7 @@ public virtual Task Multiple_AndAlso_on_same_column_converted_to_in_using_parame
[MemberData(nameof(IsAsyncData))]
public virtual Task Array_of_parameters_Contains_OrElse_comparison_with_constant_gets_combined_to_one_in(bool async)
{
- var prm1 = "ALFKI";
- var prm2 = "ANATR";
+ var (prm1, prm2) = ("ALFKI", "ANATR");
return AssertQuery(
async,
diff --git a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
index d5ca1613162..6a4d5cb42bd 100644
--- a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
@@ -1209,7 +1209,7 @@ public virtual Task Nested_contains_with_arrays_and_no_inferred_type_mapping(boo
public abstract class PrimitiveCollectionsQueryFixtureBase : SharedStoreFixtureBase, IQueryFixtureBase
{
- private PrimitiveArrayData? _expectedData;
+ private PrimitiveCollectionsData? _expectedData;
protected override string StoreName
=> "PrimitiveCollectionsTest";
@@ -1222,12 +1222,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
protected override Task SeedAsync(PrimitiveCollectionsContext context)
{
- context.AddRange(new PrimitiveArrayData().PrimitiveArrayEntities);
+ context.AddRange(new PrimitiveCollectionsData().PrimitiveArrayEntities);
return context.SaveChangesAsync();
}
public virtual ISetSource GetExpectedData()
- => _expectedData ??= new PrimitiveArrayData();
+ => _expectedData ??= new PrimitiveCollectionsData();
public IReadOnlyDictionary EntitySorters { get; } = new Dictionary>
{
@@ -1283,11 +1283,11 @@ public class PrimitiveCollectionsEntity
public enum MyEnum { Value1, Value2, Value3, Value4 }
- public class PrimitiveArrayData : ISetSource
+ public class PrimitiveCollectionsData : ISetSource
{
public IReadOnlyList PrimitiveArrayEntities { get; }
- public PrimitiveArrayData(PrimitiveCollectionsContext? context = null)
+ public PrimitiveCollectionsData(PrimitiveCollectionsContext? context = null)
{
PrimitiveArrayEntities = CreatePrimitiveArrayEntities();
}