diff --git a/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs b/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs
index c7b62e2f..2e8894bc 100644
--- a/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs
+++ b/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs
@@ -18,7 +18,7 @@ namespace GFramework.Core.Abstractions.Architectures;
/// 新的 GFramework.Cqrs.Abstractions.Cqrs 契约由内置 CQRS dispatcher 统一处理,支持 request pipeline、notification publish 与 stream request。
/// 新功能优先使用 、 与对应的 CQRS Command/Query 重载;迁移旧代码时可先保留旧入口,再逐步替换为 CQRS 请求模型。
///
-public interface IArchitectureContext
+public interface IArchitectureContext : ICqrsContext
{
///
/// 获取指定类型的服务实例
diff --git a/GFramework.Core.Abstractions/Cqrs/ICqrsRuntime.cs b/GFramework.Core.Abstractions/Cqrs/ICqrsRuntime.cs
index d9efcb07..5bcbf862 100644
--- a/GFramework.Core.Abstractions/Cqrs/ICqrsRuntime.cs
+++ b/GFramework.Core.Abstractions/Cqrs/ICqrsRuntime.cs
@@ -1,52 +1,16 @@
-using GFramework.Core.Abstractions.Architectures;
-using GFramework.Cqrs.Abstractions.Cqrs;
+using System.ComponentModel;
namespace GFramework.Core.Abstractions.Cqrs;
///
-/// 定义架构上下文使用的 CQRS runtime seam。
-/// 该抽象把请求分发、通知发布与流式处理从具体实现中解耦,
-/// 使 不再直接依赖某个固定的 runtime 类型。
+/// 提供旧 GFramework.Core.Abstractions.Cqrs 命名空间下的 CQRS runtime 兼容别名。
///
-public interface ICqrsRuntime
+///
+/// 正式 runtime seam 已迁移到 ,
+/// 但当前仍保留该接口以避免立即打断历史公开路径与既有二进制引用。
+/// 新代码应优先依赖 GFramework.Cqrs.Abstractions.Cqrs 下的正式契约。
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+public interface ICqrsRuntime : GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime
{
- ///
- /// 发送请求并返回响应。
- ///
- /// 响应类型。
- /// 当前架构上下文,用于上下文感知处理器注入与嵌套请求访问。
- /// 要分发的请求。
- /// 取消令牌。
- /// 请求响应。
- ValueTask SendAsync(
- IArchitectureContext context,
- IRequest request,
- CancellationToken cancellationToken = default);
-
- ///
- /// 发布通知到所有已注册处理器。
- ///
- /// 通知类型。
- /// 当前架构上下文,用于上下文感知处理器注入。
- /// 要发布的通知。
- /// 取消令牌。
- /// 表示通知分发完成的值任务。
- ValueTask PublishAsync(
- IArchitectureContext context,
- TNotification notification,
- CancellationToken cancellationToken = default)
- where TNotification : INotification;
-
- ///
- /// 创建流式请求的异步响应序列。
- ///
- /// 流元素类型。
- /// 当前架构上下文,用于上下文感知处理器注入。
- /// 流式请求。
- /// 取消令牌。
- /// 按需生成的异步响应序列。
- IAsyncEnumerable CreateStream(
- IArchitectureContext context,
- IStreamRequest request,
- CancellationToken cancellationToken = default);
}
diff --git a/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs
index cdcde44d..584090ab 100644
--- a/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs
+++ b/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs
@@ -1,7 +1,6 @@
using System.Reflection;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Command;
-using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Enums;
using GFramework.Core.Abstractions.Environment;
using GFramework.Core.Abstractions.Ioc;
diff --git a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
index 4104ccb1..19c59dcb 100644
--- a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
+++ b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
@@ -1,12 +1,12 @@
using System.Reflection;
using GFramework.Core.Abstractions.Bases;
-using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Core.Tests.Cqrs;
using GFramework.Core.Tests.Systems;
using GFramework.Cqrs.Abstractions.Cqrs;
+using LegacyICqrsRuntime = GFramework.Core.Abstractions.Cqrs.ICqrsRuntime;
namespace GFramework.Core.Tests.Ioc;
@@ -160,12 +160,16 @@ public class MicrosoftDiContainerTests
public void RegisterHandlers_Should_Not_Duplicate_Cqrs_Infrastructure_When_It_Is_Already_Registered()
{
Assert.That(_container.GetAll(), Has.Count.EqualTo(1));
+ Assert.That(_container.GetAll(), Has.Count.EqualTo(1));
Assert.That(_container.GetAll(), Has.Count.EqualTo(1));
+ Assert.That(_container.Get(), Is.SameAs(_container.Get()));
CqrsTestRuntime.RegisterHandlers(_container);
Assert.That(_container.GetAll(), Has.Count.EqualTo(1));
+ Assert.That(_container.GetAll(), Has.Count.EqualTo(1));
Assert.That(_container.GetAll(), Has.Count.EqualTo(1));
+ Assert.That(_container.Get(), Is.SameAs(_container.Get()));
}
///
@@ -245,6 +249,46 @@ public class MicrosoftDiContainerTests
Assert.That(results.Count, Is.EqualTo(0));
}
+ ///
+ /// 测试容器未冻结时,会折叠“不同服务类型指向同一实例”的兼容别名重复,
+ /// 但会保留同一服务类型的重复显式注册。
+ ///
+ [Test]
+ public void GetAll_Should_Preserve_Duplicate_Registrations_For_The_Same_ServiceType_While_Deduplicating_Aliases()
+ {
+ var instance = new AliasAwareService();
+
+ _container.Register(instance);
+ _container.Register(instance);
+ _container.Register(instance);
+
+ var results = _container.GetAll();
+
+ Assert.That(results, Has.Count.EqualTo(2));
+ Assert.That(results[0], Is.SameAs(instance));
+ Assert.That(results[1], Is.SameAs(instance));
+ }
+
+ ///
+ /// 测试非泛型 GetAll 在容器未冻结时与泛型重载保持相同的别名去重语义。
+ ///
+ [Test]
+ public void
+ GetAll_Type_Should_Preserve_Duplicate_Registrations_For_The_Same_ServiceType_While_Deduplicating_Aliases()
+ {
+ var instance = new AliasAwareService();
+
+ _container.Register(instance);
+ _container.Register(instance);
+ _container.Register(instance);
+
+ var results = _container.GetAll(typeof(ISharedAliasService));
+
+ Assert.That(results, Has.Count.EqualTo(2));
+ Assert.That(results[0], Is.SameAs(instance));
+ Assert.That(results[1], Is.SameAs(instance));
+ }
+
///
/// 测试获取排序后的所有实例的功能
///
@@ -357,6 +401,17 @@ public class MicrosoftDiContainerTests
Is.True);
}
+ ///
+ /// 测试当程序集集合中包含空元素时,CQRS handler 注册入口会在委托给注册服务前直接失败。
+ ///
+ [Test]
+ public void RegisterCqrsHandlersFromAssemblies_WithNullAssemblyItem_Should_ThrowArgumentNullException()
+ {
+ var assemblies = new Assembly[] { typeof(DeterministicOrderNotification).Assembly, null! };
+
+ Assert.Throws(() => _container.RegisterCqrsHandlersFromAssemblies(assemblies));
+ }
+
///
/// 测试冻结容器以防止进一步注册的功能
///
@@ -712,6 +767,28 @@ public interface IMixedService
string? Name { get; set; }
}
+///
+/// 用于验证未冻结查询路径中的服务别名去重行为。
+///
+public interface ISharedAliasService;
+
+///
+/// 主服务别名接口。
+///
+public interface IPrimaryAliasService : ISharedAliasService;
+
+///
+/// 次级兼容别名接口。
+///
+public interface ISecondaryAliasService : ISharedAliasService;
+
+///
+/// 同时实现多个别名接口的测试服务。
+///
+public sealed class AliasAwareService : IPrimaryAliasService, ISecondaryAliasService
+{
+}
+
///
/// 实现优先级的服务
///
diff --git a/GFramework.Core/Architectures/ArchitectureContext.cs b/GFramework.Core/Architectures/ArchitectureContext.cs
index 9b6d7dc2..e0ac2dd6 100644
--- a/GFramework.Core/Architectures/ArchitectureContext.cs
+++ b/GFramework.Core/Architectures/ArchitectureContext.cs
@@ -1,7 +1,6 @@
using System.Collections.Concurrent;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Command;
-using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Environment;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Abstractions.Ioc;
@@ -190,7 +189,7 @@ public class ArchitectureContext : IArchitectureContext
/// 查询响应类型
/// 要发送的查询对象
/// 查询结果
- public TResponse SendQuery(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query)
+ public TResponse SendQuery(Cqrs.Abstractions.Cqrs.Query.IQuery query)
{
return SendQueryAsync(query).AsTask().GetAwaiter().GetResult();
}
@@ -216,8 +215,7 @@ public class ArchitectureContext : IArchitectureContext
/// 要发送的查询对象
/// 取消令牌,用于取消操作
/// 包含查询结果的ValueTask
- public async ValueTask SendQueryAsync(
- GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query,
+ public async ValueTask SendQueryAsync(Cqrs.Abstractions.Cqrs.Query.IQuery query,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(query);
@@ -354,7 +352,7 @@ public class ArchitectureContext : IArchitectureContext
/// 取消令牌,用于取消操作
/// 包含命令执行结果的ValueTask
public async ValueTask SendCommandAsync(
- GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command,
+ Cqrs.Abstractions.Cqrs.Command.ICommand command,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(command);
@@ -393,7 +391,7 @@ public class ArchitectureContext : IArchitectureContext
/// 命令响应类型
/// 要发送的命令对象
/// 命令执行结果
- public TResponse SendCommand(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command)
+ public TResponse SendCommand(Cqrs.Abstractions.Cqrs.Command.ICommand command)
{
return SendCommandAsync(command).AsTask().GetAwaiter().GetResult();
}
diff --git a/GFramework.Core/Ioc/MicrosoftDiContainer.cs b/GFramework.Core/Ioc/MicrosoftDiContainer.cs
index dc14485e..6152366f 100644
--- a/GFramework.Core/Ioc/MicrosoftDiContainer.cs
+++ b/GFramework.Core/Ioc/MicrosoftDiContainer.cs
@@ -4,8 +4,8 @@ using GFramework.Core.Abstractions.Bases;
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Abstractions.Systems;
-using GFramework.Core.Logging;
using GFramework.Core.Rule;
+using GFramework.Cqrs;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Core.Ioc;
@@ -35,6 +35,14 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
#endregion
+ ///
+ /// 记录某个实例在未冻结查询中可见的服务类型分组信息。
+ ///
+ /// 当前分组对应的服务类型。
+ /// 该服务类型下的描述符数量。
+ /// 该服务类型首次出现的位置,用于稳定打破并列。
+ private sealed record VisibleServiceTypeGroup(Type ServiceType, int Count, int FirstIndex);
+
#region Fields
///
@@ -57,12 +65,6 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
///
private readonly HashSet
/// 要接入的程序集集合。
/// 为 。
+ /// 中存在 元素。
/// 容器已冻结,无法继续注册 CQRS 处理器。
public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies)
{
ArgumentNullException.ThrowIfNull(assemblies);
+ var assemblyArray = assemblies.ToArray();
+ foreach (var assembly in assemblyArray)
+ {
+ ArgumentNullException.ThrowIfNull(assembly);
+ }
_lock.EnterWriteLock();
try
{
ThrowIfFrozen();
-
- var processedAssemblyKeys = new HashSet(StringComparer.Ordinal);
- foreach (var assembly in assemblies
- .Where(static assembly => assembly is not null)
- .OrderBy(GetCqrsAssemblyRegistrationKey, StringComparer.Ordinal))
- {
- var assemblyKey = GetCqrsAssemblyRegistrationKey(assembly);
- if (!processedAssemblyKeys.Add(assemblyKey))
- continue;
-
- if (_registeredCqrsHandlerAssemblyKeys.Contains(assemblyKey))
- {
- _logger.Debug(
- $"Skipping CQRS handler registration for assembly {assemblyKey} because it was already registered.");
- continue;
- }
-
- ResolveCqrsHandlerRegistrar().RegisterHandlers([assembly]);
- _registeredCqrsHandlerAssemblyKeys.Add(assemblyKey);
- }
+ ResolveCqrsRegistrationService().RegisterHandlers(assemblyArray);
}
finally
{
@@ -456,22 +445,22 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
#region Get
///
- /// 获取当前容器中已注册的 CQRS 处理器注册器。
+ /// 获取当前容器中已注册的 CQRS 程序集注册协调器。
/// 该方法仅供容器内部在注册阶段使用,因此直接读取服务描述符中的实例绑定,
/// 避免在容器未冻结前依赖完整的服务提供者构建流程。
///
- /// 已注册的 CQRS 处理器注册器实例。
- /// 未找到可用的 CQRS 处理器注册器实例时抛出。
- private ICqrsHandlerRegistrar ResolveCqrsHandlerRegistrar()
+ /// 已注册的 CQRS 程序集注册协调器实例。
+ /// 未找到可用的 CQRS 程序集注册协调器实例时抛出。
+ private ICqrsRegistrationService ResolveCqrsRegistrationService()
{
var descriptor = GetServicesUnsafe.LastOrDefault(static service =>
- service.ServiceType == typeof(ICqrsHandlerRegistrar));
+ service.ServiceType == typeof(ICqrsRegistrationService));
- if (descriptor?.ImplementationInstance is ICqrsHandlerRegistrar registrar)
- return registrar;
+ if (descriptor?.ImplementationInstance is ICqrsRegistrationService registrationService)
+ return registrationService;
const string errorMessage =
- "ICqrsHandlerRegistrar not registered. Ensure the CQRS runtime module has been installed before registering handlers.";
+ "ICqrsRegistrationService not registered. Ensure the CQRS runtime module has been installed before registering handlers.";
_logger.Error(errorMessage);
throw new InvalidOperationException(errorMessage);
}
@@ -618,29 +607,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
{
if (_provider == null)
{
- // 如果容器未冻结,从服务集合中获取已注册的实例
- var serviceType = typeof(T);
- var registeredServices = GetServicesUnsafe
- .Where(s => s.ServiceType == serviceType || serviceType.IsAssignableFrom(s.ServiceType)).ToList();
-
- var result = new List();
- foreach (var descriptor in registeredServices)
- {
- if (descriptor.ImplementationInstance is T instance)
- {
- result.Add(instance);
- }
- else if (descriptor.ImplementationFactory != null)
- {
- // 在未冻结状态下无法调用工厂方法,跳过
- }
- else if (descriptor.ImplementationType != null)
- {
- // 在未冻结状态下无法创建实例,跳过
- }
- }
-
- return result;
+ return CollectRegisteredImplementationInstances(typeof(T)).Cast().ToList();
}
var services = _provider!.GetServices().ToList();
@@ -658,37 +625,17 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
///
/// 服务类型
/// 只读的服务实例列表
- /// 当容器未冻结时抛出
+ /// 当 为 时抛出
public IReadOnlyList