简体   繁体   English

依赖注入的可能循环依赖

[英]A possible circular dependency with Dependency Injection

I'm using this template as my base project.我使用这个模板作为我的基础项目。 I would like to set a custom time zone that is user defined hence it is stored in the database.我想设置一个用户定义的自定义时区,因此它存储在数据库中。

The following is my failed attempt.以下是我失败的尝试。 Well, I tried many different approaches in vein.好吧,我尝试了许多不同的方法。 I hope that someone will be able to give me a right direction here.我希望有人能够在这里给我一个正确的方向。

namespace MyApp.Infrastructure.Services;

public class DateTimeService : IDateTime
{
    private readonly IAppDefaultServices _appDefaultServices;
    public DateTimeService(IAppDefaultServices appDefaultServices)
    {
        _appDefaultServices = appDefaultServices;
    }
    public DateTime Now()
    {
        DateTime utcTime = DateTime.UtcNow;
        TimeZoneInfo defaultZone = TimeZoneInfo.FindSystemTimeZoneById(_appDefaultServices.DefaultTimeZone);
        DateTime appDateTime = TimeZoneInfo.ConvertTimeFromUtc(utcTime, defaultZone);
        return appDateTime;
    }
}

namespace MyApp.Application.Common.Interfaces;

public interface IDateTime
{
    DateTime Now();
}


namespace MyApp.Infrastructure.Services;

public class AppDefaultServices : IAppDefaultServices
{
    private readonly IApplicationDbContext _context;
    public AppDefaultServices(IApplicationDbContext context)
    {
        _context = context;
    }
    private AppDefault _appDefault => _context.AppDefaults.First();

    public string DefaultTimeZone => _appDefault.TimeZoneId == null ? "Some Standard Time" : _appDefault.TimeZoneId;

}

namespace MyApp.Application.Common.Interfaces;
public interface IAppDefaultServices
{
    public string DefaultTimeZone { get; }
}

namespace Microsoft.Extensions.DependencyInjection;

public static class ConfigureServices
{
    public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddTransient<IAppDefaultServices, AppDefaultServices>();
        services.AddTransient<IDateTime, DateTimeService>();
    }
}

When I tried to run the application in Visual Studio, I get no errors but I can see the following message in thread window.当我尝试在 Visual Studio 中运行应用程序时,我没有收到任何错误,但我可以在线程 window 中看到以下消息。

The maximum number of stack frames supported by Visual Studio has been exceeded.已超出 Visual Studio 支持的最大堆栈帧数。

Please note that DateTimeService is being used in MyApp.Infrastructure.Persistence.Interceptors.AuditableEntitySaveChangesInterceptor which is part of ApplicationDbContext请注意DateTimeService正在MyApp.Infrastructure.Persistence.Interceptors.AuditableEntitySaveChangesInterceptor中使用,它是ApplicationDbContext的一部分

I really appreciate it if anyone could please shed a light here.如果有人能在这里阐明一下,我真的很感激。

Based on Steve's comment below, I'm posting a minimal code to reproduce the issue.基于史蒂夫在下面的评论,我发布了一个最小的代码来重现这个问题。

namespace MyApp.Infrastructure.Persistence;

public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>, IApplicationDbContext
{
    private readonly AuditableEntitySaveChangesInterceptor _auditableEntitySaveChangesInterceptor;

    public ApplicationDbContext(
        DbContextOptions<ApplicationDbContext> options,
        IOptions<OperationalStoreOptions> operationalStoreOptions,
        AuditableEntitySaveChangesInterceptor auditableEntitySaveChangesInterceptor) 
        : base(options, operationalStoreOptions)
    {
        _auditableEntitySaveChangesInterceptor = auditableEntitySaveChangesInterceptor;
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());

        base.OnModelCreating(builder);
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.AddInterceptors(_auditableEntitySaveChangesInterceptor);
    }

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        return await base.SaveChangesAsync(cancellationToken);
    }
}

Interceptor code below...下面的Interceptor代码...

namespace MyApp.Infrastructure.Persistence.Interceptors;
    
public class AuditableEntitySaveChangesInterceptor : SaveChangesInterceptor
{
    private readonly IDateTime _dateTime;

    public AuditableEntitySaveChangesInterceptor(
        IDateTime dateTime)
    {
        _dateTime = dateTime;
    }

    public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
    {
        UpdateEntities(eventData.Context);

        return base.SavingChanges(eventData, result);
    }

    public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
    {
        UpdateEntities(eventData.Context);

        return base.SavingChangesAsync(eventData, result, cancellationToken);
    }

    public void UpdateEntities(DbContext? context)
    {
        if (context == null) return;

        foreach (var entry in context.ChangeTracker.Entries<BaseAuditableEntity>())
        {
            if (entry.State == EntityState.Added)
            {
                entry.Entity.Created = _dateTime.Now();
            } 

            if (entry.State == EntityState.Added || entry.State == EntityState.Modified)
            {
                entry.Entity.LastModified = _dateTime.Now();
            }
        }
    }
}

Consider the following solutions to refactor your way out of this cyclic dependency:考虑以下解决方案来重构您摆脱这种循环依赖的方式:

  • Change DateTimeService to always return UtcNow .DateTimeService更改为始终返回UtcNow This prevents it needing to load the time zone and depend on IAppDefaultServices这可以防止它需要加载时区并依赖IAppDefaultServices
  • Consider loading the default time zone once at startup.考虑在启动时加载一次默认时区。 This allows the DateTimeService to be injected with that pre-loaded time zone and prevent it from depending on IAppDefaultServices .这允许DateTimeService被注入该预加载的时区,并防止它依赖于IAppDefaultServices
  • Consider changing AppDefaultServices such that it doesn't depend on IApplicationDbContext , but instead depends on a second DbContext that solely allows accessing the AppDefaults in a read-only fashion.考虑更改AppDefaultServices使其不依赖于IApplicationDbContext ,而是依赖于仅允许以只读方式访问AppDefaults的第二个DbContext This new read-only DbContext will not need the interceptor, as it only adds behavior when mutating data.这个新的只读DbContext不需要拦截器,因为它只在改变数据时添加行为。
  • If, after applying the previous option, AppDefaultServices still requires the IApplicationDbContext dependency, consider splitting the AppDefaultServices into multiple classes with multiple interfaces, such that the new interface only contains the DefaultTimeZone property.如果在应用上一个选项后, AppDefaultServices仍然需要IApplicationDbContext依赖项,请考虑将AppDefaultServices拆分为具有多个接口的多个类,以便新接口仅包含DefaultTimeZone属性。 This allows its implementation to solely contain a dependency on the 'AppDefaultsDbContext'.这允许其实现仅包含对“AppDefaultsDbContext”的依赖。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM