From 2c6ba86e5ae9ed166ddbd424c4a7f37195549cf8 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 26 Jul 2022 22:17:10 +0300 Subject: [PATCH] Some docs/exception messages for bulk delete (#28523) * Some docs/exception messages for bulk delete * Address review comments --- .../RelationalQueryableExtensions.cs | 32 ++++++++++++++++--- .../Properties/RelationalStrings.Designer.cs | 14 ++++++++ .../Properties/RelationalStrings.resx | 6 ++++ .../Query/QuerySqlGenerator.cs | 3 +- ...RelationalQueryTranslationPostprocessor.cs | 3 +- ...yableMethodTranslatingExpressionVisitor.cs | 14 ++++++-- ...alShapedQueryCompilingExpressionVisitor.cs | 7 ++-- .../Internal/SqlServerQuerySqlGenerator.cs | 2 +- 8 files changed, 67 insertions(+), 14 deletions(-) diff --git a/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs b/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs index 52271345704..45d135b1c2f 100644 --- a/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs @@ -236,19 +236,43 @@ internal static readonly MethodInfo AsSplitQueryMethodInfo #region BulkDelete /// - /// TBD + /// Deletes all entity instances which match the LINQ query from the database. /// + /// + /// + /// This operation executes immediately against the database, rather than being deferred until + /// 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. + /// + /// + /// See Executing bulk operations with EF Core + /// for more information and examples. + /// + /// /// The source query. - /// TBD + /// The total number of entity instances deleted from the database. public static int BulkDelete(this IQueryable source) => source.Provider.Execute(Expression.Call(BulkDeleteMethodInfo.MakeGenericMethod(typeof(TSource)), source.Expression)); /// - /// TBD + /// Asynchronously deletes all entity instances which match the LINQ query from the database. /// + /// + /// + /// This operation executes immediately against the database, rather than being deferred until + /// 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. + /// + /// + /// See Executing bulk operations with EF Core + /// for more information and examples. + /// + /// /// The source query. /// A to observe while waiting for the task to complete. - /// TBD + /// The total number of entity instances deleted from the database. public static Task BulkDeleteAsync(this IQueryable source, CancellationToken cancellationToken = default) => source.Provider is IAsyncQueryProvider provider ? provider.ExecuteAsync>( diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index eed94e6bbd5..5053bf88afe 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -47,6 +47,20 @@ public static string BadSequenceString public static string BadSequenceType => GetString("BadSequenceType"); + /// + /// The bulk operation cannot be performed on keyless entity type '{entityType}', since it contains an operator not natively supported by the database provider. + /// + public static string BulkOperationOnKeylessEntityTypeWithUnsupportedOperator(object? entityType) + => string.Format( + GetString("BulkOperationOnKeylessEntityTypeWithUnsupportedOperator", nameof(entityType)), + entityType); + + /// + /// The bulk operation contains a select expression feature that isn't supported in the query SQL generator, but has been declared as supported in RelationalQueryableMethodTranslatingExpressionVisitor. + /// + public static string BulkOperationWithUnsupportedOperatorInSqlGeneration + => GetString("BulkOperationWithUnsupportedOperatorInSqlGeneration"); + /// /// The instance of DbConnection is currently in use. The connection can only be changed when the existing connection is not being used. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 5a38d8f7d7a..6b766fc516f 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -127,6 +127,12 @@ Invalid type for sequence. Valid types are long (the default), int, short, byte and decimal. + + The bulk operation cannot be performed on keyless entity type '{entityType}', since it contains an operator not natively supported by the database provider. + + + The bulk operation contains a select expression feature that isn't supported in the query SQL generator, but has been declared as supported in RelationalQueryableMethodTranslatingExpressionVisitor. + The instance of DbConnection is currently in use. The connection can only be changed when the existing connection is not being used. diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs index a3e1e23ce29..f85e4373be5 100644 --- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs @@ -176,8 +176,7 @@ protected override Expression VisitDelete(DeleteExpression deleteExpression) } else { - // TODO: Exception message - throw new InvalidOperationException(); + throw new InvalidOperationException(RelationalStrings.BulkOperationWithUnsupportedOperatorInSqlGeneration); } return deleteExpression; diff --git a/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs b/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs index 603e1d506d9..fe29f5a2393 100644 --- a/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs @@ -64,8 +64,7 @@ private sealed class SelectExpressionMutableVerifyingExpressionVisitor : Express { switch (expression) { - case SelectExpression selectExpression - when selectExpression.IsMutable(): + case SelectExpression selectExpression when selectExpression.IsMutable(): throw new InvalidDataException(selectExpression.Print()); case ShapedQueryExpression shapedQueryExpression: diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 390185ca04b..88bec48a983 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -1029,8 +1029,8 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp } else { - // TODO: Exception message - throw new InvalidOperationException("specific message about keyless"); + throw new InvalidOperationException( + RelationalStrings.BulkOperationOnKeylessEntityTypeWithUnsupportedOperator(entityType.DisplayName())); } } } @@ -1042,6 +1042,16 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp /// /// Validates if the current select expression can be used for bulk delete operation or it requires to be pushed into a subquery. /// + /// + /// + /// By default, only single-table select expressions are supported, and only with a predicate. + /// + /// + /// Providers can override this to allow more select expression features to be supported without pushing down into a subquery. + /// When doing this, VisitDelete must also be overridden in the provider's QuerySqlGenerator to add SQL generation support for + /// the feature. + /// + /// /// The select expression to validate. /// The entity shaper expression on which delete operation is being applied. /// The table expression from which rows are being deleted. diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs index 9006994de26..419a7487d8a 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs @@ -49,10 +49,11 @@ protected override Expression VisitExtension(Expression extensionExpression) : base.VisitExtension(extensionExpression); /// - /// asdagv + /// Visits the given , returning an expression that when compiled, can execute the non- + /// query operation against the database. /// - /// asdasfdsa - /// asdasd + /// The expression to be compiled. + /// An expression which executes a non-query operation. protected virtual Expression VisitNonQuery(NonQueryExpression nonQueryExpression) { var relationalCommandCache = new RelationalCommandCache( diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs index ce1fcc4686c..83d9250f5c5 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs @@ -67,7 +67,7 @@ protected override Expression VisitDelete(DeleteExpression deleteExpression) } else { - throw new NotSupportedException(); + throw new InvalidOperationException(RelationalStrings.BulkOperationWithUnsupportedOperatorInSqlGeneration); } return deleteExpression;