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 events for entity state changes #10896

Merged
merged 1 commit into from
Feb 10, 2018
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
31 changes: 28 additions & 3 deletions src/EFCore/ChangeTracking/ChangeTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

namespace Microsoft.EntityFrameworkCore.ChangeTracking
{
// This is the app-developer facing public API to the change tracker
/// <summary>
/// Provides access to change tracking information and operations for entity instances the context is tracking.
/// Instances of this class are typically obtained from <see cref="DbContext.ChangeTracker" /> and it is not designed
Expand All @@ -39,16 +38,16 @@ public ChangeTracker(
Check.NotNull(stateManager, nameof(stateManager));
Check.NotNull(changeDetector, nameof(changeDetector));

#pragma warning disable 612
Context = context;
#pragma warning restore 612

_queryTrackingBehavior = context
.GetService<IDbContextOptions>()
.Extensions
.OfType<CoreOptionsExtension>()
.FirstOrDefault()
?.QueryTrackingBehavior
?? QueryTrackingBehavior.TrackAll;

StateManager = stateManager;
ChangeDetector = changeDetector;
_model = model;
Expand Down Expand Up @@ -257,6 +256,32 @@ public virtual void TrackGraph(

private IEntityEntryGraphIterator GraphIterator { get; }

/// <summary>
/// An event fired when an entity is tracked by the context, either because it was returned
/// from a tracking query, or because it was attached or added to the context.
/// </summary>
public event Action<object, EntityTrackedEventArgs> Tracked
{
add => StateManager.Tracked += value;
remove => StateManager.Tracked -= value;
}

/// <summary>
/// <para>
/// An event fired when an entity that is tracked by the associated <see cref="DbContext" /> has moved
/// from one <see cref="EntityState" /> to another.
/// </para>
/// <para>
/// Note that this event does not fire for entities when they are first tracked by the context.
/// Use the <see cref="Tracked" /> event to get notified when the context begins tracking an entity.
/// </para>
/// </summary>
public event Action<object, EntityStateEventArgs> StateChanged
{
add => StateManager.StateChanged += value;
remove => StateManager.StateChanged -= value;
}

#region Hidden System.Object members

/// <summary>
Expand Down
33 changes: 33 additions & 0 deletions src/EFCore/ChangeTracking/EntityEntryEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;

namespace Microsoft.EntityFrameworkCore.ChangeTracking
{
/// <summary>
/// Event arguments for events relating to tracked <see cref="EntityEntry" />s.
/// </summary>
public class EntityEntryEventArgs : EventArgs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provide DbContext here, too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its available on the EntityEntry

{
private readonly InternalEntityEntry _internalEntityEntry;
private EntityEntry _entry;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public EntityEntryEventArgs(
[NotNull] InternalEntityEntry internalEntityEntry)
{
_internalEntityEntry = internalEntityEntry;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check not null

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an internal constructor.

}

/// <summary>
/// The <see cref="EntityEntry" /> for the entity.
/// </summary>
public virtual EntityEntry Entry => _entry ?? (_entry = new EntityEntry(_internalEntityEntry));
}
}
38 changes: 38 additions & 0 deletions src/EFCore/ChangeTracking/EntityStateEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;

namespace Microsoft.EntityFrameworkCore.ChangeTracking
{
/// <summary>
/// Event arguments for the <see cref="ChangeTracker.StateChanged" /> event.
/// </summary>
public class EntityStateEventArgs : EntityEntryEventArgs
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public EntityStateEventArgs(
[NotNull] InternalEntityEntry internalEntityEntry,
EntityState oldState,
EntityState newState)
: base(internalEntityEntry)
{
OldState = oldState;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

NewState = newState;
}

/// <summary>
/// The state that the entity is transitioning from.
/// </summary>
public virtual EntityState OldState { get; }

/// <summary>
/// The state that the entity is transitioning to.
/// </summary>
public virtual EntityState NewState { get; }
}
}
31 changes: 31 additions & 0 deletions src/EFCore/ChangeTracking/EntityTrackedEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;

namespace Microsoft.EntityFrameworkCore.ChangeTracking
{
/// <summary>
/// Event arguments for the <see cref="ChangeTracker.Tracked" /> event.
/// </summary>
public class EntityTrackedEventArgs : EntityEntryEventArgs
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public EntityTrackedEventArgs(
[NotNull] InternalEntityEntry internalEntityEntry,
bool fromQuery)
: base(internalEntityEntry)
{
FromQuery = fromQuery;
}

/// <summary>
/// <c>True</c> if the entity is being tracked as part of a database query; <c>false</c> otherwise.
/// </summary>
public virtual bool FromQuery { get; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:trollface:

}
}
24 changes: 24 additions & 0 deletions src/EFCore/ChangeTracking/Internal/IStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,5 +235,29 @@ IEnumerable<InternalEntityEntry> GetDependentsUsingRelationshipSnapshot(
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
void Unsubscribe();

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
event Action<object, EntityTrackedEventArgs> Tracked;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
void OnTracked([NotNull] InternalEntityEntry internalEntityEntry, bool fromQuery);

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
event Action<object, EntityStateEventArgs> StateChanged;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
void OnStateChanged([NotNull] InternalEntityEntry internalEntityEntry, EntityState oldState);
}
}
27 changes: 25 additions & 2 deletions src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ private void SetEntityState(EntityState oldState, EntityState newState, bool acc
_stateData.FlagAllProperties(EntityType.PropertyCount(), PropertyFlag.TemporaryOrModified, flagged: false);
}

if (_stateData.EntityState != oldState)
{
_stateData.EntityState = oldState;
}

StateManager.InternalEntityEntryNotifier.StateChanging(this, newState);

if (newState == EntityState.Unchanged
Expand Down Expand Up @@ -265,7 +270,21 @@ private void SetEntityState(EntityState oldState, EntityState newState, bool acc
StateManager.ChangedCount--;
}

FireStateChanged(oldState);
}

private void FireStateChanged(EntityState oldState)
{
StateManager.InternalEntityEntryNotifier.StateChanged(this, oldState, fromQuery: false);

if (oldState != EntityState.Detached)
{
StateManager.OnStateChanged(this, oldState);
}
else
{
StateManager.OnTracked(this, fromQuery: false);
}
}

private void SetServiceProperties(EntityState oldState, EntityState newState)
Expand Down Expand Up @@ -293,9 +312,13 @@ private void SetServiceProperties(EntityState oldState, EntityState newState)
public virtual void MarkUnchangedFromQuery([CanBeNull] ISet<IForeignKey> handledForeignKeys)
{
StateManager.InternalEntityEntryNotifier.StateChanging(this, EntityState.Unchanged);

_stateData.EntityState = EntityState.Unchanged;

StateManager.InternalEntityEntryNotifier.StateChanged(this, EntityState.Detached, fromQuery: true);

StateManager.OnTracked(this, fromQuery: true);

var trackingQueryMode = StateManager.GetTrackingQueryMode(EntityType);
if (trackingQueryMode != TrackingQueryMode.Simple)
{
Expand Down Expand Up @@ -405,7 +428,7 @@ public virtual void SetPropertyModified(
if (changeState)
{
StateManager.ChangedCount++;
StateManager.InternalEntityEntryNotifier.StateChanged(this, currentState, fromQuery: false);
FireStateChanged(currentState);
}
}
else if (currentState == EntityState.Modified
Expand All @@ -416,7 +439,7 @@ public virtual void SetPropertyModified(
StateManager.InternalEntityEntryNotifier.StateChanging(this, EntityState.Unchanged);
_stateData.EntityState = EntityState.Unchanged;
StateManager.ChangedCount--;
StateManager.InternalEntityEntryNotifier.StateChanged(this, currentState, fromQuery: false);
FireStateChanged(currentState);
}
}

Expand Down
34 changes: 34 additions & 0 deletions src/EFCore/ChangeTracking/Internal/StateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -932,5 +932,39 @@ private static void AcceptAllChanges(IEnumerable<InternalEntityEntry> changedEnt
entry.AcceptChanges();
}
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public event Action<object, EntityTrackedEventArgs> Tracked;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual void OnTracked(InternalEntityEntry internalEntityEntry, bool fromQuery)
{
var @event = Tracked;

@event?.Invoke(Context.ChangeTracker, new EntityTrackedEventArgs(internalEntityEntry, fromQuery));
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public event Action<object, EntityStateEventArgs> StateChanged;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual void OnStateChanged(InternalEntityEntry internalEntityEntry, EntityState oldState)
{
var @event = StateChanged;

@event?.Invoke(Context.ChangeTracker, new EntityStateEventArgs(internalEntityEntry, oldState, internalEntityEntry.EntityState));
}
}
}
Loading