Skip to content

Commit

Permalink
Add support for TagWith on ExecuteDelete/Update (#28782)
Browse files Browse the repository at this point in the history
Resolves #28690
  • Loading branch information
smitpatel committed Aug 19, 2022
1 parent 5857067 commit be33de6
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 5 deletions.
30 changes: 29 additions & 1 deletion src/EFCore.Relational/Query/QuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ protected virtual void GenerateRootCommand(Expression queryExpression)
switch (queryExpression)
{
case SelectExpression selectExpression:
GenerateTagsHeaderComment(selectExpression);
GenerateTagsHeaderComment(selectExpression.Tags);

if (selectExpression.IsNonComposedFromSql())
{
Expand All @@ -95,6 +95,16 @@ protected virtual void GenerateRootCommand(Expression queryExpression)
}
break;

case UpdateExpression updateExpression:
GenerateTagsHeaderComment(updateExpression.Tags);
VisitUpdate(updateExpression);
break;

case DeleteExpression deleteExpression:
GenerateTagsHeaderComment(deleteExpression.Tags);
VisitDelete(deleteExpression);
break;

default:
base.Visit(queryExpression);
break;
Expand All @@ -117,6 +127,7 @@ protected virtual IRelationalCommandBuilder Sql
/// Generates the head comment for tags.
/// </summary>
/// <param name="selectExpression">A select expression to generate tags for.</param>
[Obsolete("Use the method which takes tags instead.")]
protected virtual void GenerateTagsHeaderComment(SelectExpression selectExpression)
{
if (selectExpression.Tags.Count > 0)
Expand All @@ -130,6 +141,23 @@ protected virtual void GenerateTagsHeaderComment(SelectExpression selectExpressi
}
}

/// <summary>
/// Generates the head comment for tags.
/// </summary>
/// <param name="tags">A set of tags to print as comment.</param>
protected virtual void GenerateTagsHeaderComment(ISet<string> tags)
{
if (tags.Count > 0)
{
foreach (var tag in tags)
{
_relationalCommandBuilder.AppendLines(_sqlGenerationHelper.GenerateComment(tag));
}

_relationalCommandBuilder.AppendLine();
}
}

/// <inheritdoc />
protected override Expression VisitSqlFragment(SqlFragmentExpression sqlFragmentExpression)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,24 @@ protected override Expression VisitExtension(Expression extensionExpression)
/// <returns>An expression which executes a non-query operation.</returns>
protected virtual Expression VisitNonQuery(NonQueryExpression nonQueryExpression)
{
// Apply tags
var innerExpression = nonQueryExpression.Expression;
switch (innerExpression)
{
case UpdateExpression updateExpression:
innerExpression = updateExpression.ApplyTags(_tags);
break;

case DeleteExpression deleteExpression:
innerExpression = deleteExpression.ApplyTags(_tags);
break;
}

var relationalCommandCache = new RelationalCommandCache(
Dependencies.MemoryCache,
RelationalDependencies.QuerySqlGeneratorFactory,
RelationalDependencies.RelationalParameterBasedSqlProcessorFactory,
nonQueryExpression.Expression,
innerExpression,
_useRelationalNulls);

return Expression.Call(
Expand Down
25 changes: 24 additions & 1 deletion src/EFCore.Relational/Query/SqlExpressions/DeleteExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,22 @@ public sealed class DeleteExpression : Expression, IPrintableExpression
/// <param name="table">A table on which the delete operation is being applied.</param>
/// <param name="selectExpression">A select expression which is used to determine which rows to delete.</param>
public DeleteExpression(TableExpression table, SelectExpression selectExpression)
: this(table, selectExpression, new HashSet<string>())
{
}

private DeleteExpression(TableExpression table, SelectExpression selectExpression, ISet<string> tags)
{
Table = table;
SelectExpression = selectExpression;
Tags = tags;
}

/// <summary>
/// The list of tags applied to this <see cref="DeleteExpression" />.
/// </summary>
public ISet<string> Tags { get; }

/// <summary>
/// The table on which the delete operation is being applied.
/// </summary>
Expand All @@ -34,6 +45,13 @@ public DeleteExpression(TableExpression table, SelectExpression selectExpression
/// </summary>
public SelectExpression SelectExpression { get; }

/// <summary>
/// Applies a given set of tags.
/// </summary>
/// <param name="tags">A list of tags to apply.</param>
public DeleteExpression ApplyTags(ISet<string> tags)
=> new(Table, SelectExpression, tags);

/// <inheritdoc />
public override Type Type
=> typeof(object);
Expand All @@ -58,12 +76,17 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
/// <returns>This expression if no children changed, or an expression with the updated children.</returns>
public DeleteExpression Update(SelectExpression selectExpression)
=> selectExpression != SelectExpression
? new DeleteExpression(Table, selectExpression)
? new DeleteExpression(Table, selectExpression, Tags)
: this;

/// <inheritdoc />
public void Print(ExpressionPrinter expressionPrinter)
{
foreach (var tag in Tags)
{
expressionPrinter.Append($"-- {tag}");
}
expressionPrinter.AppendLine();
expressionPrinter.AppendLine($"DELETE FROM {Table.Name} AS {Table.Alias}");
expressionPrinter.Visit(SelectExpression);
}
Expand Down
26 changes: 25 additions & 1 deletion src/EFCore.Relational/Query/SqlExpressions/UpdateExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,24 @@ public sealed class UpdateExpression : Expression, IPrintableExpression
/// <param name="selectExpression">A select expression which is used to determine which rows to update and to get data from additional tables.</param>
/// <param name="setColumnValues">A list of <see cref="SetColumnValue"/> which specifies columns and their corresponding values to update.</param>
public UpdateExpression(TableExpression table, SelectExpression selectExpression, IReadOnlyList<SetColumnValue> setColumnValues)
: this(table, selectExpression, setColumnValues, new HashSet<string>())
{
}

private UpdateExpression(
TableExpression table, SelectExpression selectExpression, IReadOnlyList<SetColumnValue> setColumnValues, ISet<string> tags)
{
Table = table;
SelectExpression = selectExpression;
SetColumnValues = setColumnValues;
Tags = tags;
}

/// <summary>
/// The list of tags applied to this <see cref="UpdateExpression" />.
/// </summary>
public ISet<string> Tags { get; }

/// <summary>
/// The table on which the update operation is being applied.
/// </summary>
Expand All @@ -42,6 +54,13 @@ public UpdateExpression(TableExpression table, SelectExpression selectExpression
/// </summary>
public IReadOnlyList<SetColumnValue> SetColumnValues { get; }

/// <summary>
/// Applies a given set of tags.
/// </summary>
/// <param name="tags">A list of tags to apply.</param>
public UpdateExpression ApplyTags(ISet<string> tags)
=> new(Table, SelectExpression, SetColumnValues, tags);

/// <inheritdoc />
public override Type Type
=> typeof(object);
Expand Down Expand Up @@ -89,12 +108,17 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
/// <returns>This expression if no children changed, or an expression with the updated children.</returns>
public UpdateExpression Update(SelectExpression selectExpression, IReadOnlyList<SetColumnValue> setColumnValues)
=> selectExpression != SelectExpression || !SetColumnValues.SequenceEqual(setColumnValues)
? new UpdateExpression(Table, selectExpression, setColumnValues)
? new UpdateExpression(Table, selectExpression, setColumnValues, Tags)
: this;

/// <inheritdoc />
public void Print(ExpressionPrinter expressionPrinter)
{
foreach (var tag in Tags)
{
expressionPrinter.Append($"-- {tag}");
}
expressionPrinter.AppendLine();
expressionPrinter.AppendLine($"UPDATE {Table.Name} AS {Table.Alias}");
expressionPrinter.AppendLine("SET");
using (expressionPrinter.Indent())
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ private UpdateExpression VisitUpdate(UpdateExpression updateExpression)

return selectExpression != updateExpression.SelectExpression
|| setColumnValues != null
? new UpdateExpression(updateExpression.Table, selectExpression, setColumnValues ?? updateExpression.SetColumnValues)
? updateExpression.Update(selectExpression, setColumnValues ?? updateExpression.SetColumnValues)
: updateExpression;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ protected NorthwindBulkUpdatesTestBase(TFixture fixture)
ClearLog();
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Delete_Where_TagWith(bool async)
=> AssertDelete(
async,
ss => ss.Set<OrderDetail>().Where(e => e.OrderID < 10300).TagWith("MyDelete"),
rowsAffectedCount: 140);

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Delete_Where(bool async)
Expand Down Expand Up @@ -350,6 +358,17 @@ from o in ss.Set<Order>().Where(o => o.OrderID < od.OrderID).OrderBy(e => e.Orde
select od,
rowsAffectedCount: 74);

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Update_Where_set_constant_TagWith(bool async)
=> AssertUpdate(
async,
ss => ss.Set<Customer>().Where(c => c.CustomerID.StartsWith("F")).TagWith("MyUpdate"),
e => e,
s => s.SetProperty(c => c.ContactName, c => "Updated"),
rowsAffectedCount: 8,
(b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName)));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Update_Where_set_constant(bool async)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ public NorthwindBulkUpdatesSqlServerTest(NorthwindBulkUpdatesSqlServerFixture<No
public virtual void Check_all_tests_overridden()
=> TestHelpers.AssertAllMethodsOverridden(GetType());

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

AssertSql(
@"-- MyDelete
DELETE FROM [o]
FROM [Order Details] AS [o]
WHERE [o].[OrderID] < 10300");
}

public override async Task Delete_Where(bool async)
{
await base.Delete_Where(async);
Expand Down Expand Up @@ -532,6 +544,19 @@ OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY
WHERE [o].[OrderID] < 10276");
}

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

AssertExecuteUpdateSql(
@"-- MyUpdate
UPDATE [c]
SET [c].[ContactName] = N'Updated'
FROM [Customers] AS [c]
WHERE [c].[CustomerID] LIKE N'F%'");
}

public override async Task Update_Where_set_constant(bool async)
{
await base.Update_Where_set_constant(async);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ public NorthwindBulkUpdatesSqliteTest(NorthwindBulkUpdatesSqliteFixture<NoopMode
public virtual void Check_all_tests_overridden()
=> TestHelpers.AssertAllMethodsOverridden(GetType());

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

AssertSql(
@"-- MyDelete
DELETE FROM ""Order Details"" AS ""o""
WHERE ""o"".""OrderID"" < 10300");
}

public override async Task Delete_Where(bool async)
{
await base.Delete_Where(async);
Expand Down Expand Up @@ -516,6 +527,18 @@ public override async Task Delete_with_outer_apply(bool async)
SqliteStrings.ApplyNotSupported,
(await Assert.ThrowsAsync<InvalidOperationException>(() => base.Delete_with_outer_apply(async))).Message);

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

AssertExecuteUpdateSql(
@"-- MyUpdate
UPDATE ""Customers"" AS ""c""
SET ""ContactName"" = 'Updated'
WHERE ""c"".""CustomerID"" LIKE 'F%'");
}

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

0 comments on commit be33de6

Please sign in to comment.