diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs
index 73756688170..3c761592e23 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs
@@ -22,6 +22,7 @@ public class CosmosQueryableMethodTranslatingExpressionVisitor : QueryableMethod
private readonly CosmosSqlTranslatingExpressionVisitor _sqlTranslator;
private readonly CosmosProjectionBindingExpressionVisitor _projectionBindingExpressionVisitor;
private readonly bool _subquery;
+ private ReadItemInfo? _readItemExpression;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -91,56 +92,85 @@ protected CosmosQueryableMethodTranslatingExpressionVisitor(
[return: NotNullIfNotNull(nameof(expression))]
public override Expression? Visit(Expression? expression)
{
- if (expression is MethodCallExpression
+ if (_queryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.TrackAll // Issue #33893
+ && expression is MethodCallExpression
{
Method: { Name: nameof(Queryable.FirstOrDefault), IsGenericMethod: true },
- Arguments:
- [
- MethodCallExpression
- {
- Method: { Name: nameof(Queryable.Where), IsGenericMethod: true },
- Arguments:
- [
- EntityQueryRootExpression { EntityType: var entityType },
- UnaryExpression { Operand: LambdaExpression lambdaExpression, NodeType: ExpressionType.Quote }
- ]
- } whereMethodCall
- ]
- } firstOrDefaultMethodCall
- && firstOrDefaultMethodCall.Method.GetGenericMethodDefinition() == QueryableMethods.FirstOrDefaultWithoutPredicate
- && whereMethodCall.Method.GetGenericMethodDefinition() == QueryableMethods.Where)
+ Arguments: [MethodCallExpression innerMethodCall]
+ })
{
- var queryProperties = new List();
- var parameterNames = new List();
-
- if (ExtractPartitionKeyFromPredicate(entityType, lambdaExpression.Body, queryProperties, parameterNames))
+ var clrType = innerMethodCall.Type.TryGetSequenceType() ?? typeof(object);
+ if (innerMethodCall is
+ {
+ Method: { Name: nameof(Queryable.Select), IsGenericMethod: true },
+ Arguments:
+ [
+ MethodCallExpression innerInnerMethodCall,
+ UnaryExpression { NodeType: ExpressionType.Quote } unaryExpression
+ ]
+ })
{
- var entityTypePrimaryKeyProperties = entityType.FindPrimaryKey()!.Properties;
- var idProperty = entityType.GetProperties()
- .First(p => p.GetJsonPropertyName() == StoreKeyConvention.IdPropertyJsonName);
- var partitionKeyProperties = entityType.GetPartitionKeyProperties();
-
- if (entityTypePrimaryKeyProperties.SequenceEqual(queryProperties)
- && (!partitionKeyProperties.Any()
- || partitionKeyProperties.All(p => entityTypePrimaryKeyProperties.Contains(p)))
- && (idProperty.GetValueGeneratorFactory() != null
- || entityTypePrimaryKeyProperties.Contains(idProperty)))
+ if (unaryExpression.Operand is LambdaExpression)
{
- var propertyParameterList = queryProperties.Zip(
- parameterNames,
- (property, parameter) => (property, parameter))
- .ToDictionary(tuple => tuple.property, tuple => tuple.parameter);
+ innerMethodCall = innerInnerMethodCall;
+ }
+ }
- var readItemExpression = new ReadItemExpression(entityType, propertyParameterList);
+ if (innerMethodCall is
+ {
+ Method: { Name: nameof(Queryable.Where), IsGenericMethod: true },
+ Arguments:
+ [
+ EntityQueryRootExpression { EntityType: var entityType },
+ UnaryExpression { Operand: LambdaExpression lambdaExpression, NodeType: ExpressionType.Quote }
+ ]
+ })
+ {
+ var queryProperties = new List();
+ var parameterNames = new List();
+
+ if (ExtractPartitionKeyFromPredicate(entityType, lambdaExpression.Body, queryProperties, parameterNames))
+ {
+ var entityTypePrimaryKeyProperties = entityType.FindPrimaryKey()!.Properties;
+ var idProperty = entityType.GetProperties()
+ .First(p => p.GetJsonPropertyName() == StoreKeyConvention.IdPropertyJsonName);
+ var partitionKeyProperties = entityType.GetPartitionKeyProperties();
+
+ if (entityTypePrimaryKeyProperties.SequenceEqual(queryProperties)
+ && (!partitionKeyProperties.Any()
+ || partitionKeyProperties.All(p => entityTypePrimaryKeyProperties.Contains(p)))
+ // This should ideally only be looking for properties with the `IdValueGeneratorFactory` generator. since
+ // this is how the `id` property will be generated from other key values.
+ && ((idProperty.GetValueGeneratorFactory() != null
+ // If we can't create an instance, then we might not be able to construct the resource id.
+ && CanCreateEmptyInstance(entityType))
+ || entityTypePrimaryKeyProperties.Contains(idProperty)))
+ {
+ var propertyParameterList = queryProperties.Zip(
+ parameterNames,
+ (property, parameter) => (property, parameter))
+ .ToDictionary(tuple => tuple.property, tuple => tuple.parameter);
- return CreateShapedQueryExpression(entityType, readItemExpression)
- .UpdateResultCardinality(ResultCardinality.SingleOrDefault);
+ _readItemExpression = new ReadItemInfo(entityType, propertyParameterList, clrType);
+ }
}
}
}
return base.Visit(expression);
+ static bool CanCreateEmptyInstance(IEntityType entityType)
+ {
+ var binding = entityType.ServiceOnlyConstructorBinding;
+ if (binding == null)
+ {
+ _ = entityType.ConstructorBinding;
+ binding = entityType.ServiceOnlyConstructorBinding;
+ }
+
+ return binding != null;
+ }
+
static bool ExtractPartitionKeyFromPredicate(
IEntityType entityType,
Expression joinCondition,
@@ -256,7 +286,11 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
protected override ShapedQueryExpression CreateShapedQueryExpression(IEntityType entityType)
- => CreateShapedQueryExpression(entityType, _sqlExpressionFactory.Select(entityType));
+ => CreateShapedQueryExpression(
+ entityType,
+ _readItemExpression == null
+ ? _sqlExpressionFactory.Select(entityType)
+ : _sqlExpressionFactory.ReadItem(entityType, _readItemExpression));
private ShapedQueryExpression CreateShapedQueryExpression(IEntityType entityType, Expression queryExpression)
{
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingReadItemExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingReadItemExpressionVisitor.cs
deleted file mode 100644
index 74d658c4de7..00000000000
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingReadItemExpressionVisitor.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#nullable disable
-
-namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
-
-public partial class CosmosShapedQueryCompilingExpressionVisitor
-{
- private sealed class CosmosProjectionBindingRemovingReadItemExpressionVisitor : CosmosProjectionBindingRemovingExpressionVisitorBase
- {
- private readonly ReadItemExpression _readItemExpression;
-
- public CosmosProjectionBindingRemovingReadItemExpressionVisitor(
- ReadItemExpression readItemExpression,
- ParameterExpression jObjectParameter,
- bool trackQueryResults)
- : base(jObjectParameter, trackQueryResults)
- {
- _readItemExpression = readItemExpression;
- }
-
- protected override ProjectionExpression GetProjection(ProjectionBindingExpression _)
- => _readItemExpression.ProjectionExpression;
- }
-}
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ReadItemQueryingEnumerable.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ReadItemQueryingEnumerable.cs
index 420dc595df8..c8c7130dc87 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ReadItemQueryingEnumerable.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ReadItemQueryingEnumerable.cs
@@ -23,7 +23,7 @@ private sealed class ReadItemQueryingEnumerable : IEnumerable, IAsyncEnume
{
private readonly CosmosQueryContext _cosmosQueryContext;
private readonly string _cosmosContainer;
- private readonly ReadItemExpression _readItemExpression;
+ private readonly ReadItemInfo _readItemInfo;
private readonly Func _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger _queryLogger;
@@ -33,7 +33,7 @@ private sealed class ReadItemQueryingEnumerable : IEnumerable, IAsyncEnume
public ReadItemQueryingEnumerable(
CosmosQueryContext cosmosQueryContext,
string cosmosContainer,
- ReadItemExpression readItemExpression,
+ ReadItemInfo readItemInfo,
Func shaper,
Type contextType,
bool standAloneStateManager,
@@ -41,7 +41,7 @@ public ReadItemQueryingEnumerable(
{
_cosmosQueryContext = cosmosQueryContext;
_cosmosContainer = cosmosContainer;
- _readItemExpression = readItemExpression;
+ _readItemInfo = readItemInfo;
_shaper = shaper;
_contextType = contextType;
_queryLogger = _cosmosQueryContext.QueryLogger;
@@ -67,7 +67,7 @@ public string ToQueryString()
private bool TryGetPartitionKey(out PartitionKey partitionKeyValue)
{
- var properties = _readItemExpression.EntityType.GetPartitionKeyProperties();
+ var properties = _readItemInfo.EntityType.GetPartitionKeyProperties();
if (!properties.Any())
{
partitionKeyValue = PartitionKey.None;
@@ -95,7 +95,7 @@ private bool TryGetPartitionKey(out PartitionKey partitionKeyValue)
private bool TryGetResourceId(out string resourceId)
{
- var idProperty = _readItemExpression.EntityType.GetProperties()
+ var idProperty = _readItemInfo.EntityType.GetProperties()
.FirstOrDefault(p => p.GetJsonPropertyName() == StoreKeyConvention.IdPropertyJsonName);
if (TryGetParameterValue(idProperty, out var value))
@@ -124,7 +124,7 @@ private bool TryGetResourceId(out string resourceId)
private bool TryGetParameterValue(IProperty property, out object value)
{
value = null;
- return _readItemExpression.PropertyParameters.TryGetValue(property, out var parameterName)
+ return _readItemInfo.PropertyParameters.TryGetValue(property, out var parameterName)
&& _cosmosQueryContext.ParameterValues.TryGetValue(parameterName, out value);
}
@@ -139,39 +139,36 @@ private static string GetString(IProperty property, object value)
private bool TryGenerateIdFromKeys(IProperty idProperty, out object value)
{
- var entityEntry = Activator.CreateInstance(_readItemExpression.EntityType.ClrType);
-
#pragma warning disable EF1001 // Internal EF Core API usage.
+ // The idea here is that if a `IdValueGeneratorFactory` has been configured to generate an `id` value from the
+ // values of other properties, then we need an entity instance to use with the value generator.
+ var entityInstance = _readItemInfo.EntityType.GetOrCreateEmptyMaterializer(_cosmosQueryContext.EntityMaterializerSource)
+ (new MaterializationContext(ValueBuffer.Empty, _cosmosQueryContext.Context));
+
var internalEntityEntry = new InternalEntityEntry(
- _cosmosQueryContext.Context.GetDependencies().StateManager, _readItemExpression.EntityType, entityEntry);
-#pragma warning restore EF1001 // Internal EF Core API usage.
+ _cosmosQueryContext.Context.GetDependencies().StateManager, _readItemInfo.EntityType, entityInstance);
- foreach (var keyProperty in _readItemExpression.EntityType.FindPrimaryKey().Properties)
+ foreach (var keyProperty in _readItemInfo.EntityType.FindPrimaryKey().Properties)
{
- var property = _readItemExpression.EntityType.FindProperty(keyProperty.Name);
+ var property = _readItemInfo.EntityType.FindProperty(keyProperty.Name);
if (TryGetParameterValue(property, out var parameterValue))
{
-#pragma warning disable EF1001 // Internal EF Core API usage.
internalEntityEntry[property] = parameterValue;
-#pragma warning restore EF1001 // Internal EF Core API usage.
}
}
-#pragma warning disable EF1001 // Internal EF Core API usage.
internalEntityEntry.SetEntityState(EntityState.Added);
-
value = internalEntityEntry[idProperty];
-
internalEntityEntry.SetEntityState(EntityState.Detached);
-#pragma warning restore EF1001 // Internal EF Core API usage.
return value != null;
+#pragma warning restore EF1001 // Internal EF Core API usage.
}
-
private sealed class Enumerator : IEnumerator, IAsyncEnumerator
{
private readonly CosmosQueryContext _cosmosQueryContext;
+ private readonly ReadItemInfo _readItemInfo;
private readonly string _cosmosContainer;
private readonly Func _shaper;
private readonly Type _contextType;
@@ -188,6 +185,7 @@ private sealed class Enumerator : IEnumerator, IAsyncEnumerator
public Enumerator(ReadItemQueryingEnumerable readItemEnumerable, CancellationToken cancellationToken = default)
{
_cosmosQueryContext = readItemEnumerable._cosmosQueryContext;
+ _readItemInfo = readItemEnumerable._readItemInfo;
_cosmosContainer = readItemEnumerable._cosmosContainer;
_shaper = readItemEnumerable._shaper;
_contextType = readItemEnumerable._contextType;
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
index 93634431db9..23a17d2d437 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.cs
@@ -59,45 +59,36 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery
QueryCompilationContext.QueryContextParameter,
jObjectParameter);
- return New(
- typeof(QueryingEnumerable<>).MakeGenericType(shaperLambda.ReturnType).GetConstructors()[0],
- Convert(
- QueryCompilationContext.QueryContextParameter,
- typeof(CosmosQueryContext)),
- Constant(sqlExpressionFactory),
- Constant(querySqlGeneratorFactory),
- Constant(selectExpression),
- Constant(shaperLambda.Compile()),
- Constant(_contextType),
- Constant(cosmosQueryCompilationContext.CosmosContainer),
- Constant(_partitionKeyValueFromExtension, typeof(PartitionKey)),
- Constant(
- QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution),
- Constant(_threadSafetyChecksEnabled));
+ var cosmosQueryContextConstant = Convert(QueryCompilationContext.QueryContextParameter, typeof(CosmosQueryContext));
+ var shaperConstant = Constant(shaperLambda.Compile());
+ var contextTypeConstant = Constant(_contextType);
+ var containerConstant = Constant(cosmosQueryCompilationContext.CosmosContainer);
+ var threadSafetyConstant = Constant(_threadSafetyChecksEnabled);
+ var standAloneStateManagerConstant = Constant(
+ QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution);
- case ReadItemExpression readItemExpression:
- shaperBody = new CosmosProjectionBindingRemovingReadItemExpressionVisitor(
- readItemExpression, jObjectParameter,
- QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.TrackAll)
- .Visit(shaperBody);
-
- var shaperReadItemLambda = Lambda(
- shaperBody,
- QueryCompilationContext.QueryContextParameter,
- jObjectParameter);
-
- return New(
- typeof(ReadItemQueryingEnumerable<>).MakeGenericType(shaperReadItemLambda.ReturnType).GetConstructors()[0],
- Convert(
- QueryCompilationContext.QueryContextParameter,
- typeof(CosmosQueryContext)),
- Constant(cosmosQueryCompilationContext.CosmosContainer),
- Constant(readItemExpression),
- Constant(shaperReadItemLambda.Compile()),
- Constant(_contextType),
- Constant(
- QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution),
- Constant(_threadSafetyChecksEnabled));
+ return selectExpression.ReadItemInfo != null
+ ? New(
+ typeof(ReadItemQueryingEnumerable<>).MakeGenericType(selectExpression.ReadItemInfo.Type).GetConstructors()[0],
+ cosmosQueryContextConstant,
+ containerConstant,
+ Constant(selectExpression.ReadItemInfo),
+ shaperConstant,
+ contextTypeConstant,
+ standAloneStateManagerConstant,
+ threadSafetyConstant)
+ : New(
+ typeof(QueryingEnumerable<>).MakeGenericType(shaperLambda.ReturnType).GetConstructors()[0],
+ cosmosQueryContextConstant,
+ Constant(sqlExpressionFactory),
+ Constant(querySqlGeneratorFactory),
+ Constant(selectExpression),
+ shaperConstant,
+ contextTypeConstant,
+ containerConstant,
+ Constant(_partitionKeyValueFromExtension, typeof(PartitionKey)),
+ standAloneStateManagerConstant,
+ threadSafetyConstant);
default:
throw new NotSupportedException(CoreStrings.UnhandledExpressionNode(shapedQueryExpression.QueryExpression));
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosValueConverterCompensatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosValueConverterCompensatingExpressionVisitor.cs
index e58f20a8f82..9cbdde344fb 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosValueConverterCompensatingExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosValueConverterCompensatingExpressionVisitor.cs
@@ -24,7 +24,6 @@ protected override Expression VisitExtension(Expression extensionExpression)
=> extensionExpression switch
{
ShapedQueryExpression shapedQueryExpression => VisitShapedQueryExpression(shapedQueryExpression),
- ReadItemExpression readItemExpression => readItemExpression,
SelectExpression selectExpression => VisitSelect(selectExpression),
SqlConditionalExpression sqlConditionalExpression => VisitSqlConditional(sqlConditionalExpression),
_ => base.VisitExtension(extensionExpression)
diff --git a/src/EFCore.Cosmos/Query/Internal/Expressions/ReadItemExpression.cs b/src/EFCore.Cosmos/Query/Internal/Expressions/ReadItemInfo.cs
similarity index 64%
rename from src/EFCore.Cosmos/Query/Internal/Expressions/ReadItemExpression.cs
rename to src/EFCore.Cosmos/Query/Internal/Expressions/ReadItemInfo.cs
index 1989781ce1f..f6e8178fdd4 100644
--- a/src/EFCore.Cosmos/Query/Internal/Expressions/ReadItemExpression.cs
+++ b/src/EFCore.Cosmos/Query/Internal/Expressions/ReadItemInfo.cs
@@ -10,35 +10,15 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
/// 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 ReadItemExpression : Expression
+public class ReadItemInfo
{
- private const string RootAlias = "c";
-
///
/// 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 override Type Type
- => typeof(object);
-
- ///
- /// 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 override ExpressionType NodeType
- => ExpressionType.Extension;
-
- ///
- /// 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 ProjectionExpression ProjectionExpression { get; }
+ public virtual Type Type { get; }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -62,18 +42,13 @@ public override ExpressionType NodeType
/// 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 ReadItemExpression(
+ public ReadItemInfo(
IEntityType entityType,
- IDictionary propertyParameters)
+ IDictionary propertyParameters,
+ Type type)
{
- ProjectionExpression = new ProjectionExpression(
- new EntityProjectionExpression(
- entityType,
- new ObjectReferenceExpression(entityType, RootAlias)),
- RootAlias);
-
+ Type = type;
EntityType = entityType;
-
PropertyParameters = propertyParameters;
}
}
diff --git a/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs b/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs
index 3f759ce9cc9..fdf006147c0 100644
--- a/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs
+++ b/src/EFCore.Cosmos/Query/Internal/Expressions/SelectExpression.cs
@@ -25,6 +25,18 @@ public class SelectExpression : Expression, IPrintableExpression
private readonly List<(Expression ValueExpression, IProperty Property)> _partitionKeyValues = new();
+ ///
+ /// 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(IEntityType entityType, ReadItemInfo readItemInfo)
+ : this(entityType)
+ {
+ 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
@@ -69,6 +81,14 @@ public SelectExpression(
_orderings = orderings;
}
+ ///
+ /// 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 ReadItemInfo? ReadItemInfo { 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
diff --git a/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs
index c277d45246c..3ef9969b804 100644
--- a/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs
+++ b/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs
@@ -312,4 +312,12 @@ SqlConditionalExpression Condition(
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
SelectExpression Select(IEntityType entityType, string sql, Expression argument);
+
+ ///
+ /// 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.
+ ///
+ SelectExpression ReadItem(IEntityType entityType, ReadItemInfo argument);
}
diff --git a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
index 7ba033dddb3..6ebb83cd545 100644
--- a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
+++ b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
@@ -631,6 +631,20 @@ public virtual SelectExpression Select(IEntityType entityType)
return selectExpression;
}
+ ///
+ /// 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 SelectExpression ReadItem(IEntityType entityType, ReadItemInfo readItemInfo)
+ {
+ var selectExpression = new SelectExpression(entityType, readItemInfo);
+ AddDiscriminator(selectExpression, entityType);
+
+ return selectExpression;
+ }
+
///
/// 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.Cosmos/Query/Internal/SqlExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/SqlExpressionVisitor.cs
index e867950b8f9..98c96d8860d 100644
--- a/src/EFCore.Cosmos/Query/Internal/SqlExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/SqlExpressionVisitor.cs
@@ -22,7 +22,6 @@ protected override Expression VisitExtension(Expression extensionExpression)
{
ShapedQueryExpression shapedQueryExpression
=> shapedQueryExpression.UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression)),
- ReadItemExpression readItemExpression => readItemExpression,
SelectExpression selectExpression => VisitSelect(selectExpression),
ProjectionExpression projectionExpression => VisitProjection(projectionExpression),
EntityProjectionExpression entityProjectionExpression => VisitEntityProjection(entityProjectionExpression),
diff --git a/src/EFCore/Query/QueryContext.cs b/src/EFCore/Query/QueryContext.cs
index 08bad5a6903..e3a3ef09b81 100644
--- a/src/EFCore/Query/QueryContext.cs
+++ b/src/EFCore/Query/QueryContext.cs
@@ -51,6 +51,12 @@ protected QueryContext(QueryContextDependencies dependencies)
///
protected virtual QueryContextDependencies Dependencies { get; }
+ ///
+ /// The , which can be used to create stand-alone entity instances.
+ ///
+ public virtual IEntityMaterializerSource EntityMaterializerSource
+ => Dependencies.EntityMaterializerSource;
+
///
/// Sets the navigation for given entity as loaded.
///
diff --git a/src/EFCore/Query/QueryContextDependencies.cs b/src/EFCore/Query/QueryContextDependencies.cs
index 1978342f43f..3623094a885 100644
--- a/src/EFCore/Query/QueryContextDependencies.cs
+++ b/src/EFCore/Query/QueryContextDependencies.cs
@@ -53,6 +53,7 @@ public QueryContextDependencies(
IExecutionStrategy executionStrategy,
IConcurrencyDetector concurrencyDetector,
IExceptionDetector exceptionDetector,
+ IEntityMaterializerSource entityMaterializerSource,
IDiagnosticsLogger commandLogger,
IDiagnosticsLogger queryLogger)
{
@@ -60,6 +61,7 @@ public QueryContextDependencies(
ExecutionStrategy = executionStrategy;
ConcurrencyDetector = concurrencyDetector;
ExceptionDetector = exceptionDetector;
+ EntityMaterializerSource = entityMaterializerSource;
CommandLogger = commandLogger;
QueryLogger = queryLogger;
}
@@ -94,6 +96,11 @@ public IStateManager StateManager
///
public IExceptionDetector ExceptionDetector { get; init; }
+ ///
+ /// The , which can be used to create stand-alone entity instances.
+ ///
+ public IEntityMaterializerSource EntityMaterializerSource { get; }
+
///
/// The command logger.
///
diff --git a/test/EFCore.Cosmos.FunctionalTests/FindCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/FindCosmosTest.cs
index 3897e3be08d..bf0e875145a 100644
--- a/test/EFCore.Cosmos.FunctionalTests/FindCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/FindCosmosTest.cs
@@ -72,6 +72,407 @@ public override void Find_shadow_key_from_store()
public override void Returns_null_for_shadow_key_not_in_store()
=> CosmosTestHelpers.Instance.NoSyncTest(() => base.Returns_null_for_shadow_key_not_in_store());
+ public override void Find_int_key_tracked()
+ {
+ base.Find_int_key_tracked();
+
+ AssertSql();
+ }
+
+ public override void Find_nullable_int_key_tracked()
+ {
+ base.Find_nullable_int_key_tracked();
+
+ AssertSql();
+ }
+
+ public override void Find_string_key_tracked()
+ {
+ base.Find_string_key_tracked();
+
+ AssertSql();
+ }
+
+ public override void Find_composite_key_tracked()
+ {
+ base.Find_composite_key_tracked();
+
+ AssertSql();
+ }
+
+ public override void Find_base_type_tracked()
+ {
+ base.Find_base_type_tracked();
+
+ AssertSql();
+ }
+
+ public override void Find_derived_type_tracked()
+ {
+ base.Find_derived_type_tracked();
+
+ AssertSql();
+ }
+
+ public override void Find_derived_type_using_base_set_tracked()
+ {
+ base.Find_derived_type_using_base_set_tracked();
+
+ AssertSql();
+ }
+
+ public override void Find_shadow_key_tracked()
+ {
+ base.Find_shadow_key_tracked();
+
+ AssertSql();
+ }
+
+ public override void Returns_null_for_null_key_values_array()
+ {
+ base.Returns_null_for_null_key_values_array();
+
+ AssertSql();
+ }
+
+ public override void Returns_null_for_null_key()
+ {
+ base.Returns_null_for_null_key();
+
+ AssertSql();
+ }
+
+ public override void Returns_null_for_null_nullable_key()
+ {
+ base.Returns_null_for_null_nullable_key();
+
+ AssertSql();
+ }
+
+ public override void Returns_null_for_null_in_composite_key()
+ {
+ base.Returns_null_for_null_in_composite_key();
+
+ AssertSql();
+ }
+
+ public override void Throws_for_multiple_values_passed_for_simple_key()
+ {
+ base.Throws_for_multiple_values_passed_for_simple_key();
+
+ AssertSql();
+ }
+
+ public override void Throws_for_wrong_number_of_values_for_composite_key()
+ {
+ base.Throws_for_wrong_number_of_values_for_composite_key();
+
+ AssertSql();
+ }
+
+ public override void Throws_for_bad_type_for_simple_key()
+ {
+ base.Throws_for_bad_type_for_simple_key();
+
+ AssertSql();
+ }
+
+ public override void Throws_for_bad_type_for_composite_key()
+ {
+ base.Throws_for_bad_type_for_composite_key();
+
+ AssertSql();
+ }
+
+ public override void Throws_for_bad_entity_type()
+ {
+ base.Throws_for_bad_entity_type();
+
+ AssertSql();
+ }
+
+ public override void Throws_for_bad_entity_type_with_different_namespace()
+ {
+ base.Throws_for_bad_entity_type_with_different_namespace();
+
+ AssertSql();
+ }
+
+ public override async Task Find_int_key_tracked_async(CancellationType cancellationType)
+ {
+ await base.Find_int_key_tracked_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Find_int_key_from_store_async(CancellationType cancellationType)
+ {
+ await base.Find_int_key_from_store_async(cancellationType);
+
+ 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)
+""");
+ }
+
+ public override async Task Find_nullable_int_key_tracked_async(CancellationType cancellationType)
+ {
+ await base.Find_nullable_int_key_tracked_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Find_nullable_int_key_from_store_async(CancellationType cancellationType)
+ {
+ await base.Find_nullable_int_key_from_store_async(cancellationType);
+
+ 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)
+""");
+ }
+
+ public override async Task Find_string_key_tracked_async(CancellationType cancellationType)
+ {
+ await base.Find_string_key_tracked_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Find_string_key_from_store_async(CancellationType cancellationType)
+ {
+ await base.Find_string_key_from_store_async(cancellationType);
+
+ 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)
+""");
+ }
+
+ public override async Task Find_composite_key_tracked_async(CancellationType cancellationType)
+ {
+ await base.Find_composite_key_tracked_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Find_composite_key_from_store_async(CancellationType cancellationType)
+ {
+ await base.Find_composite_key_from_store_async(cancellationType);
+
+ 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)
+""");
+ }
+
+ public override async Task Find_base_type_tracked_async(CancellationType cancellationType)
+ {
+ await base.Find_base_type_tracked_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Find_base_type_from_store_async(CancellationType cancellationType)
+ {
+ await base.Find_base_type_from_store_async(cancellationType);
+
+ AssertSql(
+ """
+ReadItem(None, BaseType|77)
+""");
+ }
+
+ public override async Task Returns_null_for_base_type_not_in_store_async(CancellationType cancellationType)
+ {
+ await base.Returns_null_for_base_type_not_in_store_async(cancellationType);
+
+ AssertSql(
+ """
+ReadItem(None, BaseType|99)
+""");
+ }
+
+ public override async Task Find_derived_type_tracked_async(CancellationType cancellationType)
+ {
+ await base.Find_derived_type_tracked_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Find_derived_type_from_store_async(CancellationType cancellationType)
+ {
+ await base.Find_derived_type_from_store_async(cancellationType);
+
+ 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)
+""");
+ }
+
+ 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)
+""");
+ }
+
+ public override async Task Find_derived_type_using_base_set_tracked_async(CancellationType cancellationType)
+ {
+ await base.Find_derived_type_using_base_set_tracked_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Find_shadow_key_tracked_async(CancellationType cancellationType)
+ {
+ await base.Find_shadow_key_tracked_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Find_shadow_key_from_store_async(CancellationType cancellationType)
+ {
+ await base.Find_shadow_key_from_store_async(cancellationType);
+
+ 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)
+""");
+ }
+
+ public override async Task Returns_null_for_null_key_values_array_async(CancellationType cancellationType)
+ {
+ await base.Returns_null_for_null_key_values_array_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Returns_null_for_null_key_async(CancellationType cancellationType)
+ {
+ await base.Returns_null_for_null_key_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Returns_null_for_null_in_composite_key_async(CancellationType cancellationType)
+ {
+ await base.Returns_null_for_null_in_composite_key_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Throws_for_multiple_values_passed_for_simple_key_async(CancellationType cancellationType)
+ {
+ await base.Throws_for_multiple_values_passed_for_simple_key_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Throws_for_wrong_number_of_values_for_composite_key_async(CancellationType cancellationType)
+ {
+ await base.Throws_for_wrong_number_of_values_for_composite_key_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Throws_for_bad_type_for_simple_key_async(CancellationType cancellationType)
+ {
+ await base.Throws_for_bad_type_for_simple_key_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Throws_for_bad_type_for_composite_key_async(CancellationType cancellationType)
+ {
+ await base.Throws_for_bad_type_for_composite_key_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Throws_for_bad_entity_type_async(CancellationType cancellationType)
+ {
+ await base.Throws_for_bad_entity_type_async(cancellationType);
+
+ AssertSql();
+ }
+
+ public override async Task Throws_for_bad_entity_type_with_different_namespace_async(CancellationType cancellationType)
+ {
+ await base.Throws_for_bad_entity_type_with_different_namespace_async(cancellationType);
+
+ AssertSql();
+ }
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+
+ protected void ClearLog()
+ => Fixture.TestSqlLoggerFactory.Clear();
+
public class FindCosmosTestSet(FindCosmosFixture fixture) : FindCosmosTest(fixture)
{
protected override TestFinder Finder { get; } = new FindViaSetFinder();
diff --git a/test/EFCore.Specification.Tests/FindTestBase.cs b/test/EFCore.Specification.Tests/FindTestBase.cs
index 982523e9895..c8da019b5ef 100644
--- a/test/EFCore.Specification.Tests/FindTestBase.cs
+++ b/test/EFCore.Specification.Tests/FindTestBase.cs
@@ -1,6 +1,7 @@
// 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;
using System.ComponentModel.DataAnnotations.Schema;
#nullable disable
@@ -335,7 +336,18 @@ public virtual async Task Find_int_key_tracked_async(CancellationType cancellati
public virtual async Task Find_int_key_from_store_async(CancellationType cancellationType)
{
using var context = CreateContext();
- Assert.Equal("Smokey", (await Finder.FindAsync(cancellationType, context, [77])).Foo);
+
+ var entity = await Finder.FindAsync(cancellationType, context, [77]);
+
+ 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]
@@ -736,6 +748,9 @@ protected class IntKey
public int Id { get; set; }
public string Foo { get; set; }
+
+ public Owned1 OwnedReference { get; set; }
+ public List OwnedCollection { get; set; }
}
protected class NullableIntKey
@@ -765,6 +780,21 @@ protected class ShadowKey
public string Foo { get; set; }
}
+ [Owned]
+ protected class Owned1
+ {
+ public int Prop { get; set; }
+ public Owned2 NestedOwned { get; set; }
+ public List NestedOwnedCollection { get; set; }
+ }
+
+ [Owned]
+ protected class Owned2
+ {
+ [Required]
+ public string Prop { get; set; }
+ }
+
protected DbContext CreateContext()
=> Fixture.CreateContext();
@@ -788,7 +818,18 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
protected override Task SeedAsync(PoolableDbContext context)
{
context.AddRange(
- new IntKey { Id = 77, Foo = "Smokey" },
+ 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
@@ -847,7 +888,7 @@ public override ValueTask FindAsync(
};
}
- public class FindViaContextFinder : TestFinder
+ public class FindViaNonGenericContextFinder : TestFinder
{
public override TEntity Find(DbContext context, params object[] keyValues)
=> (TEntity)context.Find(typeof(TEntity), keyValues);
@@ -868,7 +909,7 @@ public override async ValueTask FindAsync(
};
}
- public class FindViaNonGenericContextFinder : TestFinder
+ public class FindViaContextFinder : TestFinder
{
public override TEntity Find(DbContext context, params object[] keyValues)
=> context.Find(keyValues);
diff --git a/test/EFCore.SqlServer.FunctionalTests/FindSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/FindSqlServerTest.cs
index e03c887696a..5540e94ad32 100644
--- a/test/EFCore.SqlServer.FunctionalTests/FindSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/FindSqlServerTest.cs
@@ -43,9 +43,21 @@ public override void Find_int_key_from_store()
"""
@__p_0='77'
-SELECT TOP(1) [i].[Id], [i].[Foo]
-FROM [IntKey] AS [i]
-WHERE [i].[Id] = @__p_0
+SELECT [i3].[Id], [i3].[Foo], [s].[IntKeyId], [s].[Id], [s].[Prop], [s].[NestedOwned_Prop], [s].[Owned1IntKeyId], [s].[Owned1Id], [s].[Id0], [s].[Prop0], [i3].[OwnedReference_Prop], [i3].[OwnedReference_NestedOwned_Prop], [i2].[Owned1IntKeyId], [i2].[Id], [i2].[Prop]
+FROM (
+ SELECT TOP(1) [i].[Id], [i].[Foo], [i].[OwnedReference_Prop], [i].[OwnedReference_NestedOwned_Prop]
+ FROM [IntKey] AS [i]
+ WHERE [i].[Id] = @__p_0
+) AS [i3]
+LEFT JOIN (
+ SELECT [i0].[IntKeyId], [i0].[Id], [i0].[Prop], [i0].[NestedOwned_Prop], [i1].[Owned1IntKeyId], [i1].[Owned1Id], [i1].[Id] AS [Id0], [i1].[Prop] AS [Prop0]
+ FROM [IntKey_OwnedCollection] AS [i0]
+ LEFT JOIN [IntKey_OwnedCollection_NestedOwnedCollection] AS [i1] ON [i0].[IntKeyId] = [i1].[Owned1IntKeyId] AND [i0].[Id] = [i1].[Owned1Id]
+) AS [s] ON [i3].[Id] = [s].[IntKeyId]
+LEFT JOIN [IntKey_NestedOwnedCollection] AS [i2] ON CASE
+ WHEN [i3].[OwnedReference_Prop] IS NOT NULL THEN [i3].[Id]
+END = [i2].[Owned1IntKeyId]
+ORDER BY [i3].[Id], [s].[IntKeyId], [s].[Id], [s].[Owned1IntKeyId], [s].[Owned1Id], [s].[Id0], [i2].[Owned1IntKeyId]
""");
}
@@ -57,9 +69,21 @@ public override void Returns_null_for_int_key_not_in_store()
"""
@__p_0='99'
-SELECT TOP(1) [i].[Id], [i].[Foo]
-FROM [IntKey] AS [i]
-WHERE [i].[Id] = @__p_0
+SELECT [i3].[Id], [i3].[Foo], [s].[IntKeyId], [s].[Id], [s].[Prop], [s].[NestedOwned_Prop], [s].[Owned1IntKeyId], [s].[Owned1Id], [s].[Id0], [s].[Prop0], [i3].[OwnedReference_Prop], [i3].[OwnedReference_NestedOwned_Prop], [i2].[Owned1IntKeyId], [i2].[Id], [i2].[Prop]
+FROM (
+ SELECT TOP(1) [i].[Id], [i].[Foo], [i].[OwnedReference_Prop], [i].[OwnedReference_NestedOwned_Prop]
+ FROM [IntKey] AS [i]
+ WHERE [i].[Id] = @__p_0
+) AS [i3]
+LEFT JOIN (
+ SELECT [i0].[IntKeyId], [i0].[Id], [i0].[Prop], [i0].[NestedOwned_Prop], [i1].[Owned1IntKeyId], [i1].[Owned1Id], [i1].[Id] AS [Id0], [i1].[Prop] AS [Prop0]
+ FROM [IntKey_OwnedCollection] AS [i0]
+ LEFT JOIN [IntKey_OwnedCollection_NestedOwnedCollection] AS [i1] ON [i0].[IntKeyId] = [i1].[Owned1IntKeyId] AND [i0].[Id] = [i1].[Owned1Id]
+) AS [s] ON [i3].[Id] = [s].[IntKeyId]
+LEFT JOIN [IntKey_NestedOwnedCollection] AS [i2] ON CASE
+ WHEN [i3].[OwnedReference_Prop] IS NOT NULL THEN [i3].[Id]
+END = [i2].[Owned1IntKeyId]
+ORDER BY [i3].[Id], [s].[IntKeyId], [s].[Id], [s].[Owned1IntKeyId], [s].[Owned1Id], [s].[Id0], [i2].[Owned1IntKeyId]
""");
}
@@ -72,29 +96,29 @@ public override void Find_nullable_int_key_tracked()
public override void Find_nullable_int_key_from_store()
{
- base.Find_int_key_from_store();
+ base.Find_nullable_int_key_from_store();
AssertSql(
"""
-@__p_0='77'
+@__p_0='77' (Nullable = true)
-SELECT TOP(1) [i].[Id], [i].[Foo]
-FROM [IntKey] AS [i]
-WHERE [i].[Id] = @__p_0
+SELECT TOP(1) [n].[Id], [n].[Foo]
+FROM [NullableIntKey] AS [n]
+WHERE [n].[Id] = @__p_0
""");
}
public override void Returns_null_for_nullable_int_key_not_in_store()
{
- base.Returns_null_for_int_key_not_in_store();
+ base.Returns_null_for_nullable_int_key_not_in_store();
AssertSql(
"""
-@__p_0='99'
+@__p_0='99' (Nullable = true)
-SELECT TOP(1) [i].[Id], [i].[Foo]
-FROM [IntKey] AS [i]
-WHERE [i].[Id] = @__p_0
+SELECT TOP(1) [n].[Id], [n].[Foo]
+FROM [NullableIntKey] AS [n]
+WHERE [n].[Id] = @__p_0
""");
}
diff --git a/test/EFCore.Sqlite.FunctionalTests/FindSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/FindSqliteTest.cs
index 5bdcfb7ac78..c44ae69657b 100644
--- a/test/EFCore.Sqlite.FunctionalTests/FindSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/FindSqliteTest.cs
@@ -31,5 +31,31 @@ public class FindSqliteFixture : FindFixtureBase
{
protected override ITestStoreFactory TestStoreFactory
=> SqliteTestStoreFactory.Instance;
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
+ {
+ base.OnModelCreating(modelBuilder, context);
+
+ modelBuilder.Entity(
+ b =>
+ {
+ // This configuration for SQLite prevents attempts to use the default composite key config, which doesn't work
+ // on SQLite. See #26708
+ b.OwnsOne(
+ e => e.OwnedReference, b =>
+ {
+ b.OwnsOne(e => e.NestedOwned);
+ b.OwnsMany(e => e.NestedOwnedCollection).ToTable("NestedOwnedCollection").HasKey(e => e.Prop);
+ });
+
+ b.OwnsMany(
+ e => e.OwnedCollection, b =>
+ {
+ b.ToTable("OwnedCollection").HasKey(e => e.Prop);
+ b.OwnsOne(e => e.NestedOwned);
+ b.OwnsMany(e => e.NestedOwnedCollection).ToTable("OwnedNestedOwnedCollection").HasKey(e => e.Prop);
+ });
+ });
+ }
}
}