GraphFoundation动态更新图

GraphFoundation动态更新图实现了一个基于ECS(实体组件系统)的泛型图数据结构库,支持事务操作、事件驱动、索引管理和链式结构。核心功能如下:

  1. 图核心模型
    节点(Node):由键(TKey)、数据(TNodeData)和可选符号(Symbol)组成,存储为ECS组件。

边(Edge):由唯一ID、源节点、目标节点、方向、权重和数据(TEdgeData)组成,同样存储为ECS组件。

关系管理:通过ECS的RelationManager维护节点与边之间的连接关系(出边/入边)。

索引:利用IndexManager为节点键和边ID建立快速查找索引。

  1. 事务支持
    提供ITransaction接口,支持命令模式:所有修改操作(增删改)都封装为命令(如AddNodeCommand),在事务中执行。

事务使用读写锁(ReaderWriterLockSlim)保证线程安全,支持原子提交和回滚(通过命令的Undo方法)。

事务提交时发布对应的图事件(如NodeAddedEvent),供外部订阅。

  1. 事件系统
    所有图变更都会发布事件(继承自GraphEvent),包括节点/边的添加、移除、更新。

提供扩展方法筛选特定事件类型(如NodeAdded、EdgeRemoved)。

  1. 链式结构(Chain)
    Chain<TKey, TNodeData, TEdgeData> 表示一个有序节点序列,内部自动维护连接相邻节点的有向边。

支持动态插入、移除、移动节点,并自动更新边(保留原有边的数据和权重)。

支持延迟节点创建:当链中插入不存在的节点时,可通过回调创建节点。

实现了IReadOnlyList,可像列表一样访问,并实现了IDisposable,销毁时自动清理所有边。

  1. 符号索引投影器(SymbolIndexProjector)
    维护节点符号(Symbol)到节点键的映射,便于通过符号快速查找节点。

自动订阅图事件,实时更新映射。

  1. 错误处理
    定义IErrorHandler接口,提供默认的ErrorHandlerService(组合多个处理器)和FallbackErrorHandler(回退策略)。

ErrorHandlingCommand包装原始命令,在执行或回滚时捕获异常并交由错误处理器处理。

通过UseErrorHandling扩展方法将错误处理集成到上下文中。

  1. 线程安全与并发控制
    所有读操作使用读锁,事务操作使用写锁,保证多线程环境下的数据一致性。

使用ECS框架(ECSEnhanced)的EcsContext和EntityManager管理实体生命周期。

  1. 扩展与集成
    实现了ICoreGraph接口,可注入到IContext(上下文管理)中作为服务使用。

提供扩展方法CreateChain快速从节点序列构建链。

典型应用场景

工作流/流程图:节点为任务或状态,边为流转关系,链表示顺序执行路径。

数据流图:节点为算子,边为数据依赖,支持事务性修改。

游戏开发:节点为游戏对象,边为关系,需要高性能和事务回滚。

该库通过ECS优化性能,通过事务和命令保证操作原子性,通过事件实现响应式更新,是一个功能完备的图数据结构基础设施。

csharp 复制代码
#region GraphFoundation

#nullable enable

namespace GraphFoundation.Core
{
    using ContextManagement;
    using ECSEnhanced;
    using ECSEnhanced.Locking;
    using System;
    using System.Collections.Generic;
    using System.Reactive.Subjects;

    public abstract record GraphEvent();

    public record NodeAddedEvent<TKey>(TKey Key, object? Data, string? Symbol) : GraphEvent;
    public record NodeRemovedEvent<TKey>(TKey Key, object? OldData, string? OldSymbol) : GraphEvent;
    public record NodeUpdatedEvent<TKey>(TKey Key, object? OldData, object? NewData, string? OldSymbol, string? NewSymbol) : GraphEvent;

    public record EdgeAddedEvent<TKey>(string EdgeId, TKey Source, TKey Target, bool IsDirected, double Weight, object? Data) : GraphEvent;
    public record EdgeRemovedEvent<TKey>(string EdgeId, TKey Source, TKey Target) : GraphEvent;
    public record EdgeUpdatedEvent<TKey>(string EdgeId, TKey Source, TKey Target, bool IsDirected, double Weight, object? OldData, object? NewData) : GraphEvent;

    public interface ICommand
    {
        void Execute(TransactionContextBase context);
        void Undo(TransactionContextBase context);
    }

    public abstract class TransactionContextBase
    {
        internal List<GraphEvent> PendingEvents { get; } = new();
    }

    public interface ITransaction : IDisposable
    {
        void Enqueue(ICommand command);
        void Commit();
        void Rollback();
        bool IsActive { get; }
        void OnCommit(Action callback);
        IContext Context { get; }
    }

    public interface IReadOnlyGraph<TKey, TNodeData, TEdgeData>
        where TKey : notnull
        where TNodeData : class
    {
        TNodeData? GetNodeData(TKey key);
        string? GetNodeSymbol(TKey key);
        TEdgeData? GetEdgeData(string edgeId);
        double GetEdgeWeight(string edgeId);
        TKey GetEdgeSource(string edgeId);
        TKey GetEdgeTarget(string edgeId);
        IReadOnlyList<TKey> GetAllNodeKeys();
        IReadOnlyList<string> GetAllEdgeIds();
        IReadOnlyList<(TKey Source, TKey Target, string EdgeId, bool IsDirected, double Weight, TEdgeData Data)> GetOutgoingEdges(TKey source);
        IReadOnlyList<(TKey Source, TKey Target, string EdgeId, bool IsDirected, double Weight, TEdgeData Data)> GetIncomingEdges(TKey target);
        bool ContainsNode(TKey key);
        bool ContainsEdge(string edgeId);
        IObservable<GraphEvent> Events { get; }
    }

    public interface ICoreGraph<TKey, TNodeData, TEdgeData> : IReadOnlyGraph<TKey, TNodeData, TEdgeData>, IDisposable
        where TKey : notnull
        where TNodeData : class
    {
        ITransaction BeginTransaction();
        void AddNode(TKey key, TNodeData data, string? symbol = null);
        void RemoveNode(TKey key);
        void AddEdge(string edgeId, TKey source, TKey target, bool isDirected, TEdgeData data, double weight = 1.0);
        void RemoveEdge(string edgeId);
        void UpdateNode(TKey key, TNodeData newData, string? newSymbol = null);
        IContext Context { get; }
    }

    internal static class Constants
    {
        public const string ChainEdgeIdFormat = "Chain_{0}_{1}";
        public const double DefaultEdgeWeight = 1.0;
        public const int MinChainLengthForEdges = 2;
    }
}

namespace GraphFoundation.Core.Implementation
{
    using ECSEnhanced;
    using GraphFoundation.Core;
    using GraphFoundation.ErrorHandling;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reactive.Subjects;
    using System.Threading;

    public struct NodeComponent<TKey, TNodeData> : IComponent where TKey : notnull
    {
        public TKey Key;
        public TNodeData Data;
        public string? Symbol;
        public NodeComponent(TKey key, TNodeData data, string? symbol = null) => (Key, Data, Symbol) = (key, data, symbol);
    }

    public struct EdgeComponent<TKey, TEdgeData> : IComponent where TKey : notnull
    {
        public string Id;
        public TKey Source;
        public TKey Target;
        public bool IsDirected;
        public double Weight;
        public TEdgeData Data;
        public EdgeComponent(string id, TKey source, TKey target, bool directed, TEdgeData data, double weight = 1.0)
            => (Id, Source, Target, IsDirected, Weight, Data) = (id, source, target, directed, weight, data);
    }

    public enum EdgeRelationType { From, To }

    public class TransactionContext<TKey, TNodeData, TEdgeData> : TransactionContextBase
        where TKey : notnull
        where TNodeData : class
    {
        internal CoreGraph<TKey, TNodeData, TEdgeData> Core { get; }
        internal IContext Context { get; }
        public TransactionContext(CoreGraph<TKey, TNodeData, TEdgeData> core) => (Core, Context) = (core, core.Context);
    }

    public abstract class NodeCommandBase<TKey, TNodeData, TEdgeData> : ICommand
        where TKey : notnull
        where TNodeData : class
    {
        protected abstract void ExecuteCore(TransactionContext<TKey, TNodeData, TEdgeData> ctx);
        protected abstract void UndoCore(TransactionContext<TKey, TNodeData, TEdgeData> ctx);

        public void Execute(TransactionContextBase context)
        {
            var typedCtx = (TransactionContext<TKey, TNodeData, TEdgeData>)context;
            ExecuteCore(typedCtx);
        }

        public void Undo(TransactionContextBase context)
        {
            var typedCtx = (TransactionContext<TKey, TNodeData, TEdgeData>)context;
            UndoCore(typedCtx);
        }
    }

    public abstract class EdgeCommandBase<TKey, TNodeData, TEdgeData> : ICommand
        where TKey : notnull
        where TNodeData : class
    {
        protected abstract void ExecuteCore(TransactionContext<TKey, TNodeData, TEdgeData> ctx);
        protected abstract void UndoCore(TransactionContext<TKey, TNodeData, TEdgeData> ctx);

        public void Execute(TransactionContextBase context)
        {
            var typedCtx = (TransactionContext<TKey, TNodeData, TEdgeData>)context;
            ExecuteCore(typedCtx);
        }

        public void Undo(TransactionContextBase context)
        {
            var typedCtx = (TransactionContext<TKey, TNodeData, TEdgeData>)context;
            UndoCore(typedCtx);
        }
    }

    public class CoreGraph<TKey, TNodeData, TEdgeData> : ICoreGraph<TKey, TNodeData, TEdgeData>
        where TKey : notnull
        where TNodeData : class
    {
        private readonly EcsContext _context;
        private readonly EntityManager _entityManager;
        private readonly IndexManager _indexManager;
        private readonly RelationManager _relationManager;
        public readonly ReaderWriterLockSlim _transactionLock = new(LockRecursionPolicy.NoRecursion);
        private readonly Subject<GraphEvent> _eventSubject = new();
        private bool _disposed;
        private readonly CancellationTokenRegistration _cancellationRegistration;
        private bool _eventCompleted;
        private readonly IErrorHandler? _errorHandler;

        public IContext Context { get; }
        public IObservable<GraphEvent> Events => _eventSubject;

        internal EntityManager EntityManager => _entityManager;
        internal IndexManager IndexManager => _indexManager;
        internal RelationManager RelationManager => _relationManager;

        internal void PublishEvent(GraphEvent evt)
        {
            lock (_eventSubject)
            {
                if (_eventCompleted) return;
                _eventSubject.OnNext(evt);
            }
        }

        internal void PublishEvents(IEnumerable<GraphEvent> events)
        {
            foreach (var evt in events)
                PublishEvent(evt);
        }

        internal void ReleaseWriteLock() => _transactionLock.ExitWriteLock();

        public CoreGraph(IContext context, EcsContext? ecsContext = null)
        {
            Context = context ?? throw new ArgumentNullException(nameof(context));
            _context = ecsContext ?? new EcsContext(enableBgUpdate: false);
            _entityManager = _context.World.EntityManager;
            _indexManager = _context.Indexes;
            _relationManager = _context.Relations;

            _indexManager.RegisterIndex<NodeComponent<TKey, TNodeData>>(comp => comp.Key, unique: true);
            _indexManager.RegisterIndex<EdgeComponent<TKey, TEdgeData>>(comp => comp.Id, unique: true);

            context.GetOrAddService<ICoreGraph<TKey, TNodeData, TEdgeData>>(() => this);

            _cancellationRegistration = context.CancellationToken.Register(() =>
            {
                if (!_disposed && !_eventCompleted)
                {
                    lock (_eventSubject)
                    {
                        if (!_eventCompleted)
                        {
                            _eventSubject.OnCompleted();
                            _eventCompleted = true;
                        }
                    }
                }
            });

            _errorHandler = context.GetErrorHandler();
        }

        #region Internal helpers (no locks, assume caller holds write lock or is internal)
        internal Entity GetNodeEntityInternal(TKey key)
            => _indexManager.FindEntity<NodeComponent<TKey, TNodeData>>(key);

        internal Entity GetEdgeEntityInternal(string edgeId)
            => _indexManager.FindEntity<EdgeComponent<TKey, TEdgeData>>(edgeId);

        internal bool ContainsNodeInternal(TKey key)
        {
            var nodeComp = GetNodeComponentInternal(key);
            return nodeComp.HasValue && EqualityComparer<TKey>.Default.Equals(nodeComp.Value.Key, key);
        }

        internal bool ContainsEdgeInternal(string edgeId)
        {
            var edgeComp = GetEdgeComponentInternal(edgeId);
            return edgeComp.HasValue && edgeComp.Value.Id == edgeId;
        }

        internal NodeComponent<TKey, TNodeData>? GetNodeComponentInternal(TKey key)
        {
            var entity = GetNodeEntityInternal(key);
            if (!entity.IsValid)
                return null;

            if (_entityManager.TryGetComponent<NodeComponent<TKey, TNodeData>>(entity, out var comp))
                return comp;
            return null;
        }

        internal EdgeComponent<TKey, TEdgeData>? GetEdgeComponentInternal(string edgeId)
        {
            var entity = GetEdgeEntityInternal(edgeId);
            if (!entity.IsValid)
                return null;

            if (_entityManager.TryGetComponent<EdgeComponent<TKey, TEdgeData>>(entity, out var comp))
                return comp;
            return null;
        }

        internal TNodeData? GetNodeDataInternal(TKey key)
            => GetNodeComponentInternal(key)?.Data;

        internal string? GetNodeSymbolInternal(TKey key)
            => GetNodeComponentInternal(key)?.Symbol;

        internal TEdgeData? GetEdgeDataInternal(string edgeId)
        {
            var comp = GetEdgeComponentInternal(edgeId);
            return comp.HasValue ? comp.Value.Data : default;
        }

        internal double GetEdgeWeightInternal(string edgeId)
        {
            var comp = GetEdgeComponentInternal(edgeId);
            if (comp == null) throw new KeyNotFoundException($"Edge with id '{edgeId}' not found.");
            return comp.Value.Weight;
        }

        internal IReadOnlyList<(TKey Source, TKey Target, string EdgeId, bool IsDirected, double Weight, TEdgeData Data)>
            GetOutgoingEdgesInternal(TKey source)
        {
            var nodeEntity = GetNodeEntityInternal(source);
            if (!nodeEntity.IsValid)
                return Array.Empty<(TKey, TKey, string, bool, double, TEdgeData)>();

            var result = new List<(TKey, TKey, string, bool, double, TEdgeData)>();
            foreach (var edgeEntity in _relationManager.GetRelatedTargets<EdgeRelationType>(nodeEntity, EdgeRelationType.From))
            {
                if (!edgeEntity.IsValid) continue;
                if (!_entityManager.TryGetComponent<EdgeComponent<TKey, TEdgeData>>(edgeEntity, out var comp))
                    continue;
                result.Add((comp.Source, comp.Target, comp.Id, comp.IsDirected, comp.Weight, comp.Data));
            }
            return result;
        }

        internal IReadOnlyList<(TKey Source, TKey Target, string EdgeId, bool IsDirected, double Weight, TEdgeData Data)>
            GetIncomingEdgesInternal(TKey target)
        {
            var nodeEntity = GetNodeEntityInternal(target);
            if (!nodeEntity.IsValid)
                return Array.Empty<(TKey, TKey, string, bool, double, TEdgeData)>();

            var result = new List<(TKey, TKey, string, bool, double, TEdgeData)>();
            foreach (var edgeEntity in _relationManager.GetRelatedTargets<EdgeRelationType>(nodeEntity, EdgeRelationType.To))
            {
                if (!edgeEntity.IsValid) continue;
                if (!_entityManager.TryGetComponent<EdgeComponent<TKey, TEdgeData>>(edgeEntity, out var comp))
                    continue;
                result.Add((comp.Source, comp.Target, comp.Id, comp.IsDirected, comp.Weight, comp.Data));
            }
            return result;
        }

        public (TKey Source, TKey Target, TEdgeData Data, double Weight)? TryGetEdgeDetails(string edgeId)
        {
            using (Lock.Read(_transactionLock))
            {
                var comp = GetEdgeComponentInternal(edgeId);
                if (comp == null) return null;
                return (comp.Value.Source, comp.Value.Target, comp.Value.Data, comp.Value.Weight);
            }
        }
        #endregion

        #region Public read methods (with read lock)
        public TNodeData? GetNodeData(TKey key) { using (Lock.Read(_transactionLock)) return GetNodeDataInternal(key); }
        public string? GetNodeSymbol(TKey key) { using (Lock.Read(_transactionLock)) return GetNodeSymbolInternal(key); }
        public TEdgeData? GetEdgeData(string edgeId) { using (Lock.Read(_transactionLock)) return GetEdgeDataInternal(edgeId); }
        public double GetEdgeWeight(string edgeId) { using (Lock.Read(_transactionLock)) return GetEdgeWeightInternal(edgeId); }
        public TKey GetEdgeSource(string edgeId)
        {
            using (Lock.Read(_transactionLock))
            {
                var comp = GetEdgeComponentInternal(edgeId);
                if (comp == null) throw new KeyNotFoundException($"Edge with id '{edgeId}' not found.");
                return comp.Value.Source;
            }
        }
        public TKey GetEdgeTarget(string edgeId)
        {
            using (Lock.Read(_transactionLock))
            {
                var comp = GetEdgeComponentInternal(edgeId);
                if (comp == null) throw new KeyNotFoundException($"Edge with id '{edgeId}' not found.");
                return comp.Value.Target;
            }
        }
        public IReadOnlyList<TKey> GetAllNodeKeys()
        {
            using (Lock.Read(_transactionLock))
            {
                var keys = new List<TKey>();
                foreach (var entity in _entityManager.GetEntities<NodeComponent<TKey, TNodeData>>())
                    keys.Add(_entityManager.GetComponent<NodeComponent<TKey, TNodeData>>(entity).Key);
                return keys;
            }
        }
        public IReadOnlyList<string> GetAllEdgeIds()
        {
            using (Lock.Read(_transactionLock))
            {
                var ids = new List<string>();
                foreach (var entity in _entityManager.GetEntities<EdgeComponent<TKey, TEdgeData>>())
                    ids.Add(_entityManager.GetComponent<EdgeComponent<TKey, TEdgeData>>(entity).Id);
                return ids;
            }
        }
        public IReadOnlyList<(TKey Source, TKey Target, string EdgeId, bool IsDirected, double Weight, TEdgeData Data)> GetOutgoingEdges(TKey source)
        { using (Lock.Read(_transactionLock)) return GetOutgoingEdgesInternal(source); }
        public IReadOnlyList<(TKey Source, TKey Target, string EdgeId, bool IsDirected, double Weight, TEdgeData Data)> GetIncomingEdges(TKey target)
        { using (Lock.Read(_transactionLock)) return GetIncomingEdgesInternal(target); }
        public bool ContainsNode(TKey key) { using (Lock.Read(_transactionLock)) return ContainsNodeInternal(key); }
        public bool ContainsEdge(string edgeId) { using (Lock.Read(_transactionLock)) return ContainsEdgeInternal(edgeId); }
        #endregion

        #region Transactions and high-level API
        public ITransaction BeginTransaction() { _transactionLock.EnterWriteLock(); return new LockedTransaction(this, _errorHandler); }

        public void AddNode(TKey key, TNodeData data, string? symbol = null)
        {
            using var tx = BeginTransaction();
            tx.Enqueue(new AddNodeCommand<TKey, TNodeData, TEdgeData>(key, data, symbol));
            tx.Commit();
        }

        public void RemoveNode(TKey key)
        {
            using var tx = BeginTransaction();
            tx.Enqueue(new RemoveNodeCommand<TKey, TNodeData, TEdgeData>(key));
            tx.Commit();
        }

        public void AddEdge(string edgeId, TKey source, TKey target, bool isDirected, TEdgeData data, double weight = 1.0)
        {
            using var tx = BeginTransaction();
            tx.Enqueue(new AddEdgeCommand<TKey, TNodeData, TEdgeData>(edgeId, source, target, isDirected, data, weight));
            tx.Commit();
        }

        public void RemoveEdge(string edgeId)
        {
            using var tx = BeginTransaction();
            tx.Enqueue(new RemoveEdgeCommand<TKey, TNodeData, TEdgeData>(edgeId));
            tx.Commit();
        }

        public void UpdateNode(TKey key, TNodeData newData, string? newSymbol = null)
        {
            using var tx = BeginTransaction();
            tx.Enqueue(new UpdateNodeCommand<TKey, TNodeData, TEdgeData>(key, newData, newSymbol));
            tx.Commit();
        }

        public void Dispose()
        {
            if (_disposed) return;
            _cancellationRegistration.Dispose();
            lock (_eventSubject)
            {
                if (!_eventCompleted)
                {
                    _eventSubject.OnCompleted();
                    _eventCompleted = true;
                }
            }
            _context.Dispose();
            _transactionLock.Dispose();
            _eventSubject.Dispose();
            _disposed = true;
        }
        #endregion

        private class LockedTransaction : ITransaction
        {
            private readonly CoreGraph<TKey, TNodeData, TEdgeData> _parent;
            private readonly IErrorHandler? _errorHandler;
            private readonly List<ICommand> _commands = new();
            private readonly List<Action> _commitCallbacks = new();
            private bool _committed, _disposed;

            public LockedTransaction(CoreGraph<TKey, TNodeData, TEdgeData> parent, IErrorHandler? errorHandler)
            {
                _parent = parent;
                _errorHandler = errorHandler;
            }

            public IContext Context => _parent.Context;
            public void Enqueue(ICommand command) => _commands.Add(command);
            public void OnCommit(Action callback) => _commitCallbacks.Add(callback);
            public bool IsActive => !_committed && !_disposed;

            public void Commit()
            {
                if (_committed || _disposed) return;

                var context = new TransactionContext<TKey, TNodeData, TEdgeData>(_parent);
                bool commitSuccess = false;
                try
                {
                    foreach (var cmd in _commands)
                    {
                        var executableCmd = _errorHandler != null
                            ? new ErrorHandlingCommand(cmd, _errorHandler)
                            : cmd;
                        executableCmd.Execute(context);
                    }
                    commitSuccess = true;
                }
                catch (Exception ex)
                {
                    try
                    {
                        RollbackCommands(context);
                    }
                    catch (Exception rollbackEx)
                    {
                        throw new AggregateException("Transaction commit failed and rollback encountered errors.", ex, rollbackEx);
                    }
                    throw;
                }
                finally
                {
                    _parent.ReleaseWriteLock();
                }

                if (commitSuccess)
                {
                    foreach (var evt in context.PendingEvents)
                    {
                        try
                        {
                            _parent.PublishEvent(evt);
                        }
                        catch (Exception ex)
                        {
                            _errorHandler?.HandleError(new GraphError(GraphOperationErrorType.Unknown, ex, "Event publishing failed after transaction commit"));
                        }
                    }
                    foreach (var cb in _commitCallbacks)
                    {
                        try { cb(); }
                        catch (Exception ex)
                        {
                            _errorHandler?.HandleError(new GraphError(GraphOperationErrorType.Unknown, ex, "Commit callback failed"));
                        }
                    }
                }
                _committed = true;
                _disposed = true;
            }

            private void RollbackCommands(TransactionContext<TKey, TNodeData, TEdgeData> context)
            {
                var undoExceptions = new List<Exception>();
                for (int i = _commands.Count - 1; i >= 0; i--)
                {
                    try { _commands[i].Undo(context); }
                    catch (Exception undoEx) { undoExceptions.Add(undoEx); }
                }
                if (undoExceptions.Count > 0)
                    throw new AggregateException("Transaction commit failed and undo encountered errors.", undoExceptions);
            }

            public void Rollback()
            {
                if (_committed || _disposed) return;
                var context = new TransactionContext<TKey, TNodeData, TEdgeData>(_parent);
                var undoExceptions = new List<Exception>();
                try
                {
                    for (int i = _commands.Count - 1; i >= 0; i--)
                    {
                        try { _commands[i].Undo(context); }
                        catch (Exception ex) { undoExceptions.Add(ex); }
                    }
                    if (undoExceptions.Count > 0)
                        throw new AggregateException("Rollback encountered errors.", undoExceptions);
                }
                finally
                {
                    _parent.ReleaseWriteLock();
                }
                _disposed = true;
            }

            public void Dispose()
            {
                if (_disposed) return;
                if (!_committed) try { Rollback(); } catch { }
                _disposed = true;
            }
        }
    }

    public class AddNodeCommand<TKey, TNodeData, TEdgeData> : NodeCommandBase<TKey, TNodeData, TEdgeData>
        where TKey : notnull
        where TNodeData : class
    {
        private readonly TKey _key;
        private readonly TNodeData _data;
        private readonly string? _symbol;
        private Entity _createdEntity;

        public AddNodeCommand(TKey key, TNodeData data, string? symbol = null) => (_key, _data, _symbol) = (key, data, symbol);

        protected override void ExecuteCore(TransactionContext<TKey, TNodeData, TEdgeData> ctx)
        {
            var core = ctx.Core;
            if (core.ContainsNodeInternal(_key))
                throw new InvalidOperationException($"Node with key '{_key}' already exists.");
            _createdEntity = core.EntityManager.CreateEntity();
            core.EntityManager.AddComponent(_createdEntity, new NodeComponent<TKey, TNodeData>(_key, _data, _symbol));
            ctx.PendingEvents.Add(new NodeAddedEvent<TKey>(_key, _data, _symbol));
        }

        protected override void UndoCore(TransactionContext<TKey, TNodeData, TEdgeData> ctx)
        {
            if (!_createdEntity.IsValid) return;
            ctx.Core.EntityManager.DestroyEntity(_createdEntity);
        }
    }

    public class RemoveNodeCommand<TKey, TNodeData, TEdgeData> : NodeCommandBase<TKey, TNodeData, TEdgeData>
        where TKey : notnull
        where TNodeData : class
    {
        private readonly TKey _key;
        private Entity _removedEntity;
        private NodeComponent<TKey, TNodeData> _oldComponent;
        private List<EdgeComponent<TKey, TEdgeData>> _removedEdges = new();

        public RemoveNodeCommand(TKey key) => _key = key;

        protected override void ExecuteCore(TransactionContext<TKey, TNodeData, TEdgeData> ctx)
        {
            var core = ctx.Core;
            var entity = core.GetNodeEntityInternal(_key);
            if (!entity.IsValid)
                throw new InvalidOperationException($"Node with key '{_key}' not found.");

            _removedEntity = entity;
            _oldComponent = core.EntityManager.GetComponent<NodeComponent<TKey, TNodeData>>(_removedEntity);

            var outgoing = core.RelationManager.GetRelatedTargets<EdgeRelationType>(_removedEntity, EdgeRelationType.From);
            var incoming = core.RelationManager.GetRelatedTargets<EdgeRelationType>(_removedEntity, EdgeRelationType.To);

            var allEdgeEntities = new HashSet<Entity>(outgoing);
            allEdgeEntities.UnionWith(incoming);

            foreach (var edgeEntity in allEdgeEntities)
            {
                var edgeComp = core.EntityManager.GetComponent<EdgeComponent<TKey, TEdgeData>>(edgeEntity);
                _removedEdges.Add(edgeComp);
                var srcEntity = core.GetNodeEntityInternal(edgeComp.Source);
                var tgtEntity = core.GetNodeEntityInternal(edgeComp.Target);
                core.RelationManager.RemoveRelation(srcEntity, edgeEntity, EdgeRelationType.From);
                core.RelationManager.RemoveRelation(tgtEntity, edgeEntity, EdgeRelationType.To);
                core.EntityManager.DestroyEntity(edgeEntity);
            }

            core.EntityManager.DestroyEntity(_removedEntity);
            ctx.PendingEvents.Add(new NodeRemovedEvent<TKey>(_key, _oldComponent.Data, _oldComponent.Symbol));
        }

        protected override void UndoCore(TransactionContext<TKey, TNodeData, TEdgeData> ctx)
        {
            var core = ctx.Core;
            if (!core.ContainsNodeInternal(_key))
            {
                var newEntity = core.EntityManager.CreateEntity();
                core.EntityManager.AddComponent(newEntity, _oldComponent);
            }

            foreach (var edgeComp in _removedEdges)
            {
                if (!core.ContainsEdgeInternal(edgeComp.Id))
                {
                    var newEntity = core.EntityManager.CreateEntity();
                    core.EntityManager.AddComponent(newEntity, edgeComp);
                    var srcEntity = core.GetNodeEntityInternal(edgeComp.Source);
                    var tgtEntity = core.GetNodeEntityInternal(edgeComp.Target);
                    core.RelationManager.AddRelation(srcEntity, newEntity, EdgeRelationType.From);
                    core.RelationManager.AddRelation(tgtEntity, newEntity, EdgeRelationType.To);
                }
            }
            _removedEdges.Clear();
        }
    }

    public class AddEdgeCommand<TKey, TNodeData, TEdgeData> : EdgeCommandBase<TKey, TNodeData, TEdgeData>
        where TKey : notnull
        where TNodeData : class
    {
        private readonly string _edgeId;
        private readonly TKey _source;
        private readonly TKey _target;
        private readonly bool _isDirected;
        private readonly double _weight;
        private readonly TEdgeData _data;
        private Entity _createdEntity;

        public AddEdgeCommand(string edgeId, TKey source, TKey target, bool isDirected, TEdgeData data, double weight = Constants.DefaultEdgeWeight)
            => (_edgeId, _source, _target, _isDirected, _weight, _data) = (edgeId, source, target, isDirected, weight, data);

        protected override void ExecuteCore(TransactionContext<TKey, TNodeData, TEdgeData> ctx)
        {
            var core = ctx.Core;

            if (!core.ContainsNodeInternal(_source))
                throw new InvalidOperationException($"Source node '{_source}' not found.");
            if (!core.ContainsNodeInternal(_target))
                throw new InvalidOperationException($"Target node '{_target}' not found.");
            if (core.ContainsEdgeInternal(_edgeId))
                throw new InvalidOperationException($"Edge with id '{_edgeId}' already exists.");

            _createdEntity = core.EntityManager.CreateEntity();
            if (!_createdEntity.IsValid)
                throw new InvalidOperationException($"Failed to create valid edge entity.");

            var edgeComp = new EdgeComponent<TKey, TEdgeData>(_edgeId, _source, _target, _isDirected, _data, _weight);
            core.EntityManager.AddComponent(_createdEntity, edgeComp);

            var srcEntity = core.GetNodeEntityInternal(_source);
            var tgtEntity = core.GetNodeEntityInternal(_target);
            if (!srcEntity.IsValid || !tgtEntity.IsValid)
                throw new InvalidOperationException($"Source or target node entity invalid.");

            core.RelationManager.AddRelation(srcEntity, _createdEntity, EdgeRelationType.From);
            core.RelationManager.AddRelation(tgtEntity, _createdEntity, EdgeRelationType.To);

            ctx.PendingEvents.Add(new EdgeAddedEvent<TKey>(_edgeId, _source, _target, _isDirected, _weight, _data));
        }

        protected override void UndoCore(TransactionContext<TKey, TNodeData, TEdgeData> ctx)
        {
            if (!_createdEntity.IsValid) return;
            var core = ctx.Core;
            var srcEntity = core.GetNodeEntityInternal(_source);
            var tgtEntity = core.GetNodeEntityInternal(_target);
            core.RelationManager.RemoveRelation(srcEntity, _createdEntity, EdgeRelationType.From);
            core.RelationManager.RemoveRelation(tgtEntity, _createdEntity, EdgeRelationType.To);
            core.EntityManager.DestroyEntity(_createdEntity);
        }
    }

    public class RemoveEdgeCommand<TKey, TNodeData, TEdgeData> : EdgeCommandBase<TKey, TNodeData, TEdgeData>
        where TKey : notnull
        where TNodeData : class
    {
        private readonly string _edgeId;
        private Entity _removedEntity;
        private EdgeComponent<TKey, TEdgeData> _oldComponent;

        public RemoveEdgeCommand(string edgeId) => _edgeId = edgeId;

        protected override void ExecuteCore(TransactionContext<TKey, TNodeData, TEdgeData> ctx)
        {
            var core = ctx.Core;
            var entity = core.GetEdgeEntityInternal(_edgeId);
            if (!entity.IsValid)
            {
                throw new InvalidOperationException($"Edge with id '{_edgeId}' not found.");
            }

            _removedEntity = entity;
            _oldComponent = core.EntityManager.GetComponent<EdgeComponent<TKey, TEdgeData>>(_removedEntity);
            var srcEntity = core.GetNodeEntityInternal(_oldComponent.Source);
            var tgtEntity = core.GetNodeEntityInternal(_oldComponent.Target);
            core.RelationManager.RemoveRelation(srcEntity, _removedEntity, EdgeRelationType.From);
            core.RelationManager.RemoveRelation(tgtEntity, _removedEntity, EdgeRelationType.To);
            core.EntityManager.DestroyEntity(_removedEntity);

            ctx.PendingEvents.Add(new EdgeRemovedEvent<TKey>(_edgeId, _oldComponent.Source, _oldComponent.Target));
        }

        protected override void UndoCore(TransactionContext<TKey, TNodeData, TEdgeData> ctx)
        {
            var core = ctx.Core;
            if (core.ContainsEdgeInternal(_edgeId)) return;

            var newEntity = core.EntityManager.CreateEntity();
            core.EntityManager.AddComponent(newEntity, _oldComponent);
            var srcEntity = core.GetNodeEntityInternal(_oldComponent.Source);
            var tgtEntity = core.GetNodeEntityInternal(_oldComponent.Target);
            core.RelationManager.AddRelation(srcEntity, newEntity, EdgeRelationType.From);
            core.RelationManager.AddRelation(tgtEntity, newEntity, EdgeRelationType.To);
        }
    }

    public class UpdateNodeCommand<TKey, TNodeData, TEdgeData> : NodeCommandBase<TKey, TNodeData, TEdgeData>
        where TKey : notnull
        where TNodeData : class
    {
        private readonly TKey _key;
        private readonly TNodeData _newData;
        private readonly string? _newSymbol;
        private TNodeData? _oldData;
        private string? _oldSymbol;
        private bool _executed;

        public UpdateNodeCommand(TKey key, TNodeData newData, string? newSymbol = null) => (_key, _newData, _newSymbol) = (key, newData, newSymbol);

        protected override void ExecuteCore(TransactionContext<TKey, TNodeData, TEdgeData> ctx)
        {
            var core = ctx.Core;
            var entity = core.GetNodeEntityInternal(_key);
            if (!entity.IsValid)
                throw new InvalidOperationException($"Node with key '{_key}' not found.");

            var oldComp = core.EntityManager.GetComponent<NodeComponent<TKey, TNodeData>>(entity);
            _oldData = oldComp.Data;
            _oldSymbol = oldComp.Symbol;

            core.EntityManager.SetComponent(entity, new NodeComponent<TKey, TNodeData>(_key, _newData, _newSymbol));
            _executed = true;

            ctx.PendingEvents.Add(new NodeUpdatedEvent<TKey>(_key, _oldData, _newData, _oldSymbol, _newSymbol));
        }

        protected override void UndoCore(TransactionContext<TKey, TNodeData, TEdgeData> ctx)
        {
            if (!_executed) return;
            var core = ctx.Core;
            var entity = core.GetNodeEntityInternal(_key);
            if (entity.IsValid)
                core.EntityManager.SetComponent(entity, new NodeComponent<TKey, TNodeData>(_key, _oldData!, _oldSymbol));
        }
    }
}

namespace GraphFoundation
{
    using GraphFoundation.Core;
    using GraphFoundation.Core.Implementation;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;

    public class SymbolIndexProjector<TKey, TNodeData, TEdgeData> : IDisposable
        where TKey : notnull
        where TNodeData : class
    {
        private readonly Dictionary<string, TKey> _symbolToKey = new();
        private readonly IDisposable _subscription;
        private readonly object _lock = new();
        private bool _disposed;

        public SymbolIndexProjector(ICoreGraph<TKey, TNodeData, TEdgeData> graph)
        {
            lock (_lock)
            {
                foreach (var key in graph.GetAllNodeKeys())
                {
                    var symbol = graph.GetNodeSymbol(key);
                    if (symbol != null) _symbolToKey[symbol] = key;
                }
            }
            _subscription = graph.Events.Subscribe(OnEvent);
        }

        private void OnEvent(GraphEvent evt)
        {
            lock (_lock)
            {
                if (_disposed) return;
                switch (evt)
                {
                    case NodeAddedEvent<TKey> added when added.Symbol != null:
                        _symbolToKey[added.Symbol] = added.Key;
                        break;
                    case NodeRemovedEvent<TKey> removed when removed.OldSymbol != null:
                        _symbolToKey.Remove(removed.OldSymbol);
                        break;
                    case NodeUpdatedEvent<TKey> updated:
                        if (updated.OldSymbol != null) _symbolToKey.Remove(updated.OldSymbol);
                        if (updated.NewSymbol != null) _symbolToKey[updated.NewSymbol] = updated.Key;
                        break;
                }
            }
        }

        public TKey? GetKeyBySymbol(string symbol)
        {
            lock (_lock)
                return _symbolToKey.TryGetValue(symbol, out var key) ? key : default;
        }

        public void Dispose()
        {
            if (!_disposed)
            {
                _subscription.Dispose();
                _disposed = true;
            }
        }
    }

    public class Chain<TKey, TNodeData, TEdgeData> : IReadOnlyList<TKey>, IDisposable
        where TKey : notnull
        where TNodeData : class
    {
        private readonly CoreGraph<TKey, TNodeData, TEdgeData> _graph;
        private readonly string _chainId;
        private List<TKey> _keys;
        private List<string> _edgeIds;
        private bool _disposed;
        private readonly object _lock = new();

        public IReadOnlyList<string> EdgeIds => _edgeIds.AsReadOnly();
        public bool IsValid => !_disposed;

        public Chain(IContext context, IEnumerable<TKey> keys, IEnumerable<string> edgeIds)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));
            if (!context.TryGetService<ICoreGraph<TKey, TNodeData, TEdgeData>>(out var graph) || graph is not CoreGraph<TKey, TNodeData, TEdgeData> coreGraph)
                throw new InvalidOperationException("No CoreGraph service found in context or invalid type.");
            _graph = coreGraph;
            _chainId = Guid.NewGuid().ToString("N");
            _keys = keys.ToList();
            _edgeIds = edgeIds.ToList();

            foreach (var key in _keys)
                if (!_graph.ContainsNode(key))
                    throw new ArgumentException($"Node {key} does not exist.");
            foreach (var eid in _edgeIds)
                if (!_graph.ContainsEdge(eid))
                    throw new ArgumentException($"Edge {eid} does not exist.");
        }

        private string GetEdgeId(int index) => string.Format(Constants.ChainEdgeIdFormat, _chainId, index);

        private void ThrowIfDisposed()
        {
            if (_disposed) throw new ObjectDisposedException(nameof(Chain<TKey, TNodeData, TEdgeData>));
        }

        public TKey this[int index]
        {
            get
            {
                ThrowIfDisposed();
                lock (_lock) return _keys[index];
            }
        }

        public int Count
        {
            get
            {
                ThrowIfDisposed();
                lock (_lock) return _keys.Count;
            }
        }

        public IEnumerator<TKey> GetEnumerator()
        {
            ThrowIfDisposed();
            lock (_lock) return _keys.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

        private void UpdateChain(Func<List<TKey>, List<TKey>> keyUpdate, Func<TKey, (TNodeData data, string? symbol)?>? nodeCreator = null)
        {
            ThrowIfDisposed();
            lock (_lock)
            {
                var newKeys = keyUpdate(_keys.ToList());

                var oldEdgesMap = new Dictionary<(TKey, TKey), (TEdgeData Data, double Weight)>();
                foreach (var eid in _edgeIds)
                {
                    var details = _graph.TryGetEdgeDetails(eid);
                    if (details.HasValue)
                        oldEdgesMap[(details.Value.Source, details.Value.Target)] = (details.Value.Data, details.Value.Weight);
                }

                using (var tx = _graph.BeginTransaction())
                {
                    // Add missing nodes (using internal method to avoid read lock recursion)
                    foreach (var key in newKeys)
                    {
                        if (!_graph.ContainsNodeInternal(key))
                        {
                            if (nodeCreator == null)
                                throw new InvalidOperationException($"Node '{key}' does not exist and no nodeCreator provided.");
                            var creation = nodeCreator(key) ?? throw new InvalidOperationException($"Node '{key}' does not exist and nodeCreator returned null.");
                            tx.Enqueue(new AddNodeCommand<TKey, TNodeData, TEdgeData>(key, creation.data, creation.symbol));
                        }
                    }

                    // Remove old edges (using internal method)
                    foreach (var eid in _edgeIds)
                        if (_graph.ContainsEdgeInternal(eid))
                            tx.Enqueue(new RemoveEdgeCommand<TKey, TNodeData, TEdgeData>(eid));

                    var newEdgeInfo = new List<(string EdgeId, TKey Source, TKey Target, TEdgeData Data, double Weight)>();
                    if (newKeys.Count >= Constants.MinChainLengthForEdges)
                    {
                        for (int i = 0; i < newKeys.Count - 1; i++)
                        {
                            var edgeId = GetEdgeId(i);
                            var source = newKeys[i];
                            var target = newKeys[i + 1];
                            TEdgeData dataToUse = default(TEdgeData)!;
                            double weight = Constants.DefaultEdgeWeight;
                            if (oldEdgesMap.TryGetValue((source, target), out var oldInfo))
                            {
                                dataToUse = oldInfo.Data;
                                weight = oldInfo.Weight;
                            }
                            newEdgeInfo.Add((edgeId, source, target, dataToUse, weight));
                            tx.Enqueue(new AddEdgeCommand<TKey, TNodeData, TEdgeData>(edgeId, source, target, true, dataToUse, weight));
                        }
                    }

                    tx.OnCommit(() =>
                    {
                        lock (_lock)
                        {
                            _keys = newKeys;
                            _edgeIds = newEdgeInfo.Select(e => e.EdgeId).ToList();
                        }
                    });
                    tx.Commit();
                }
            }
        }

        public void InsertAt(int index, TKey key, TNodeData? data = default, string? symbol = null) =>
            UpdateChain(keys => { if (index < 0 || index > keys.Count) throw new ArgumentOutOfRangeException(nameof(index)); keys.Insert(index, key); return keys; },
                keyNode => keyNode.Equals(key) ? (data ?? default(TNodeData)!, symbol) : null);

        public void RemoveAt(int index) =>
            UpdateChain(keys => { if (index < 0 || index >= keys.Count) throw new ArgumentOutOfRangeException(nameof(index)); keys.RemoveAt(index); return keys; });

        public void Move(int oldIndex, int newIndex) =>
            UpdateChain(keys =>
            {
                if (oldIndex == newIndex) return keys;
                if (oldIndex < 0 || oldIndex >= keys.Count || newIndex < 0 || newIndex >= keys.Count)
                    throw new ArgumentOutOfRangeException();
                var key = keys[oldIndex];
                keys.RemoveAt(oldIndex);
                keys.Insert(newIndex, key);
                return keys;
            });

        public void Dispose()
        {
            if (_disposed) return;
            try
            {
                using (var tx = _graph.BeginTransaction())
                {
                    foreach (var eid in _edgeIds)
                        if (_graph.ContainsEdgeInternal(eid))
                            tx.Enqueue(new RemoveEdgeCommand<TKey, TNodeData, TEdgeData>(eid));
                    tx.OnCommit(() => { lock (_lock) _edgeIds.Clear(); });
                    tx.Commit();
                }
            }
            catch
            {
                // Ignore exceptions during disposal
            }
            _disposed = true;
        }
    }

    public static class GraphExtensions
    {
        public static Chain<TKey, TNodeData, TEdgeData> CreateChain<TKey, TNodeData, TEdgeData>(this IContext context, IEnumerable<TKey> keys)
            where TKey : notnull
            where TNodeData : class
        {
            if (!context.TryGetService<ICoreGraph<TKey, TNodeData, TEdgeData>>(out var graph) || graph is not CoreGraph<TKey, TNodeData, TEdgeData> coreGraph)
                throw new InvalidOperationException("No CoreGraph service found in context or invalid type.");

            var keysList = keys.ToList();
            var chainId = Guid.NewGuid().ToString("N");
            var edgeIds = new List<string>();

            using (var tx = coreGraph.BeginTransaction())
            {
                for (int i = 0; i < keysList.Count - 1; i++)
                {
                    var edgeId = string.Format(Constants.ChainEdgeIdFormat, chainId, i);
                    tx.Enqueue(new AddEdgeCommand<TKey, TNodeData, TEdgeData>(edgeId, keysList[i], keysList[i + 1], true, default(TEdgeData)!, Constants.DefaultEdgeWeight));
                    edgeIds.Add(edgeId);
                }
                tx.Commit();
            }

            return new Chain<TKey, TNodeData, TEdgeData>(context, keysList, edgeIds);
        }

        public static IObservable<NodeAddedEvent<TKey>> NodeAdded<TKey, TNodeData, TEdgeData>(this IReadOnlyGraph<TKey, TNodeData, TEdgeData> graph)
            where TKey : notnull where TNodeData : class => graph.Events.OfType<NodeAddedEvent<TKey>>();
        public static IObservable<NodeRemovedEvent<TKey>> NodeRemoved<TKey, TNodeData, TEdgeData>(this IReadOnlyGraph<TKey, TNodeData, TEdgeData> graph)
            where TKey : notnull where TNodeData : class => graph.Events.OfType<NodeRemovedEvent<TKey>>();
        public static IObservable<NodeUpdatedEvent<TKey>> NodeUpdated<TKey, TNodeData, TEdgeData>(this IReadOnlyGraph<TKey, TNodeData, TEdgeData> graph)
            where TKey : notnull where TNodeData : class => graph.Events.OfType<NodeUpdatedEvent<TKey>>();
        public static IObservable<EdgeAddedEvent<TKey>> EdgeAdded<TKey, TNodeData, TEdgeData>(this IReadOnlyGraph<TKey, TNodeData, TEdgeData> graph)
            where TKey : notnull where TNodeData : class => graph.Events.OfType<EdgeAddedEvent<TKey>>();
        public static IObservable<EdgeRemovedEvent<TKey>> EdgeRemoved<TKey, TNodeData, TEdgeData>(this IReadOnlyGraph<TKey, TNodeData, TEdgeData> graph)
            where TKey : notnull where TNodeData : class => graph.Events.OfType<EdgeRemovedEvent<TKey>>();
        public static IObservable<EdgeUpdatedEvent<TKey>> EdgeUpdated<TKey, TNodeData, TEdgeData>(this IReadOnlyGraph<TKey, TNodeData, TEdgeData> graph)
            where TKey : notnull where TNodeData : class => graph.Events.OfType<EdgeUpdatedEvent<TKey>>();
    }
}

namespace GraphFoundation.ErrorHandling
{
    using ContextManagement;
    using GraphFoundation.Core;
    using System;
    using System.Collections.Generic;

    public enum GraphOperationErrorType
    {
        None,
        NodeNotFound,
        EdgeNotFound,
        DuplicateNode,
        DuplicateEdge,
        InvalidOperation,
        TransactionFailure,
        CommandExecutionFailed,
        UndoFailed,
        RollbackFailed,
        ConcurrencyConflict,
        Timeout,
        Cancellation,
        Unknown
    }

    public class GraphError
    {
        public string Id { get; } = Guid.NewGuid().ToString();
        public DateTime Timestamp { get; } = DateTime.UtcNow;
        public GraphOperationErrorType Type { get; }
        public Exception Exception { get; }
        public string Message { get; }
        public object? Context { get; }

        public GraphError(GraphOperationErrorType type, Exception exception, string? message = null, object? context = null)
        {
            Type = type;
            Exception = exception;
            Message = message ?? exception.Message;
            Context = context;
        }
    }

    public interface IErrorHandler
    {
        bool HandleError(GraphError error);
    }

    public class ErrorHandlerService : IErrorHandler, IDisposable
    {
        private readonly List<IErrorHandler> _handlers = new();
        private bool _disposed;

        public void AddHandler(IErrorHandler handler)
        {
            if (handler == null) throw new ArgumentNullException(nameof(handler));
            _handlers.Add(handler);
        }

        public bool HandleError(GraphError error)
        {
            if (_disposed) return false;
            bool handled = false;
            foreach (var handler in _handlers)
            {
                try
                {
                    if (handler.HandleError(error))
                        handled = true;
                }
                catch { }
            }
            return handled;
        }

        public void Dispose()
        {
            if (_disposed) return;
            _handlers.Clear();
            _disposed = true;
        }
    }

    public class FallbackErrorHandler : IErrorHandler
    {
        private readonly Func<GraphError, bool> _fallbackAction;
        public FallbackErrorHandler(Func<GraphError, bool> fallbackAction) => _fallbackAction = fallbackAction ?? throw new ArgumentNullException(nameof(fallbackAction));
        public bool HandleError(GraphError error) => _fallbackAction(error);
    }

    public static class ErrorHandlingExtensions
    {
        public static IContext UseErrorHandling(this IContext context, Action<ErrorHandlerService> configure)
        {
            var service = new ErrorHandlerService();
            configure(service);
            context.GetOrAddService<IErrorHandler>(() => service);
            context.GetOrAddService<ErrorHandlerService>(() => service);
            return context;
        }

        public static IErrorHandler? GetErrorHandler(this IContext context)
        {
            return context.TryGetService<IErrorHandler>(out var handler) ? handler : null;
        }
    }

    public class ErrorHandlingCommand : ICommand
    {
        private readonly ICommand _inner;
        private readonly IErrorHandler _errorHandler;

        public ErrorHandlingCommand(ICommand inner, IErrorHandler errorHandler)
        {
            _inner = inner ?? throw new ArgumentNullException(nameof(inner));
            _errorHandler = errorHandler ?? throw new ArgumentNullException(nameof(errorHandler));
        }

        public void Execute(TransactionContextBase context)
        {
            try
            {
                _inner.Execute(context);
            }
            catch (Exception ex)
            {
                var error = CreateError(ex, context);
                _errorHandler.HandleError(error);
                throw;
            }
        }

        public void Undo(TransactionContextBase context)
        {
            try
            {
                _inner.Undo(context);
            }
            catch (Exception ex)
            {
                var error = CreateError(ex, context);
                _errorHandler.HandleError(error);
            }
        }

        private GraphError CreateError(Exception ex, TransactionContextBase context)
        {
            var errorType = DetermineErrorType(ex);
            return new GraphError(errorType, ex, context: context);
        }

        private GraphOperationErrorType DetermineErrorType(Exception ex) => ex switch
        {
            KeyNotFoundException => GraphOperationErrorType.NodeNotFound,
            InvalidOperationException => GraphOperationErrorType.InvalidOperation,
            OperationCanceledException => GraphOperationErrorType.Cancellation,
            TimeoutException => GraphOperationErrorType.Timeout,
            _ => GraphOperationErrorType.Unknown
        };
    }
}

#endregion
相关推荐
Rhystt2 小时前
代码随想录算法训练营第六十天|多余的边?从基础到进阶!
开发语言·c++·算法·图论
愤豆2 小时前
07-Java语言核心-JVM原理-JVM对象模型详解
java·jvm·c#
flying_13142 小时前
图神经网络分享系列-HAN(Heterogeneous Graph Attention Network)(二)
深度学习·神经网络·tensorflow·图论·图神经网络·代码实战·han
张人玉3 小时前
上位机项目笔记
笔记·c#·上位机
The Open Group3 小时前
当企业进入平台时代:架构如何支撑生态
架构
踩着两条虫3 小时前
VTJ.PRO 在线应用开发平台的后端模块系统
后端·架构·nestjs
王码码20353 小时前
Flutter for OpenHarmony:使用 pluto_grid 打造高性能数据网格
flutter·http·华为·架构·harmonyos
GISer_Jing3 小时前
Claude Code架构深度解析:从核心文件到Harness的确定性控制体系
ai·架构·aigc
踩着两条虫4 小时前
AI驱动的Vue3应用开发平台 深入探究(十三):物料系统之区块与页面模板
前端·vue.js·人工智能·架构·系统架构