You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 6如何实现Transient服务代码仅首次请求执行

问题场景

在ASP.NET Core 6 MVC项目中,定义了Transient生存期服务ContentService,该服务继承自IContentService接口,在Program.cs中通过如下代码注册:

builder.Services.AddTransient<IContentService, ContentService>();

服务实现代码如下:

public interface IContentService
{
    public string GetText(int id);
}

public class ContentService : IContentService
{
    private readonly ContentDbContext _context;
    private List<string> textList;

    public ContentService(ContentDbContext context)
    {
        _context = context;
        ReadTexts();
    }

    public void ReadTexts()
    {
        // 从数据库读取文本内容填充textList
        // 该段代码仅允许在首次请求时执行
    }

    public string GetText(int id)
    {
        return textList[id];
    }
}

当前存在的问题:每次请求ContentService实例时都会执行构造函数,调用重负载方法ReadTexts(),但该方法仅需在首次请求时执行一次。约束条件:无法使用AddSingleton注册单例服务,需要实现Transient服务内指定代码块仅在首次请求时运行。


解决方案

核心思路是把需要一次性执行的重负载逻辑、需要跨实例共享的缓存数据,抽离到线程安全的单例级状态容器中,Transient服务每次实例化时仅访问这个共享容器,不需要重复执行读库逻辑,同时保持ContentService本身的Transient生命周期不变。

推荐实现(符合DI设计规范)

  1. 定义独立的线程安全缓存容器,用Lazy<T>保证加载逻辑全程只执行一次,天然支持多线程并发场景,不需要手动编写锁逻辑:
    public class ContentTextCache
    {
        private readonly Lazy<List<string>> _texts;
    
        public ContentTextCache(Func<List<string>> loadFunc)
        {
            // ExecutionAndPublication模式保证多线程下委托只执行一次
            _texts = new Lazy<List<string>>(loadFunc, LazyThreadSafetyMode.ExecutionAndPublication);
        }
    
        public List<string> Texts => _texts.Value;
    }
    
  2. 在Program.cs中把缓存容器注册为单例,注册时通过IServiceScopeFactory手动创建作用域获取Scoped生命周期的ContentDbContext,规避单例依赖Scoped服务的生命周期异常:
    builder.Services.AddSingleton(sp =>
    {
        var scopeFactory = sp.GetRequiredService<IServiceScopeFactory>();
        return new ContentTextCache(() =>
        {
            using var scope = scopeFactory.CreateScope();
            var dbContext = scope.ServiceProvider.GetRequiredService<ContentDbContext>();
            // 迁移原ReadTexts方法内的读库逻辑
            return dbContext.Contents.Select(c => c.Text).ToList();
        });
    });
    // 原有Transient服务注册逻辑保持不变
    builder.Services.AddTransient<IContentService, ContentService>();
    
  3. 修改ContentService实现,移除构造函数内的ReadTexts()调用,直接注入缓存容器获取已加载好的数据:
    public class ContentService : IContentService
    {
        private readonly ContentDbContext _context;
        private readonly List<string> _textList;
    
        public ContentService(ContentDbContext context, ContentTextCache textCache)
        {
            _context = context;
            _textList = textCache.Texts;
        }
    
        public string GetText(int id)
        {
            return _textList[id];
        }
    }
    

轻量实现(无需额外注册服务)

如果不想额外定义缓存类,也可以直接在ContentService类中使用静态Lazy<T>字段存储共享数据,所有Transient实例会共享同一个类级别的静态字段:

public class ContentService : IContentService
{
    private readonly ContentDbContext _context;
    // 静态字段属于类级别,所有服务实例共享
    private static readonly Lazy<List<string>> _textListLazy = new(() =>
    {
        var httpContextAccessor = new HttpContextAccessor();
        using var scope = httpContextAccessor.HttpContext?.RequestServices.CreateScope();
        var dbContext = scope?.ServiceProvider.GetRequiredService<ContentDbContext>();
        return dbContext?.Contents.Select(c => c.Text).ToList() ?? new List<string>();
    }, LazyThreadSafetyMode.ExecutionAndPublication);

    private readonly List<string> _textList;

    public ContentService(ContentDbContext context)
    {
        _context = context;
        _textList = _textListLazy.Value;
    }

    public string GetText(int id)
    {
        return _textList[id];
    }
}

该写法不需要额外注册服务,但静态逻辑强依赖IHttpContextAccessor,可测试性差,仅适合快速实现简单场景。


方案说明

  • 原有ContentService始终保持Transient生命周期,每次注入都会生成新实例,完全符合注册约束
  • 读库加载逻辑仅会在首次访问缓存时执行一次,后续所有Transient实例直接复用已加载的缓存数据,不会重复触发重负载操作
  • 如果后续需要支持内容更新后刷新缓存,只需要在缓存容器中增加线程安全的刷新方法,在内容更新逻辑中调用即可,不需要调整服务生命周期配置

内容的提问来源于stack exchange,提问作者Jaber

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 15:06:21