/// <summary>
        /// Traverse an object graph asynchronously executing a callback on each node.
        /// Depends on Entity Framework Core infrastructure, which may change in future releases.
        /// </summary>
        /// <param name="context">Used to query and save changes to a database</param>
        /// <param name="item">Object that implements ITrackable</param>
        /// <param name="callback">Async callback executed on each node in the object graph</param>
        public static async Task TraverseGraphAsync(this DbContext context, object item,
                                                    Func <EntityEntryGraphNode, Task> callback)
        {
            IStateManager             stateManager  = context.Entry(item).GetInfrastructure().StateManager;
            var                       node          = new EntityEntryGraphNode(stateManager.GetOrCreateEntry(item), null, null);
            IEntityEntryGraphIterator graphIterator = new EntityEntryGraphIterator();
            var                       visited       = new HashSet <int>();

            await graphIterator.TraverseGraphAsync(node, async (n, ct) =>
            {
                // Check visited
                if (visited.Contains(n.Entry.Entity.GetHashCode()))
                {
                    return(false);
                }

                // Execute callback
                await callback(n);

                // Add visited
                visited.Add(n.Entry.Entity.GetHashCode());

                // Return true if node state is null
                return(true);
            });
        }
        /// <summary>
        /// Traverse an object graph asynchronously executing a callback on each node.
        /// </summary>
        /// <param name="context">Used to query and save changes to a database</param>
        /// <param name="item">Object that implements ITrackable</param>
        /// <param name="callback">Async callback executed on each node in the object graph</param>
        public static async Task TraverseGraphAsync(this DbContext context, object item,
                                                    Func <EntityEntryGraphNode, Task> callback)
        {
#pragma warning disable EF1001 // Internal EF Core API usage.
            IStateManager             stateManager  = context.Entry(item).GetInfrastructure().StateManager;
            var                       node          = new EntityEntryGraphNode <object>(stateManager.GetOrCreateEntry(item), null, null, null);
            IEntityEntryGraphIterator graphIterator = new EntityEntryGraphIterator();
#pragma warning restore EF1001 // Internal EF Core API usage.
            var visited = new HashSet <int>();

            await graphIterator.TraverseGraphAsync <object>(node, async (n, ct) =>
            {
                // Check visited
                if (visited.Contains(n.Entry.Entity.GetHashCode()))
                {
                    return(false);
                }

                // Execute callback
                await callback(n);

                // Add visited
                visited.Add(n.Entry.Entity.GetHashCode());

                // Continue traversal
                return(true);
            });
        }
Example #3
0
        /// <summary>
        /// Traverse an entity graph asynchronously executing a callback on each node.
        /// </summary>
        /// <param name="context">The entity graph context.</param>
        /// <param name="entity">The entity to start traversing the graph from.</param>
        /// <param name="callback">The asynchronous callback executed on each node in the entity graph.</param>
        /// <param name="cancellationToken">A System.Threading.CancellationToken to observe while waiting for the task to complete.</param>
        public static Task TraverseGraphAsync(this DbContext context, object entity, Func <EntityEntryGraphNode, CancellationToken, Task> callback, CancellationToken cancellationToken = default)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (callback == null)
            {
                throw new ArgumentNullException(nameof(callback));
            }

            #pragma warning disable EF1001 // Internal EF Core API usage.
            var graph   = new EntityEntryGraphIterator( );
            var visited = new HashSet <object> ( );
            var entry   = context.Entry(entity).GetInfrastructure( );
            var root    = new EntityEntryGraphNode <object?> (entry, null, null, null);

            return(graph.TraverseGraphAsync(root,
                                            async(node, cancellationToken) =>
            {
                if (!visited.Add(node.Entry.Entity))
                {
                    return false;
                }

                await callback(node, cancellationToken).ConfigureAwait(false);
                return true;
            },
                                            cancellationToken));

            #pragma warning restore EF1001 // Internal EF Core API usage.
        }