Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ExecuteDelete #28492

Merged
1 commit merged into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/helix.proj
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,6 @@
<XUnitRuntimeTargetFramework>netcoreapp2.0</XUnitRuntimeTargetFramework>
<XUnitRunnerVersion>2.4.2-pre.9</XUnitRunnerVersion>
<XUnitArguments></XUnitArguments>
<XUnitWorkItemTimeout Condition = "'$(XUnitWorkItemTimeout)' == ''">01:00:00</XUnitWorkItemTimeout>
<XUnitWorkItemTimeout Condition = "'$(XUnitWorkItemTimeout)' == ''">02:00:00</XUnitWorkItemTimeout>
</PropertyGroup>
</Project>
14 changes: 14 additions & 0 deletions src/EFCore.Relational/Diagnostics/RelationalEventId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ private enum Id
Obsolete_QueryPossibleExceptionWithAggregateOperatorWarning,
Obsolete_ValueConversionSqlLiteralWarning,
MultipleCollectionIncludeWarning,
BulkOperationFailed,

// Model validation events
ModelValidationKeyDefaultValueWarning = CoreEventId.RelationalBaseId + 600,
Expand Down Expand Up @@ -741,6 +742,19 @@ private static EventId MakeQueryId(Id id)
/// </remarks>
public static readonly EventId MultipleCollectionIncludeWarning = MakeQueryId(Id.MultipleCollectionIncludeWarning);

/// <summary>
/// An error occurred while executing a bulk operation.
/// </summary>
/// <remarks>
/// <para>
/// This event is in the <see cref="DbLoggerCategory.Query" /> category.
/// </para>
/// <para>
/// This event uses the <see cref="DbContextTypeErrorEventData" /> payload when used with a <see cref="DiagnosticSource" />.
/// </para>
/// </remarks>
public static readonly EventId BulkOperationFailed = MakeQueryId(Id.BulkOperationFailed);
smitpatel marked this conversation as resolved.
Show resolved Hide resolved

private static readonly string _validationPrefix = DbLoggerCategory.Model.Validation.Name + ".";

private static EventId MakeValidationId(Id id)
Expand Down
40 changes: 40 additions & 0 deletions src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2312,6 +2312,46 @@ private static string QueryPossibleUnintendedUseOfEqualsWarning(EventDefinitionB
return d.GenerateMessage(p.Left.Print(), p.Right.Print());
}

/// <summary>
/// Logs for the <see cref="RelationalEventId.BulkOperationFailed" /> event.
/// </summary>
/// <param name="diagnostics">The diagnostics logger to use.</param>
/// <param name="contextType">The <see cref="DbContext" /> type being used.</param>
/// <param name="exception">The exception that caused this failure.</param>
public static void BulkOperationFailed(
this IDiagnosticsLogger<DbLoggerCategory.Query> diagnostics,
Type contextType,
Exception exception)
{
var definition = RelationalResources.LogExceptionDuringBulkOperation(diagnostics);

if (diagnostics.ShouldLog(definition))
{
definition.Log(
diagnostics,
contextType, Environment.NewLine, exception,
exception);
}

if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = new DbContextTypeErrorEventData(
definition,
BulkOperationFailed,
contextType,
exception);

diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
}
}

private static string BulkOperationFailed(EventDefinitionBase definition, EventData payload)
{
var d = (EventDefinition<Type, string, Exception>)definition;
var p = (DbContextTypeErrorEventData)payload;
return d.GenerateMessage(p.ContextType, Environment.NewLine, p.Exception);
}

/// <summary>
/// Logs for the <see cref="RelationalEventId.MultipleCollectionIncludeWarning" /> event.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -636,4 +636,13 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions
/// </summary>
[EntityFrameworkInternal]
public EventDefinitionBase? LogColumnOrderIgnoredWarning;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
public EventDefinitionBase? LogExceptionDuringBulkOperation;
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public static DbCommand CreateDbCommand(this IQueryable source)
throw new NotSupportedException(RelationalStrings.NoDbCommand);
}

#region FromSql

/// <summary>
/// Creates a LINQ query based on a raw SQL query.
/// </summary>
Expand Down Expand Up @@ -162,6 +164,10 @@ private static FromSqlQueryRootExpression GenerateFromSqlQueryRoot(
Expression.Constant(arguments));
}

#endregion

#region SplitQuery

/// <summary>
/// Returns a new query which is configured to load the collections in the query results in a single database query.
/// </summary>
Expand Down Expand Up @@ -224,4 +230,57 @@ public static IQueryable<TEntity> AsSplitQuery<TEntity>(

internal static readonly MethodInfo AsSplitQueryMethodInfo
= typeof(RelationalQueryableExtensions).GetTypeInfo().GetDeclaredMethod(nameof(AsSplitQuery))!;

#endregion

#region ExecuteDelete

/// <summary>
/// Deletes all 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>
/// <returns>The total number of entity instances deleted from 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.
/// </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="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>
public static Task<int> ExecuteDeleteAsync<TSource>(this IQueryable<TSource> source, CancellationToken cancellationToken = default)
=> source.Provider is IAsyncQueryProvider provider
? provider.ExecuteAsync<Task<int>>(
Expression.Call(ExecuteDeleteMethodInfo.MakeGenericMethod(typeof(TSource)), source.Expression), cancellationToken)
: throw new InvalidOperationException(CoreStrings.IQueryableProviderNotAsync);

internal static readonly MethodInfo ExecuteDeleteMethodInfo
= typeof(RelationalQueryableExtensions).GetTypeInfo().GetDeclaredMethod(nameof(ExecuteDelete))!;

#endregion
}
89 changes: 89 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,27 @@
<data name="ErrorMaterializingValueNullReference" xml:space="preserve">
<value>An error occurred while reading a database value. The expected type was '{expectedType}' but the actual value was null.</value>
</data>
<data name="ExecuteDeleteOnTableSplitting" xml:space="preserve">
<value>The operation '{operation}' is being applied on the table '{tableName}' which contains data for multiple entity types. Applying this delete operation will also delete data for other entity type(s), hence it is not supported.</value>
</data>
<data name="ExecuteOperationOnEntitySplitting" xml:space="preserve">
<value>The operation '{operation}' is being applied on entity type '{entityType}', which uses entity splitting. 'ExecuteDelete'/'ExecuteUpdate' operations on entity types using entity splitting is not supported.</value>
</data>
<data name="ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator" xml:space="preserve">
<value>The operation '{operation}' cannot be performed on keyless entity type '{entityType}', since it contains an operator not natively supported by the database provider.</value>
</data>
<data name="ExecuteOperationOnNonEntityType" xml:space="preserve">
<value>The operation '{operation}' requires an entity type which corresponds to the database table to be modified. The current operation is being applied on a non-entity projection. Remove any projection to non-entity types.</value>
</data>
<data name="ExecuteOperationOnTPC" xml:space="preserve">
<value>The operation '{operation}' is being applied on entity type '{entityType}', which is using the TPC mapping strategy and is not a leaf type. 'ExecuteDelete'/'ExecuteUpdate' operations on entity types participating in TPC hierarchies is only supported for leaf types.</value>
</data>
<data name="ExecuteOperationOnTPT" xml:space="preserve">
<value>The operation '{operation}' is being applied on entity type '{entityType}', which is using the TPT mapping strategy. 'ExecuteDelete'/'ExecuteUpdate' operations on hierarchies mapped as TPT is not supported.</value>
</data>
<data name="ExecuteOperationWithUnsupportedOperatorInSqlGeneration" xml:space="preserve">
<value>The operation '{operation}' contains a select expression feature that isn't supported in the query SQL generator, but has been declared as supported by provider during translation phase. This is a bug in your EF Core provider, please file an issue.</value>
</data>
<data name="FromSqlMissingColumn" xml:space="preserve">
<value>The required column '{column}' was not present in the results of a 'FromSql' operation.</value>
</data>
Expand Down Expand Up @@ -591,6 +612,10 @@
<value>The configured column orders for the table '{table}' contains duplicates. Ensure the specified column order values are distinct. Conflicting columns: {columns}</value>
<comment>Error RelationalEventId.DuplicateColumnOrders string string</comment>
</data>
<data name="LogExceptionDuringBulkOperation" xml:space="preserve">
<value>An exception occurred while executing a bulk operation for context type '{contextType}'.{newline}{error}</value>
<comment>Error RelationalEventId.BulkOperationFailed Type string Exception</comment>
</data>
<data name="LogExecutedCommand" xml:space="preserve">
<value>Executed DbCommand ({elapsed}ms) [Parameters=[{parameters}], CommandType='{commandType}', CommandTimeout='{commandTimeout}']{newLine}{commandText}</value>
<comment>Information RelationalEventId.CommandExecuted string string System.Data.CommandType int string string</comment>
Expand Down Expand Up @@ -792,6 +817,9 @@
<data name="NoneRelationalTypeMappingOnARelationalTypeMappingSource" xml:space="preserve">
<value>'FindMapping' was called on a 'RelationalTypeMappingSource' with a non-relational 'TypeMappingInfo'.</value>
</data>
<data name="NonQueryTranslationFailedWithDetails" xml:space="preserve">
<value>The LINQ expression '{expression}' could not be translated. Additional information: {details} See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.</value>
</data>
<data name="NonScalarFunctionCannotBeNullable" xml:space="preserve">
<value>Cannot set 'IsNullable' on DbFunction '{functionName}' since the function does not represent a scalar function.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public override bool Equals(object? obj)

public bool Equals(CommandCacheKey commandCacheKey)
{
// Intentionally reference equals
// Intentionally reference equal, don't check internal components
if (!ReferenceEquals(_queryExpression, commandCacheKey._queryExpression))
{
return false;
Expand Down
Loading