Skip to content

Commit

Permalink
Implement ExecuteUpdate
Browse files Browse the repository at this point in the history
Resolves #795
  • Loading branch information
smitpatel committed Aug 9, 2022
1 parent e68de74 commit 045de73
Show file tree
Hide file tree
Showing 22 changed files with 854 additions and 147 deletions.
68 changes: 64 additions & 4 deletions src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ internal static readonly MethodInfo AsSplitQueryMethodInfo
#region ExecuteDelete

/// <summary>
/// Deletes all entity instances which match the LINQ query from the database.
/// Deletes all database rows for given entity instances which match the LINQ query from the database.
/// </summary>
/// <remarks>
/// <para>
Expand All @@ -251,12 +251,12 @@ internal static readonly MethodInfo AsSplitQueryMethodInfo
/// </para>
/// </remarks>
/// <param name="source">The source query.</param>
/// <returns>The total number of entity instances deleted from the database.</returns>
/// <returns>The total number of rows deleted in the database.</returns>
public static int ExecuteDelete<TSource>(this IQueryable<TSource> source)
=> source.Provider.Execute<int>(Expression.Call(ExecuteDeleteMethodInfo.MakeGenericMethod(typeof(TSource)), source.Expression));

/// <summary>
/// Asynchronously deletes all entity instances which match the LINQ query from the database.
/// Asynchronously deletes database rows for given entity instances which match the LINQ query from the database.
/// </summary>
/// <remarks>
/// <para>
Expand All @@ -272,7 +272,7 @@ public static int ExecuteDelete<TSource>(this IQueryable<TSource> source)
/// </remarks>
/// <param name="source">The source query.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>The total number of entity instances deleted from the database.</returns>
/// <returns>The total number of rows deleted in the database.</returns>
public static Task<int> ExecuteDeleteAsync<TSource>(this IQueryable<TSource> source, CancellationToken cancellationToken = default)
=> source.Provider is IAsyncQueryProvider provider
? provider.ExecuteAsync<Task<int>>(
Expand All @@ -283,4 +283,64 @@ internal static readonly MethodInfo ExecuteDeleteMethodInfo
= typeof(RelationalQueryableExtensions).GetTypeInfo().GetDeclaredMethod(nameof(ExecuteDelete))!;

#endregion

#region ExecuteUpdate

/// <summary>
/// Updates all database rows for given entity instances which match the LINQ query from the database.
/// </summary>
/// <remarks>
/// <para>
/// This operation executes immediately against the database, rather than being deferred until
/// <see cref="DbContext.SaveChanges()" /> is called. It also does not interact with the EF change tracker in any way:
/// entity instances which happen to be tracked when this operation is invoked aren't taken into account, and aren't updated
/// to reflect the changes.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-bulk-operations">Executing bulk operations with EF Core</see>
/// for more information and examples.
/// </para>
/// </remarks>
/// <param name="source">The source query.</param>
/// <param name="setPropertyStatements">A collection of set property statements specifying properties to update.</param>
/// <returns>The total number of rows updated in the database.</returns>
public static int ExecuteUpdate<TSource>(
this IQueryable<TSource> source,
Expression<Func<SetPropertyStatements<TSource>, SetPropertyStatements<TSource>>> setPropertyStatements)
=> source.Provider.Execute<int>(
Expression.Call(ExecuteUpdateMethodInfo.MakeGenericMethod(typeof(TSource)), source.Expression, setPropertyStatements));

/// <summary>
/// Asynchronously updates database rows for given entity instances which match the LINQ query from the database.
/// </summary>
/// <remarks>
/// <para>
/// This operation executes immediately against the database, rather than being deferred until
/// <see cref="DbContext.SaveChanges()" /> is called. It also does not interact with the EF change tracker in any way:
/// entity instances which happen to be tracked when this operation is invoked aren't taken into account, and aren't updated
/// to reflect the changes.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-bulk-operations">Executing bulk operations with EF Core</see>
/// for more information and examples.
/// </para>
/// </remarks>
/// <param name="source">The source query.</param>
/// <param name="setPropertyStatements">A collection of set property statements specifying properties to update.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>The total number of rows updated in the database.</returns>
public static Task<int> ExecuteUpdateAsync<TSource>(
this IQueryable<TSource> source,
Expression<Func<SetPropertyStatements<TSource>, SetPropertyStatements<TSource>>> setPropertyStatements,
CancellationToken cancellationToken = default)
=> source.Provider is IAsyncQueryProvider provider
? provider.ExecuteAsync<Task<int>>(
Expression.Call(
ExecuteUpdateMethodInfo.MakeGenericMethod(typeof(TSource)), source.Expression, setPropertyStatements), cancellationToken)
: throw new InvalidOperationException(CoreStrings.IQueryableProviderNotAsync);

internal static readonly MethodInfo ExecuteUpdateMethodInfo
= typeof(RelationalQueryableExtensions).GetTypeInfo().GetDeclaredMethod(nameof(ExecuteUpdate))!;

#endregion
}
1 change: 0 additions & 1 deletion src/EFCore.Relational/Query/EntityProjectionExpression.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

namespace Microsoft.EntityFrameworkCore.Query;
Expand Down
69 changes: 56 additions & 13 deletions src/EFCore.Relational/Query/NonQueryExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,58 @@

namespace Microsoft.EntityFrameworkCore.Query;

#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
/// <summary>
/// <para>
/// An expression that contains a non-query expression. The result of non-query expression is typically number of rows affected.
/// </para>
/// <para>
/// This type is typically used by database providers (and other extensions). It is generally
/// not used in application code.
/// </para>
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-providers">Implementation of database providers and extensions</see>
/// and <see href="https://aka.ms/efcore-docs-how-query-works">How EF Core queries work</see> for more information and examples.
/// </remarks>
public class NonQueryExpression : Expression, IPrintableExpression
{
/// <summary>
/// Creates a new instance of the <see cref="NonQueryExpression" /> class with associated query expression and command source.
/// </summary>
/// <param name="expression">The expression to affect rows on the server.</param>
/// <param name="commandSource">The command source to use for this non-query operation.</param>
public NonQueryExpression(Expression expression, CommandSource commandSource)
{
Expression = expression;
CommandSource = commandSource;
}

/// <summary>
/// Creates a new instance of the <see cref="NonQueryExpression" /> class with associated delete expression.
/// </summary>
/// <param name="deleteExpression">The delete expression to delete rows on the server.</param>
public NonQueryExpression(DeleteExpression deleteExpression)
: this(deleteExpression, CommandSource.ExecuteDelete)
{
}

public NonQueryExpression(DeleteExpression expression, CommandSource commandSource)
/// <summary>
/// Creates a new instance of the <see cref="NonQueryExpression" /> class with associated update expression.
/// </summary>
/// <param name="updateExpression">The update expression to update rows on the server.</param>
public NonQueryExpression(UpdateExpression updateExpression)
: this(updateExpression, CommandSource.ExecuteUpdate)
{
DeleteExpression = expression;
CommandSource = commandSource;
}

public virtual DeleteExpression DeleteExpression { get; }
/// <summary>
/// An expression representing the non-query to be run against server.
/// </summary>
public virtual Expression Expression { get; }

/// <summary>
/// The command source to use for this non-query operation.
/// </summary>
public virtual CommandSource CommandSource { get; }

/// <inheritdoc />
Expand All @@ -29,23 +65,30 @@ public NonQueryExpression(DeleteExpression expression, CommandSource commandSour
/// <inheritdoc />
public sealed override ExpressionType NodeType => ExpressionType.Extension;

/// <inheritdoc />
protected override Expression VisitChildren(ExpressionVisitor visitor)
{
var deleteExpression = (DeleteExpression)visitor.Visit(DeleteExpression);
var expression = visitor.Visit(Expression);

return Update(deleteExpression);
return Update(expression);
}

public virtual NonQueryExpression Update(DeleteExpression deleteExpression)
=> deleteExpression != DeleteExpression
? new NonQueryExpression(deleteExpression)
/// <summary>
/// Creates a new expression that is like this one, but using the supplied children. If all of the children are the same, it will
/// return this expression.
/// </summary>
/// <param name="expression">The <see cref="Expression" /> property of the result.</param>
/// <returns>This expression if no children changed, or an expression with the updated children.</returns>
public virtual NonQueryExpression Update(Expression expression)
=> expression != Expression
? new NonQueryExpression(expression, CommandSource)
: this;

/// <inheritdoc />
public virtual void Print(ExpressionPrinter expressionPrinter)
{
expressionPrinter.Append($"({nameof(NonQueryExpression)}: ");
expressionPrinter.Visit(DeleteExpression);
expressionPrinter.Visit(Expression);
}

/// <inheritdoc />
Expand All @@ -56,8 +99,8 @@ public override bool Equals(object? obj)
&& Equals(nonQueryExpression));

private bool Equals(NonQueryExpression nonQueryExpression)
=> DeleteExpression == nonQueryExpression.DeleteExpression;
=> Expression == nonQueryExpression.Expression;

/// <inheritdoc />
public override int GetHashCode() => DeleteExpression.GetHashCode();
public override int GetHashCode() => Expression.GetHashCode();
}
52 changes: 47 additions & 5 deletions src/EFCore.Relational/Query/QuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,9 @@ protected virtual void GenerateRootCommand(Expression queryExpression)
}
break;

case DeleteExpression deleteExpression:
VisitDelete(deleteExpression);
break;

default:
throw new InvalidOperationException();
base.Visit(queryExpression);
break;
}
}

Expand Down Expand Up @@ -1229,4 +1226,49 @@ protected override Expression VisitUnion(UnionExpression unionExpression)

return unionExpression;
}

/// <inheritdoc />
protected override Expression VisitUpdate(UpdateExpression updateExpression)
{
var selectExpression = updateExpression.SelectExpression;

if (selectExpression.Offset == null
&& selectExpression.Limit == null
&& selectExpression.Having == null
&& selectExpression.Orderings.Count == 0
&& selectExpression.GroupBy.Count == 0
&& selectExpression.Tables.Count == 1
&& selectExpression.Tables[0] == updateExpression.Table
&& selectExpression.Projection.Count == 0)
{
_relationalCommandBuilder.Append("UPDATE ");
Visit(updateExpression.Table);
_relationalCommandBuilder.AppendLine();
using (_relationalCommandBuilder.Indent())
{
_relationalCommandBuilder.Append("SET ");
GenerateList(updateExpression.SetColumnValues,
e =>
{
Visit(e.Column);
_relationalCommandBuilder.Append(" = ");
Visit(e.Value);

},
joinAction: e => e.AppendLine(","));
_relationalCommandBuilder.AppendLine();
}

if (selectExpression.Predicate != null)
{
_relationalCommandBuilder.AppendLine().Append("WHERE ");
Visit(selectExpression.Predicate);
}

return updateExpression;
}

throw new InvalidOperationException(
RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration(nameof(RelationalQueryableExtensions.ExecuteUpdate)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ private sealed class TableAliasVerifyingExpressionVisitor : ExpressionVisitor
return relationalSplitCollectionShaperExpression;

case NonQueryExpression nonQueryExpression:
VerifyUniqueAliasInExpression(nonQueryExpression.DeleteExpression);
VerifyUniqueAliasInExpression(nonQueryExpression.Expression);
return nonQueryExpression;

default:
Expand Down
Loading

0 comments on commit 045de73

Please sign in to comment.