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

Add SaveChanges events #21862

Merged
merged 1 commit into from
Jul 30, 2020
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
46 changes: 43 additions & 3 deletions src/EFCore/DbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,8 @@ public virtual int SaveChanges(bool acceptAllChangesOnSuccess)
{
CheckDisposed();

SavingChanges?.Invoke(this, new SavingChangesEventArgs(acceptAllChangesOnSuccess));

var interceptionResult = DbContextDependencies.UpdateLogger.SaveChangesStarting(this);

TryDetectChanges();
Expand All @@ -496,18 +498,26 @@ public virtual int SaveChanges(bool acceptAllChangesOnSuccess)
? interceptionResult.Result
: DbContextDependencies.StateManager.SaveChanges(acceptAllChangesOnSuccess);

return DbContextDependencies.UpdateLogger.SaveChangesCompleted(this, entitiesSaved);
var result = DbContextDependencies.UpdateLogger.SaveChangesCompleted(this, entitiesSaved);

SavedChanges?.Invoke(this, new SavedChangesEventArgs(acceptAllChangesOnSuccess, result));

return result;
}
catch (DbUpdateConcurrencyException exception)
{
DbContextDependencies.UpdateLogger.OptimisticConcurrencyException(this, exception);

SaveChangesFailed?.Invoke(this, new SaveChangesFailedEventArgs(acceptAllChangesOnSuccess, exception));

throw;
}
catch (Exception exception)
{
DbContextDependencies.UpdateLogger.SaveChangesFailed(this, exception);

SaveChangesFailed?.Invoke(this, new SaveChangesFailedEventArgs(acceptAllChangesOnSuccess, exception));

throw;
}
}
Expand Down Expand Up @@ -595,6 +605,8 @@ public virtual async Task<int> SaveChangesAsync(
{
CheckDisposed();

SavingChanges?.Invoke(this, new SavingChangesEventArgs(acceptAllChangesOnSuccess));

var interceptionResult = await DbContextDependencies.UpdateLogger
.SaveChangesStartingAsync(this, cancellationToken).ConfigureAwait(false);

Expand All @@ -608,24 +620,48 @@ public virtual async Task<int> SaveChangesAsync(
.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken)
.ConfigureAwait(false);

return await DbContextDependencies.UpdateLogger
var result = await DbContextDependencies.UpdateLogger
.SaveChangesCompletedAsync(this, entitiesSaved, cancellationToken)
.ConfigureAwait(false);

SavedChanges?.Invoke(this, new SavedChangesEventArgs(acceptAllChangesOnSuccess, result));

return result;
}
catch (DbUpdateConcurrencyException exception)
{
DbContextDependencies.UpdateLogger.OptimisticConcurrencyException(this, exception);
await DbContextDependencies.UpdateLogger.OptimisticConcurrencyExceptionAsync(this, exception, cancellationToken)
.ConfigureAwait(false);

SaveChangesFailed?.Invoke(this, new SaveChangesFailedEventArgs(acceptAllChangesOnSuccess, exception));

throw;
}
catch (Exception exception)
{
await DbContextDependencies.UpdateLogger.SaveChangesFailedAsync(this, exception, cancellationToken).ConfigureAwait(false);

SaveChangesFailed?.Invoke(this, new SaveChangesFailedEventArgs(acceptAllChangesOnSuccess, exception));

throw;
}
}

/// <summary>
/// An event fired at the beginning of a call to <see cref="M:SaveChanges"/> or <see cref="M:SaveChangesAsync"/>
/// </summary>
public event EventHandler<SavingChangesEventArgs> SavingChanges;

/// <summary>
/// An event fired at the end of a call to <see cref="M:SaveChanges"/> or <see cref="M:SaveChangesAsync"/>
/// </summary>
public event EventHandler<SavedChangesEventArgs> SavedChanges;

/// <summary>
/// An event fired if a call to <see cref="M:SaveChanges"/> or <see cref="M:SaveChangesAsync"/> fails with an exception.
/// </summary>
public event EventHandler<SaveChangesFailedEventArgs> SaveChangesFailed;

/// <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
Expand Down Expand Up @@ -705,6 +741,10 @@ void IResettableService.ResetState()
service.ResetState();
}

SavingChanges = null;
SavedChanges = null;
SaveChangesFailed = null;

_disposed = true;
}

Expand Down
62 changes: 52 additions & 10 deletions src/EFCore/Diagnostics/CoreLoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,7 @@ public static void SaveChangesFailed(

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

if (interceptor != null)
{
interceptor.SaveChangesFailed(eventData);
}
interceptor?.SaveChangesFailed(eventData);
}
}

Expand Down Expand Up @@ -140,18 +137,63 @@ public static void OptimisticConcurrencyException(
definition.Log(diagnostics, exception);
}

if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
if (diagnostics.NeedsEventData<ISaveChangesInterceptor>(definition,
out var interceptor, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = new DbContextErrorEventData(
definition,
OptimisticConcurrencyException,
context,
exception);
var eventData = CreateDbContextErrorEventData(context, exception, definition);

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

interceptor?.SaveChangesFailed(eventData);
}
}

/// <summary>
/// Logs for the <see cref="CoreEventId.OptimisticConcurrencyException" /> event.
/// </summary>
/// <param name="diagnostics"> The diagnostics logger to use. </param>
/// <param name="context"> The context in use. </param>
/// <param name="exception"> The exception that caused this event. </param>
/// <param name="cancellationToken"> The cancellation token. </param>
/// <returns> A <see cref="ValueTask"/> for the async result. </returns>
public static ValueTask OptimisticConcurrencyExceptionAsync(
[NotNull] this IDiagnosticsLogger<DbLoggerCategory.Update> diagnostics,
[NotNull] DbContext context,
[NotNull] Exception exception,
CancellationToken cancellationToken = default)
{
var definition = CoreResources.LogOptimisticConcurrencyException(diagnostics);

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

if (diagnostics.NeedsEventData<ISaveChangesInterceptor>(
definition,
out var interceptor, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = CreateDbContextErrorEventData(context, exception, definition);

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

if (interceptor != null)
{
return interceptor.SaveChangesFailedAsync(eventData, cancellationToken);
}
}

return new ValueTask();
}

private static DbContextErrorEventData CreateDbContextErrorEventData(
DbContext context, Exception exception, EventDefinition<Exception> definition)
=> new DbContextErrorEventData(
definition,
OptimisticConcurrencyException,
context,
exception);

private static string OptimisticConcurrencyException(EventDefinitionBase definition, EventData payload)
{
var d = (EventDefinition<Exception>)definition;
Expand Down
28 changes: 28 additions & 0 deletions src/EFCore/SaveChangesEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;

namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// Base event arguments for the <see cref="M:DbContext.SaveChanges" /> and <see cref="M:DbContext.SaveChangesAsync" /> events.
/// </summary>
public abstract class SaveChangesEventArgs : EventArgs
{
/// <summary>
/// Creates a base event arguments instance for <see cref="M:DbContext.SaveChanges" />
/// or <see cref="M:DbContext.SaveChangesAsync" /> events.
/// </summary>
/// <param name="acceptAllChangesOnSuccess"> The value passed to SaveChanges. </param>
protected SaveChangesEventArgs(bool acceptAllChangesOnSuccess)
{
AcceptAllChangesOnSuccess = acceptAllChangesOnSuccess;
}

/// <summary>
/// The value passed to <see cref="M:DbContext.SaveChanges" /> or <see cref="M:DbContext.SaveChangesAsync" />.
/// </summary>
public virtual bool AcceptAllChangesOnSuccess { get; }
}
}
30 changes: 30 additions & 0 deletions src/EFCore/SaveChangesFailedEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using JetBrains.Annotations;

namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// Event arguments for the <see cref="DbContext.SaveChangesFailed" /> event.
/// </summary>
public class SaveChangesFailedEventArgs : SaveChangesEventArgs
{
/// <summary>
/// Creates a new <see cref="SaveChangesFailedEventArgs"/> instance with the exception that was thrown.
/// </summary>
/// <param name="acceptAllChangesOnSuccess"> The value passed to SaveChanges. </param>
/// <param name="exception"> The exception thrown. </param>
public SaveChangesFailedEventArgs(bool acceptAllChangesOnSuccess, [NotNull] Exception exception)
: base(acceptAllChangesOnSuccess)
{
Exception = exception;
}

/// <summary>
/// The exception thrown during<see cref="M:DbContext.SaveChanges"/> or <see cref="M:DbContext.SaveChangesAsync"/>.
/// </summary>
public virtual Exception Exception { get; }
}
}
27 changes: 27 additions & 0 deletions src/EFCore/SavedChangesEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// Event arguments for the <see cref="DbContext.SavedChanges" /> event.
/// </summary>
public class SavedChangesEventArgs : SaveChangesEventArgs
{
/// <summary>
/// Creates a new <see cref="SavedChangesEventArgs" /> instance with the given number of entities saved.
/// </summary>
/// <param name="acceptAllChangesOnSuccess"> The value passed to SaveChanges. </param>
/// <param name="entitiesSavedCount"> The number of entities saved. </param>
public SavedChangesEventArgs(bool acceptAllChangesOnSuccess, int entitiesSavedCount)
: base(acceptAllChangesOnSuccess)
{
EntitiesSavedCount = entitiesSavedCount;
}

/// <summary>
/// The number of entities saved.
/// </summary>
public virtual int EntitiesSavedCount { get; }
}
}
20 changes: 20 additions & 0 deletions src/EFCore/SavingChangesEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// Event arguments for the <see cref="DbContext.SavingChanges" /> event.
/// </summary>
public class SavingChangesEventArgs : SaveChangesEventArgs
{
/// <summary>
/// Creates event arguments for the <see cref="M:DbContext.SavingChanges" /> event.
/// </summary>
/// <param name="acceptAllChangesOnSuccess"> The value passed to SaveChanges. </param>
public SavingChangesEventArgs(bool acceptAllChangesOnSuccess)
: base(acceptAllChangesOnSuccess)
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Xunit.Sdk;

namespace Microsoft.EntityFrameworkCore
{
Expand All @@ -16,6 +17,8 @@ protected SaveChangesInterceptionInMemoryTestBase(InterceptionInMemoryFixtureBas
{
}

protected override bool SupportsOptimisticConcurrency => false;

public abstract class InterceptionInMemoryFixtureBase : InterceptionFixtureBase
{
protected override string StoreName => "SaveChangesInterception";
Expand Down
Loading