Skip to content

Commit

Permalink
ExecuteDelete for aggregate root when only owned types are sharing th…
Browse files Browse the repository at this point in the history
…e table

Resolves #28671
  • Loading branch information
smitpatel committed Sep 9, 2022
1 parent f20729d commit c75abeb
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,11 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
/// <returns>The non query after translation.</returns>
protected virtual NonQueryExpression? TranslateExecuteDelete(ShapedQueryExpression source)
{
if (source.ShaperExpression is IncludeExpression includeExpression)
{
source = source.UpdateShaperExpression(PruneOwnedIncludes(includeExpression));
}

if (source.ShaperExpression is not EntityShaperExpression entityShaperExpression)
{
AddTranslationErrorDetails(RelationalStrings.ExecuteDeleteOnNonEntityType);
Expand Down Expand Up @@ -1050,9 +1055,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
var selectExpression = (SelectExpression)source.QueryExpression;
if (IsValidSelectExpressionForExecuteDelete(selectExpression, entityShaperExpression, out var tableExpression))
{
if ((mappingStrategy == null && tableExpression.Table.EntityTypeMappings.Count() != 1)
|| (mappingStrategy == RelationalAnnotationNames.TphMappingStrategy
&& tableExpression.Table.EntityTypeMappings.Any(e => e.EntityType.GetRootType() != entityType.GetRootType())))
if (AreOtherNonOwnedEntityTypesInTheTable(entityType.GetRootType(), tableExpression.Table))
{
AddTranslationErrorDetails(
RelationalStrings.ExecuteDeleteOnTableSplitting(tableExpression.Table.SchemaQualifiedName));
Expand All @@ -1064,6 +1067,24 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
selectExpression.ApplyProjection();

return new NonQueryExpression(new DeleteExpression(tableExpression, selectExpression));

static bool AreOtherNonOwnedEntityTypesInTheTable(IEntityType rootType, ITableBase table)
{
foreach (var entityTypeMapping in table.EntityTypeMappings)
{
var entityType = entityTypeMapping.EntityType;
if ((entityTypeMapping.IsSharedTablePrincipal == true
&& entityType != rootType)
|| (entityTypeMapping.IsSharedTablePrincipal == false
&& entityType.GetRootType() != rootType
&& !entityType.IsOwned()))
{
return true;
}
}

return false;
}
}

// We need to convert to PK predicate
Expand Down Expand Up @@ -1095,6 +1116,20 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
Expression.Quote(Expression.Lambda(predicateBody, entityParameter)));

return TranslateExecuteDelete((ShapedQueryExpression)Visit(newSource));

static Expression PruneOwnedIncludes(IncludeExpression includeExpression)
{
if (includeExpression.Navigation is ISkipNavigation
|| includeExpression.Navigation is not INavigation navigation
|| !navigation.ForeignKey.IsOwnership)
{
return includeExpression;
}

return includeExpression.EntityExpression is IncludeExpression innerIncludeExpression
? PruneOwnedIncludes(innerIncludeExpression)
: includeExpression.EntityExpression;
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,87 @@ public abstract class NonSharedModelBulkUpdatesTestBase : NonSharedModelTestBase
protected override string StoreName
=> "NonSharedModelBulkUpdatesTests";

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Delete_aggregate_root_when_eager_loaded_owned_collection(bool async)
{
var contextFactory = await InitializeAsync<Context28671>(onModelCreating: mb => mb.Entity<Owner>().Ignore(e => e.OwnedReference));
await AssertDelete(async, contextFactory.CreateContext,
context => context.Set<Owner>(), rowsAffectedCount: 0);
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Delete_aggregate_root_when_table_sharing_with_owned(bool async)
{
var contextFactory = await InitializeAsync<Context28671>();
await AssertDelete(async, contextFactory.CreateContext,
context => context.Set<Owner>(), rowsAffectedCount: 0);
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Delete_aggregate_root_when_table_sharing_with_non_owned_throws(bool async)
{
var contextFactory = await InitializeAsync<Context28671>(
onModelCreating: mb =>
{
mb.Entity<Owner>().HasOne<OtherReference>().WithOne().HasForeignKey<OtherReference>(e => e.Id);
mb.Entity<OtherReference>().ToTable(nameof(Owner));
});


await AssertTranslationFailedWithDetails(
() => AssertDelete(async, contextFactory.CreateContext,
context => context.Set<Owner>(), rowsAffectedCount: 0),
RelationalStrings.ExecuteDeleteOnTableSplitting(nameof(Owner)));
}

protected class Context28671 : DbContext
{
public Context28671(DbContextOptions options)
: base(options)
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Owner>(
b =>
{
b.OwnsOne(e => e.OwnedReference);
b.OwnsMany(e => e.OwnedCollections);
});
}
}

public class Owner
{
public int Id { get; set; }
public string Title { get; set; }

public OwnedReference OwnedReference { get; set; }
public List<OwnedCollection> OwnedCollections { get; set; }
}

public class OwnedReference
{
public int Number { get; set; }
public string Value { get; set; }
}

public class OwnedCollection
{
public string Value { get; set; }
}

public class OtherReference
{
public int Id { get; set; }
public int Number { get; set; }
public string Title { get; set; }
}

#nullable enable
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
Expand Down Expand Up @@ -135,6 +216,12 @@ await TestHelpers.ExecuteWithStrategyInTransactionAsync(
}
}

protected static async Task AssertTranslationFailedWithDetails(Func<Task> query, string details)
=> Assert.Contains(
RelationalStrings.NonQueryTranslationFailedWithDetails("", details)[21..],
(await Assert.ThrowsAsync<InvalidOperationException>(query))
.Message);

public void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
=> facade.UseTransaction(transaction.GetDbTransaction());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,31 @@ protected override ITestStoreFactory TestStoreFactory
public virtual void Check_all_tests_overridden()
=> TestHelpers.AssertAllMethodsOverridden(GetType());

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

AssertSql(
@"DELETE FROM [o]
FROM [Owner] AS [o]");
}

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

AssertSql(
@"DELETE FROM [o]
FROM [Owner] AS [o]");
}

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

AssertSql();
}

public override async Task Delete_predicate_based_on_optional_navigation(bool async)
{
await base.Delete_predicate_based_on_optional_navigation(async);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,29 @@ protected override ITestStoreFactory TestStoreFactory
public virtual void Check_all_tests_overridden()
=> TestHelpers.AssertAllMethodsOverridden(GetType());

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

AssertSql(
@"DELETE FROM ""Owner"" AS ""o""");
}

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

AssertSql(
@"DELETE FROM ""Owner"" AS ""o""");
}

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

AssertSql();
}

public override async Task Delete_predicate_based_on_optional_navigation(bool async)
{
await base.Delete_predicate_based_on_optional_navigation(async);
Expand All @@ -25,6 +48,9 @@ SELECT 1
WHERE ""b"".""Title"" IS NOT NULL AND (""b"".""Title"" LIKE 'Arthur%') AND ""p0"".""Id"" = ""p"".""Id"")");
}

protected override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
=> base.AddOptions(builder).ConfigureWarnings(wcb => wcb.Log(SqliteEventId.CompositeKeyWithValueGeneration));

private void AssertSql(params string[] expected)
=> TestSqlLoggerFactory.AssertBaseline(expected);

Expand Down

0 comments on commit c75abeb

Please sign in to comment.