Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.Cosmos/Properties/CosmosStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@
<data name="CosmosNotInUse" xml:space="preserve">
<value>Cosmos-specific methods can only be used when the context is using the Cosmos provider.</value>
</data>
<data name="CrossDocumentJoinNotSupported" xml:space="preserve">
<value>Joins across documents aren't supported in Cosmos; consider modeling your data differently so that related data is in the same document. Alternatively, perform two separate queries to query the two documents.</value>
</data>
<data name="DefaultTTLMismatch" xml:space="preserve">
<value>The default time to live was configured to '{ttl1}' on '{entityType1}', but on '{entityType2}' it was configured to '{ttl2}'. All entity types mapped to the same container '{container}' must be configured with the same default time to live.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,10 @@ protected override ShapedQueryExpression TranslateDistinct(ShapedQueryExpression
LambdaExpression outerKeySelector,
LambdaExpression innerKeySelector,
LambdaExpression resultSelector)
=> null;
{
AddTranslationErrorDetails(CosmosStrings.CrossDocumentJoinNotSupported);
return null;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -807,7 +810,10 @@ protected override ShapedQueryExpression TranslateDistinct(ShapedQueryExpression
LambdaExpression outerKeySelector,
LambdaExpression innerKeySelector,
LambdaExpression resultSelector)
=> null;
{
AddTranslationErrorDetails(CosmosStrings.CrossDocumentJoinNotSupported);
return null;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
150 changes: 108 additions & 42 deletions test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ public OwnedQueryCosmosTest(OwnedQueryCosmosFixture fixture, ITestOutputHelper t
Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}

// TODO: Fake LeftJoin, #33969
// Fink.Barton is a non-owned navigation, cross-document join
public override Task Query_loads_reference_nav_automatically_in_projection(bool async)
=> AssertTranslationFailed(() => base.Query_loads_reference_nav_automatically_in_projection(async));
=> AssertTranslationFailedWithDetails(
() => base.Query_loads_reference_nav_automatically_in_projection(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// Non-correlated queries not supported by Cosmos
public override Task Query_with_owned_entity_equality_operator(bool async)
Expand Down Expand Up @@ -161,10 +163,11 @@ FROM root c
""");
});

// TODO: Fake LeftJoin, #33969
// 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)
=> AssertTranslationFailed(
() => base.Filter_owned_entity_chained_with_regular_entity_followed_by_projecting_owned_collection(async));
=> AssertTranslationFailedWithDetails(
() => base.Filter_owned_entity_chained_with_regular_entity_followed_by_projecting_owned_collection(async),
CosmosStrings.CrossDocumentJoinNotSupported);

public override async Task Set_throws_for_owned_type(bool async)
{
Expand All @@ -173,55 +176,65 @@ public override async Task Set_throws_for_owned_type(bool async)
AssertSql();
}

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity(bool async)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// 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)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_filter(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// 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)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// 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)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_and_scalar(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_and_scalar(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// 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)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// 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)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection_count(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// 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)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_property(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_property(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// 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)
=> AssertTranslationFailed(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_in_predicate_and_projection(async));
=> AssertTranslationFailedWithDetails(
() => base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_in_predicate_and_projection(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task Project_multiple_owned_navigations(bool async)
=> AssertTranslationFailed(
() => base.Project_multiple_owned_navigations(async));
=> AssertTranslationFailedWithDetails(
() => base.Project_multiple_owned_navigations(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// Address.Planet is a non-owned navigation, cross-document join
public override Task Project_multiple_owned_navigations_with_expansion_on_owned_collections(bool async)
=> AssertTranslationFailed(
() => base.Project_multiple_owned_navigations_with_expansion_on_owned_collections(async));
=> AssertTranslationFailedWithDetails(
() => base.Project_multiple_owned_navigations_with_expansion_on_owned_collections(async),
CosmosStrings.CrossDocumentJoinNotSupported);

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
Expand All @@ -240,13 +253,38 @@ JOIN a IN c["Orders"]
""");
});

// TODO: Fake LeftJoin, #33969
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public override Task SelectMany_with_result_selector(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
async, async a =>
{
await base.SelectMany_with_result_selector(a);

AssertSql(
"""
SELECT VALUE
{
"PersonId" : c["Id"],
"OrderId" : a["Id"]
}
FROM root c
JOIN a IN c["Orders"]
WHERE c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA")
""");
});

// 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)
=> AssertTranslationFailed(() => base.SelectMany_on_owned_reference_followed_by_regular_entity_and_collection(async));
=> AssertTranslationFailedWithDetails(
() => base.SelectMany_on_owned_reference_followed_by_regular_entity_and_collection(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// TODO: Fake LeftJoin, #33969
// 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)
=> AssertTranslationFailed(() => base.SelectMany_on_owned_reference_with_entity_in_between_ending_in_owned_collection(async));
=> AssertTranslationFailedWithDetails(
() => base.SelectMany_on_owned_reference_with_entity_in_between_ending_in_owned_collection(async),
CosmosStrings.CrossDocumentJoinNotSupported);

// Non-correlated queries not supported by Cosmos
public override Task Query_with_owned_entity_equality_method(bool async)
Expand Down Expand Up @@ -274,6 +312,32 @@ FROM root c
public override Task Query_when_subquery(bool async)
=> AssertTranslationFailed(() => base.Query_when_subquery(async));

public override Task Project_owned_reference_navigation_which_owns_additional(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
async, async a =>
{
await base.Project_owned_reference_navigation_which_owns_additional(a);

// TODO: The following should project out c["PersonAddress"], not c: #34067
AssertSql(
"""
SELECT c
FROM root c
WHERE c["Discriminator"] IN ("OwnedPerson", "Branch", "LeafB", "LeafA")
ORDER BY c["Id"]
""");
});

// TODO: #34068
public override async Task Project_owned_reference_navigation_which_does_not_own_additional(bool async)
{
// Always throws for sync.
if (async)
{
await Assert.ThrowsAsync<NullReferenceException>(() => base.Project_owned_reference_navigation_which_does_not_own_additional(async));
}
}

public override Task No_ignored_include_warning_when_implicit_load(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
async, async a =>
Expand Down Expand Up @@ -320,6 +384,7 @@ await AssertQuery(
assertOrder: true,
elementAsserter: (e, a) => AssertCollection(e, a));

// TODO: The following should project out a["Details"], not a: #34067
AssertSql(
"""
SELECT a
Expand Down Expand Up @@ -687,12 +752,13 @@ public override async Task NoTracking_Include_with_cycles_throws(bool async)
AssertSql();
}

// TODO: Fake LeftJoin, #33969
// Order.Client is a non-owned navigation, cross-document join
public override Task NoTracking_Include_with_cycles_does_not_throw_when_performing_identity_resolution(
bool async,
bool useAsTracking)
=> AssertTranslationFailed(
() => base.NoTracking_Include_with_cycles_does_not_throw_when_performing_identity_resolution(async, useAsTracking));
=> AssertTranslationFailedWithDetails(
() => base.NoTracking_Include_with_cycles_does_not_throw_when_performing_identity_resolution(async, useAsTracking),
CosmosStrings.CrossDocumentJoinNotSupported);

public override async Task Trying_to_access_non_existent_indexer_property_throws_meaningful_exception(bool async)
{
Expand Down
21 changes: 21 additions & 0 deletions test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,20 @@ public virtual Task Query_when_subquery(bool async)
assertOrder: true,
elementAsserter: (e, a) => AssertEqual(e.op, a.op));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Project_owned_reference_navigation_which_owns_additional(bool async)
=> AssertQuery(
async,
ss => ss.Set<OwnedPerson>().OrderBy(o => o.Id).Select(p => p.PersonAddress));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Project_owned_reference_navigation_which_does_not_own_additional(bool async)
=> AssertQuery(
async,
ss => ss.Set<OwnedPerson>().OrderBy(o => o.Id).Select(p => p.PersonAddress.Country));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Navigation_rewrite_on_owned_reference_projecting_scalar(bool async)
Expand Down Expand Up @@ -177,6 +191,13 @@ public virtual Task SelectMany_on_owned_collection(bool async)
async,
ss => ss.Set<OwnedPerson>().SelectMany(p => p.Orders));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task SelectMany_with_result_selector(bool async)
=> AssertQuery(
async,
ss => ss.Set<OwnedPerson>().SelectMany(o => o.Orders, (p, o) => new { PersonId = p.Id, OrderId = o.Id }));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Set_throws_for_owned_type(bool async)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,30 @@ FROM [Order] AS [o1]
""");
}

public override async Task Project_owned_reference_navigation_which_owns_additional(bool async)
{
await base.Project_owned_reference_navigation_which_owns_additional(async);

AssertSql(
"""
SELECT [o].[Id], [o].[PersonAddress_AddressLine], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId]
FROM [OwnedPerson] AS [o]
ORDER BY [o].[Id]
""");
}

public override async Task Project_owned_reference_navigation_which_does_not_own_additional(bool async)
{
await base.Project_owned_reference_navigation_which_does_not_own_additional(async);

AssertSql(
"""
SELECT [o].[Id], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId]
FROM [OwnedPerson] AS [o]
ORDER BY [o].[Id]
""");
}

public override async Task Navigation_rewrite_on_owned_reference_projecting_scalar(bool async)
{
await base.Navigation_rewrite_on_owned_reference_projecting_scalar(async);
Expand Down Expand Up @@ -291,6 +315,18 @@ FROM [OwnedPerson] AS [o]
""");
}

public override async Task SelectMany_with_result_selector(bool async)
{
await base.SelectMany_with_result_selector(async);

AssertSql(
"""
SELECT [o].[Id] AS [PersonId], [o0].[Id] AS [OrderId]
FROM [OwnedPerson] AS [o]
INNER JOIN [Order] AS [o0] ON [o].[Id] = [o0].[ClientId]
""");
}

public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity(bool async)
{
await base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity(async);
Expand Down