Skip to content

Commit

Permalink
ExecuteDelete: Use Any path for unsupported query operator (#28755)
Browse files Browse the repository at this point in the history
Work-around for #28745
  • Loading branch information
smitpatel authored Aug 17, 2022
1 parent 2313620 commit 0a33473
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1079,21 +1079,21 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
var clrType = entityType.ClrType;
var entityParameter = Expression.Parameter(clrType);
Expression predicateBody;
if (pk.Properties.Count == 1)
{
predicateBody = Expression.Call(
QueryableMethods.Contains.MakeGenericMethod(clrType), source, entityParameter);
}
else
{
var innerParameter = Expression.Parameter(clrType);
predicateBody = Expression.Call(
QueryableMethods.AnyWithPredicate.MakeGenericMethod(clrType),
source,
Expression.Quote(Expression.Lambda(
Infrastructure.ExpressionExtensions.CreateEqualsExpression(innerParameter, entityParameter),
innerParameter)));
}
//if (pk.Properties.Count == 1)
//{
// predicateBody = Expression.Call(
// EnumerableMethods.Contains.MakeGenericMethod(clrType), source, entityParameter);
//}
//else
//{
var innerParameter = Expression.Parameter(clrType);
predicateBody = Expression.Call(
QueryableMethods.AnyWithPredicate.MakeGenericMethod(clrType),
source,
Expression.Quote(Expression.Lambda(
Infrastructure.ExpressionExtensions.CreateEqualsExpression(innerParameter, entityParameter),
innerParameter)));
//}

var newSource = Expression.Call(
QueryableMethods.Where.MakeGenericMethod(clrType),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.BulkUpdates;

public abstract class NonSharedModelBulkUpdatesTestBase : NonSharedModelTestBase
{
protected override string StoreName => "NonSharedModelBulkUpdatesTests";

#nullable enable
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Delete_predicate_based_on_optional_navigation(bool async)
{
var contextFactory = await InitializeAsync<Context28745>();
await AssertDelete(async, contextFactory.CreateContext,
context => context.Posts.Where(p => p.Blog!.Title!.StartsWith("Arthur")), rowsAffectedCount: 1);
}

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

public DbSet<Blog> Blogs => Set<Blog>();
public DbSet<Post> Posts => Set<Post>();

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasData(new Blog { Id = 1, Title = "Arthur" }, new Blog { Id = 2, Title = "Brice" });

modelBuilder.Entity<Post>()
.HasData(
new { Id = 1, BlogId = 1 },
new { Id = 2, BlogId = 2 },
new { Id = 3, BlogId = 2 });
}
}

public class Blog
{
public int Id { get; set; }
public string? Title { get; set; }

public virtual ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
public int Id { get; set; }
public virtual Blog? Blog { get; set; }
}

#nullable disable


#region HelperMethods

public async Task AssertDelete<TContext, TResult>(
bool async,
Func<TContext> contextCreator,
Func<TContext, IQueryable<TResult>> query,
int rowsAffectedCount)
where TContext : DbContext
{
if (async)
{
await TestHelpers.ExecuteWithStrategyInTransactionAsync(
contextCreator, UseTransaction,
async context =>
{
var processedQuery = query(context);
var result = await processedQuery.ExecuteDeleteAsync();
Assert.Equal(rowsAffectedCount, result);
});
}
else
{
TestHelpers.ExecuteWithStrategyInTransaction(
contextCreator, UseTransaction,
context =>
{
var processedQuery = query(context);
var result = processedQuery.ExecuteDelete();
Assert.Equal(rowsAffectedCount, result);
});
}
}

public async Task AssertUpdate<TContext, TResult>(
bool async,
Func<TContext> contextCreator,
Func<TContext, IQueryable<TResult>> query,
Expression<Func<SetPropertyStatements<TResult>, SetPropertyStatements<TResult>>> setPropertyStatements,
int rowsAffectedCount)
where TResult : class
where TContext : DbContext
{
if (async)
{
await TestHelpers.ExecuteWithStrategyInTransactionAsync(
contextCreator, UseTransaction,
async context =>
{
var processedQuery = query(context);
var result = await processedQuery.ExecuteUpdateAsync(setPropertyStatements);
Assert.Equal(rowsAffectedCount, result);
});
}
else
{
TestHelpers.ExecuteWithStrategyInTransaction(
contextCreator, UseTransaction,
context =>
{
var processedQuery = query(context);

var result = processedQuery.ExecuteUpdate(setPropertyStatements);

Assert.Equal(rowsAffectedCount, result);
});
}
}

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

protected TestSqlLoggerFactory TestSqlLoggerFactory
=> (TestSqlLoggerFactory)ListLoggerFactory;

protected void ClearLog()
=> TestSqlLoggerFactory.Clear();

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.TestModels.Northwind;
using Microsoft.VisualBasic;

namespace Microsoft.EntityFrameworkCore.BulkUpdates;

Expand Down Expand Up @@ -279,6 +278,16 @@ FROM [Order Details]
}
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Delete_Where_optional_navigation_predicate(bool async)
=> AssertDelete(
async,
ss => from od in ss.Set<OrderDetail>()
where od.Order.Customer.City.StartsWith("Se")
select od,
rowsAffectedCount: 66);

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Delete_with_join(bool async)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,7 @@ await TestHelpers.ExecuteWithStrategyInTransactionAsync(

var after = processedQuery.AsNoTracking().Select(entitySelector).OrderBy(elementSorter).ToList();

if (asserter != null)
{
asserter(before, after);
}
asserter?.Invoke(before, after);
});
}
else
Expand All @@ -104,10 +101,7 @@ await TestHelpers.ExecuteWithStrategyInTransactionAsync(

var after = processedQuery.AsNoTracking().Select(entitySelector).OrderBy(elementSorter).ToList();

if (asserter != null)
{
asserter(before, after);
}
asserter?.Invoke(before, after);
});
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.BulkUpdates;

public class NonSharedModelBulkUpdatesSqlServerTest : NonSharedModelBulkUpdatesTestBase
{
protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance;

[ConditionalFact]
public virtual void Check_all_tests_overridden()
=> TestHelpers.AssertAllMethodsOverridden(GetType());

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

AssertSql(
@"DELETE FROM [p]
FROM [Posts] AS [p]
LEFT JOIN [Blogs] AS [b] ON [p].[BlogId] = [b].[Id]
WHERE [b].[Title] IS NOT NULL AND ([b].[Title] LIKE N'Arthur%')");
}

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

private void AssertExecuteUpdateSql(params string[] expected)
=> TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true);
}
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,18 @@ SELECT 1
WHERE [m].[OrderID] = [o].[OrderID] AND [m].[ProductID] = [o].[ProductID])");
}

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

AssertSql(
@"DELETE FROM [o]
FROM [Order Details] AS [o]
INNER JOIN [Orders] AS [o0] ON [o].[OrderID] = [o0].[OrderID]
LEFT JOIN [Customers] AS [c] ON [o0].[CustomerID] = [c].[CustomerID]
WHERE [c].[City] IS NOT NULL AND ([c].[City] LIKE N'Se%')");
}

public override async Task Delete_with_join(bool async)
{
await base.Delete_with_join(async);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.BulkUpdates;

public class NonSharedModelBulkUpdatesSqliteTest : NonSharedModelBulkUpdatesTestBase
{
protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance;

[ConditionalFact]
public virtual void Check_all_tests_overridden()
=> TestHelpers.AssertAllMethodsOverridden(GetType());

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

AssertSql(
@"DELETE FROM ""Posts"" AS ""p""
WHERE EXISTS (
SELECT 1
FROM ""Posts"" AS ""p0""
LEFT JOIN ""Blogs"" AS ""b"" ON ""p0"".""BlogId"" = ""b"".""Id""
WHERE ""b"".""Title"" IS NOT NULL AND (""b"".""Title"" LIKE 'Arthur%') AND ""p0"".""Id"" = ""p"".""Id"")");
}

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

private void AssertExecuteUpdateSql(params string[] expected)
=> TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true);
}
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,20 @@ SELECT 1
WHERE ""m"".""OrderID"" = ""o"".""OrderID"" AND ""m"".""ProductID"" = ""o"".""ProductID"")");
}

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

AssertSql(
@"DELETE FROM ""Order Details"" AS ""o""
WHERE EXISTS (
SELECT 1
FROM ""Order Details"" AS ""o0""
INNER JOIN ""Orders"" AS ""o1"" ON ""o0"".""OrderID"" = ""o1"".""OrderID""
LEFT JOIN ""Customers"" AS ""c"" ON ""o1"".""CustomerID"" = ""c"".""CustomerID""
WHERE ""c"".""City"" IS NOT NULL AND (""c"".""City"" LIKE 'Se%') AND ""o0"".""OrderID"" = ""o"".""OrderID"" AND ""o0"".""ProductID"" = ""o"".""ProductID"")");
}

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

0 comments on commit 0a33473

Please sign in to comment.