.NET Core异常:检测到类型为的服务存在循环依赖关系

16

最近我问了一个关于软件架构的问题:

服务应该直接调用另一个服务还是仓库?

在得到答案后,我重新组织和重构了我的应用。简单来说,我的服务互相调用,比如StudentService需要ClassService(例如获取班级平均分数),而ClassService需要StudentService(以获取分配给该班级的学生)。下面是简化后的类:

public class StudentService : IStudentService
{
    protected readonly IClassService ClassService;
    public StudentService(IClassService classService)
    {
        ClassService = classService;
    }
}

public class ClassService : IClassService
{
    protected readonly IStudentService StudentService;
    public ClassService(IStudentService studentService)
    {
        StudentService = studentService;
    }
}

.NET Core中的服务是在DI容器中注册的。

services.AddTransient<IStudentService, StudentService>();
services.AddTransient<IClassService, ClassService>();
在解决期间。
var studentService = app.ApplicationServices.GetService<IStudentService>();
我收到一个异常,内容为“检测到服务类型的循环依赖关系......”。
我理解这里的实现问题,但我不知道如何解决这里的架构问题。你能提供一些建议吗?
编辑:好的,我有一个更加现实的例子,例如员工、服务和公司。
我们有一个带有抽象泛型CRUD库存储库的存储库层。然后我们有派生类:EmployeesRepository ServicesRepository和CompaniesRepository。
EmployeesRepository实现方法:GetTopEmployeesOfTheMonth ServicesRepository实现方法:GetTopServicesForEmployees CompaniesRepository实现方法:GetCompaniesWithTopIncome 在上面的层(称为业务层)中,我们有相同的结构: 抽象泛型CRUD Helper,例如检查用户权限并调用CRUD存储库中的方法。 然后我们有派生的EmployeesHelper、ServicesHerlper和CompaniesHelper。 它们中的所有人都会检查用户权限并从适当的存储库(例如,EmployeesHelper来自EmployeesRepository等)调用方法。 此外,在此层上,我们还有用于创建更多“复杂”对象的方法-由许多实体组成的对象。 例如,CompaniesHelper具有显示销售最多服务的前五家公司的方法。 数据将显示在一个屏幕上,因此它应该通过一个API请求生成并作为JSON返回。 CompaniesHelper的ShowCompaniesWithServices方法调用CompaniesHelper方法和EmployeesHelper方法。 在另一方面,我们有EmployeesHelper,它实现了返回月度最佳员工、他们的最佳服务和所在公司的复杂对象的方法,因此需要Comapnies Helper。
如何解决这种循环依赖关系?是否有设计模式来解决它?

有一个NuGet包可以解决这个问题 https://www.nuget.org/packages/ConnectingApps.SmartInject - undefined
6个回答

8

您有两种选择:

  1. Write code such that it isn't needed to call ClassService from StudentService (or StudentService from ClassService)

  2. Create third class:

    public class StudentService: IStudentService
    {
        private readonly IClassSharedSerive _classSharedService;
        ...
    }
    
    public class ClassService: IClassService
    {
        private readonly IClassSharedSerive _classSharedService;
        private readonly IStudentService _studentService;
        ...
    }
    
    public class ClassSharedService: IClassSharesService
    {
        ... without references to IClassService and IStudentService
    }
    

但是,在大多数情况下,需要正确地编写StudentService和ClassService(方式1)。


1
我有这个问题并解决了。这个链接解决了它。https://stackoverflow.com/questions/62217815/interface-a-circular-dependency-was-detected-for-the-service-of-type - Omid Rahimi

3

我不确定我解决这个问题的方式是否非常干净,但对我来说它完全可以工作。当我遇到循环依赖问题时,我从依赖注入中删除了服务,并将服务作为参数传递给需要它的函数。

因此,当你调用这个方法时,它看起来像这样:

// Inside the Student service
var result = _classService.GetClassStudents(classId, this)

可能不适用于所有人,但在我的情况下,我有一个相当简单的设置,所以我没有深入研究。

希望这可以帮助到您。


嘿,这太棒了,正是我所需要的。具体来说,我需要在被注入到我的数据库上下文中的提供程序类的某些方法中获取我的数据库上下文。 - Randy Hall

1

因此,我认为服务不应该注入其他服务。

我认为每个服务都应该能够独立地提供其负责的数据。如果消费者需要从两个来源获取数据,则IStudentService的消费者也可以是IClassService的消费者。

最初的回答:


1
当然,这非常有限。例如,我有一个数据仓库服务,其他所有服务都依赖于它。因此,所有其他服务都将注入DataRepo? - Zapnologica
https://stackoverflow.com/questions/62217815/interface-a-circular-dependency-was-detected-for-the-service-of-type - Omid Rahimi

1

我使用了另一个解决办法:Lazy<T>

public class StudentService : IStudentService
{
    private readonly IServiceProvider _serviceProvider;
    protected Lazy<IClassService> _lazyClassService;

    public StudentService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;

        _lazyClassService = new Lazy<IClassService>(() =>
                     _serviceProvider.GetRequiredService<IClassService>());
    }

    public SomeInfo GetSomeClassInfo(string studentId)
    {
        return _lazyClassService.Value.GetFooBar(studentId);
    }

}

可能不是那么干净,也不适合每个人,但在我相对简单的设置中运行良好。 请注意,在另一个类“ClassService”中不需要使用Lazy<T>

这是一个很棒的提议,而且应该能节省内存。 - undefined

0

我不确定这是否回答了问题,但我有一个服务包装器,将IOC的所有接口封装成一个接口。

在每个单独的服务中,我将传递单独所需的服务、日志记录、存储库、电子邮件发送器等。

例如,EmailSender依赖于IRepository。

因此,IServiceWrapper是我的父接口,这可以防止循环依赖。我仅在我的net core控制器中使用服务包装器,并为服务使用单独的接口。


0
你可能想考虑重新设计你的应用程序。然而,有一个NuGet包可以直接修复这个异常:ConnectingApps.SmartInject
它使用了延迟注入。这意味着实际的创建被推迟到真正需要的时刻。
为了解决这个问题,可以使用类似以下代码的方式:
public class Something : ISomething
{
    private readonly Lazy<ISomethingElse> _somethingElse;
    public Something(Lazy<ISomethingElse> somethingElse)
    {
        _somethingElse = somethingElse;
    }
}

并且

public class SomethingElse : ISomethingElse
{
    private readonly Lazy<ISomething> _something;
    public SomethingElse(Lazy<ISomething> something)
    {
        _something = something;
    }
}

现在你只需要将以下代码行添加到你的Program.cs文件中。
builder.Services.AddLazySingleton<ISomething, Something>();

或者,您可以使用AddLazyTransientAddLazyScoped代替。 不要忘记使用以下命名空间行:
using ConnectingApps.SmartInject;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接