c# - 如何解决服务层内部的循环依赖

标签 c# asp.net asp.net-core dependency-injection

<分区>

我知道其他人已经遇到了同样的问题,但我找不到任何令人满意的解决方案,所以我在这里询问其他想法。

我的业务逻辑包含在这样的服务层中:

public class RoomService : IRoomService
{
    private readonly IRoomRepository _roomRepository;
    private readonly ICourseService _courseService;

    public RoomService(IRoomRepository roomRepository, ICourseService courseService)
    {
        _roomRepository = roomRepository ?? throw new ArgumentNullException(nameof(roomRepository));
        _courseService = courseService ?? throw new ArgumentNullException(nameof(courseService));
    }

    public Task DeleteRoomAsync(string id)
    {
        // Check if there are any courses for this room (requires ICourseService)
        // Delete room
    }
}

public class CourseService : ICourseService
{
    private readonly ICourseRepository _courseRepository;
    private readonly IRoomService _roomService;

    public CourseService(ICourseRepository courseRepository, IRoomService roomService)
    {
        _courseRepository = courseRepository ?? throw new ArgumentNullException(nameof(courseRepository));
        _roomService = roomService ?? throw new ArgumentNullException(nameof(roomService));
    }

    public Task GetAllCoursesInBuilding(string buildingId)
    {
        // Query all rooms in building (requires IRoomService)
        // Return all courses for these rooms
    }
}

这只是一个例子。在这种情况下,可能有避免服务相互依赖的变通办法,但我过去遇到过多种其他情况,没有任何干净的变通办法。

如您所见,这两个服务相互依赖,并且由于循环依赖,依赖注入(inject)将失败。

现在我可以想出两种方法来解决这个问题:

方案一

我可以在需要它们的服务方法中解决服务依赖关系,而不是将服务依赖关系注入(inject)服务构造函数:

public class RoomService : IRoomService
{
    private readonly IRoomRepository _roomRepository;
    private readonly IServiceProvider _serviceProvider;

    public RoomService(IRoomRepository roomRepository, IServiceProvider serviceProvider)
    {
        _roomRepository = roomRepository ?? throw new ArgumentNullException(nameof(roomRepository));
        _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
    }

    public Task DeleteRoomAsync(string id)
    {
        ICourseService courseService = _serviceProvider.GetRequiredService<ICourseService>();

        // Check if there are any courses for this room (requires ICourseService)
        // Delete room
    }
}

问题:这使得单元测试变得更加困难,因为我需要注入(inject)一个模拟的 IServiceProvider,它能够将我的 ICourseService 解析到类构造函数中.在编写单元测试时也不是很清楚,每个服务方法需要哪些服务,因为这完全取决于实现。

解决方案2

服务方法可能需要 ICourseService 作为方法参数从 Controller 传入:

public Task DeleteRoomAsync(ICourseService courseService, string id)
{
    // Check if there are any courses for this room (requires ICourseService)
    // Delete room
}

问题:现在我的 Controller 需要了解服务方法的实现细节:DeleteRoomAsync 需要一个 ICourseService 对象来做它的工作。 我认为这不是很干净,因为 DeleteRoomAsync 的要求将来可能会改变,但方法签名不应该。

您能想到任何更清洁的替代解决方案吗?

最佳答案

如果您的框架支持它,您可以将注入(inject)的依赖项作为 Lazy<T> 提供。这会延迟解析并允许您具有循环依赖性。

这些服务类可能如下所示:

class FooService : IFooService
{
    protected Lazy<IBarService> _bar;

    public FooService(Lazy<IBarService> bar)
    {
        _bar = bar;
    }

    public void DoSomething(bool callOtherService)
    {
        Console.WriteLine("Hello world. I am Foo.");
        if (callOtherService)
        {
            _bar.Value.DoSomethingElse(false);
        }
    }

}

class BarService : IBarService
{
    protected Lazy<IFooService> _foo;

    public BarService(Lazy<IFooService> foo)
    {
        _foo = foo;
    }
    public void DoSomethingElse(bool callOtherService)
    {
        Console.WriteLine("Hello world. I am Bar.");
        if (callOtherService)
        {
            _foo.Value.DoSomething(false);
        }
    }
}

注册它们的代码不需要修改(至少不需要 Autofac):

public static IContainer CompositionRoot()
{
    var builder = new ContainerBuilder();
    builder.RegisterType<FooService>().As<IFooService>().SingleInstance();
    builder.RegisterType<BarService>().As<IBarService>().SingleInstance();
    builder.RegisterType<Application>().SingleInstance();
    return builder.Build();
}

请参阅 DotNetFiddle 上的工作示例.

如果您的框架不支持这样的惰性注入(inject),您可能可以使用工厂(或任何其他延迟解析的模式)来做完全相同的事情。

另见 this answer这帮助我想出了这个解决方案。

关于c# - 如何解决服务层内部的循环依赖,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57965397/

相关文章:

c# - 关闭子窗体时如何刷新datagridview?

c# - 我如何创建一个方法类型 bool 并循环控制来决定案例?

c# - 如何使用 MVVM 模式在 Xamarin Forms 中为 ZXingScannerView 添加自定义叠加层?

c# - 如何将依赖项注入(inject) .Net Core 项目中的自定义 WebHostService?

c# - 使用 Controller 名称和操作名称 main.js 时,asp.net core 的 Angular 2 设置问题

c# - C#中通过WebBrowser控件获取HTML源码

c# - 使用 ASP.NET 远程登录到 Google Analytics

c# - 在 Web 应用程序中将 SQL Server 数据导出到 Excel

c# - 您在哪个现实生活场景中使用了垃圾收集器?

c# - 如何在局部 View 中获取模型属性名称