Skip to content

Commit

Permalink
Fix to #29156 - TemporalAll for temporal owned entities mapped to par…
Browse files Browse the repository at this point in the history
…ent table.

Similar to JSON entities, owned entities that are mapped to the same table as their owner should be treated as scalars for the purpose of temporal query validation - they are always in sync with the parent entity, so all operations should be allowed for them, not only AsOf.

Fixes #29156
  • Loading branch information
maumar committed Oct 11, 2023
1 parent 3a7fc44 commit 22e8580
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ public override EntityQueryRootExpression CreateQueryRoot(IEntityType entityType
/// </summary>
public override void ValidateQueryRootCreation(IEntityType entityType, EntityQueryRootExpression? source)
{
if (source is TemporalQueryRootExpression && !entityType.IsMappedToJson())
if (source is TemporalQueryRootExpression
&& !entityType.IsMappedToJson()
&& !OwnedEntityMappedToSameTableAsOwner(entityType))
{
if (!entityType.GetRootType().IsTemporal())
{
Expand All @@ -69,6 +71,12 @@ public override void ValidateQueryRootCreation(IEntityType entityType, EntityQue
base.ValidateQueryRootCreation(entityType, source);
}

private bool OwnedEntityMappedToSameTableAsOwner(IEntityType entityType)
=> entityType.IsOwned()
&& entityType.FindOwnership()!.PrincipalEntityType.GetTableMappings().FirstOrDefault()?.Table is ITable ownerTable
&& entityType.GetTableMappings().FirstOrDefault()?.Table is ITable entityTable
&& ownerTable == entityTable;

/// <summary>
/// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,58 @@ protected override Expression RewriteServerQueryExpression(Expression serverQuer
return rewriter.Visit(serverQueryExpression);
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Navigation_on_owned_entity_mapped_to_same_table_works_with_all_temporal_methods(bool async)
{
var context = CreateContext();

var queryAsOf = context.Set<OwnedPerson>()
.TemporalAsOf(new DateTime(2010, 1, 1));

var resultAsOf = async
? await queryAsOf.ToListAsync()
: queryAsOf.ToList();

var queryAll = context.Set<OwnedPerson>()
.TemporalAll()
.Select(x => x.PersonAddress);

var resultAll = async
? await queryAll.ToListAsync()
: queryAll.ToList();

var queryBetween = context.Set<OwnedPerson>()
.TemporalBetween(new DateTime(1990, 1, 1), new DateTime(2200, 1, 1))
.Select(x => x.PersonAddress);

var resultBetween = async
? await queryBetween.ToListAsync()
: queryBetween.ToList();

AssertSql(
"""
SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId]
FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o]
LEFT JOIN (
SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0]
FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0]
LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON [o0].[ClientId] = [o1].[OrderClientId] AND [o0].[Id] = [o1].[OrderId]
) AS [t] ON [o].[Id] = [t].[ClientId]
ORDER BY [o].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]
""",
//
"""
SELECT [o].[Id], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId]
FROM [OwnedPerson] FOR SYSTEM_TIME ALL AS [o]
""",
//
"""
SELECT [o].[Id], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId]
FROM [OwnedPerson] FOR SYSTEM_TIME BETWEEN '1990-01-01T00:00:00.0000000' AND '2200-01-01T00:00:00.0000000' AS [o]
""");
}

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

0 comments on commit 22e8580

Please sign in to comment.