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

.NET中使用UnitOfWork设计跨接口无法跟踪实体更新的问题

问题分析与解决方案

问题根源

你的代码无法生效的核心原因是:ASP.NET Core 中默认的 DbContext 和 UnitOfWork 都是 Scoped 生命周期服务。每个API请求都会创建独立的服务实例,请求结束后实例会被销毁。

  • 调用/create接口时,会创建一个新的UnitOfWork和DbContext,日志实体被添加到这个实例的跟踪中,但请求结束后该DbContext就被释放了。
  • 调用/save_changes接口时,会创建另一个全新的UnitOfWork和DbContext,这个实例根本不知道之前请求中添加的日志实体,调用SaveChangesAsync自然不会提交任何数据。

要实现「多次创建日志后统一提交」的需求,必须打破单请求的生命周期限制,将待提交的日志暂存在跨请求的共享存储中,再在提交时批量写入数据库。

可行解决方案

方案1:使用共享缓存暂存日志(推荐轻量场景)

通过内存缓存(单机部署)或分布式缓存(多实例部署)暂存待提交的日志,在调用提交接口时批量写入数据库。

1. 修改日志服务,将日志存入缓存

public class LogService
{
    private readonly IMemoryCache _memoryCache;
    private const string PendingLogsKey = "PendingLogs";
    private static readonly object _lockObj = new object(); // 保证多线程下缓存操作安全

    public LogService(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
    }

    public async Task CreateLog(LogCreateModel logCreateModel)
    {
        // 转换DTO为实体
        var logEntity = new Log
        {
            Message = logCreateModel.Message,
            CreatedAt = DateTime.UtcNow,
            // 填充其他字段
        };

        // 线程安全地更新缓存中的日志列表
        lock (_lockObj)
        {
            var pendingLogs = _memoryCache.Get<List<Log>>(PendingLogsKey) ?? new List<Log>();
            pendingLogs.Add(logEntity);
            _memoryCache.Set(PendingLogsKey, pendingLogs, new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) // 设置过期时间,避免日志滞留
            });
        }

        await Task.CompletedTask;
    }
}

2. 扩展UnitOfWork,添加批量提交日志的方法

public class UnitOfWork : IUnitOfWork
{
    private readonly PrimeLogDatabaseContext _context;
    // ... 原有代码

    public async Task BatchCommitPendingLogsAsync(IMemoryCache memoryCache)
    {
        const string PendingLogsKey = "PendingLogs";
        var pendingLogs = memoryCache.Get<List<Log>>(PendingLogsKey);
        
        if (pendingLogs == null || !pendingLogs.Any())
            return;

        // 批量添加实体到DbContext
        await _context.Logs.AddRangeAsync(pendingLogs);
        await SaveChangesAsync();

        // 提交后清空缓存
        memoryCache.Remove(PendingLogsKey);
    }
}

3. 修改提交接口的实现

[HttpPost, Route("save_changes")]
public async Task<IActionResult> SaveChangesAsync([FromServices] IMemoryCache memoryCache)
{
    await _unitOfWork.BatchCommitPendingLogsAsync(memoryCache);
    return Ok();
}

多实例部署适配

如果你的API是多服务器部署,本地内存缓存无法跨实例共享,需要将IMemoryCache替换为IDistributedCache(比如Redis),并对日志列表进行序列化存储:

// 使用IDistributedCache的示例
public async Task CreateLog(LogCreateModel logCreateModel)
{
    var logEntity = new Log { /* 填充属性 */ };
    var pendingLogsJson = await _distributedCache.GetStringAsync(PendingLogsKey);
    var pendingLogs = string.IsNullOrEmpty(pendingLogsJson) 
        ? new List<Log>() 
        : JsonSerializer.Deserialize<List<Log>>(pendingLogsJson);
    
    pendingLogs.Add(logEntity);
    await _distributedCache.SetStringAsync(PendingLogsKey, JsonSerializer.Serialize(pendingLogs), new DistributedCacheEntryOptions
    {
        AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
    });
}

方案2:使用消息队列(适合高并发/复杂场景)

如果你的系统有高并发需求,或者需要更可靠的日志暂存机制,可以用消息队列(如RabbitMQ、Kafka):

  1. 调用/create接口时,将日志消息发送到队列。
  2. 调用/save_changes接口时,消费队列中的所有消息,批量写入数据库。
  3. 可以配合定时任务,避免队列中的消息长期未被消费。

关键注意事项

  • 线程安全:多个请求同时写入缓存时,必须加锁或使用线程安全的集合,避免日志丢失。
  • 缓存过期:务必设置缓存过期时间,防止因提交接口未被调用导致日志永久滞留。
  • 批量插入性能:如果待提交日志数量极大,建议分批次插入数据库,避免单次操作占用过多数据库资源。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 11:39:48