虽然设置了 NoTracking 行为,但实体仍在被跟踪

Entity is still being tracked although NoTracking behaviour is set

dbcontext 跟踪行为设置为 NoTracking,但我在测试中仍然失败。

与EF提供的IdentityDbContext相比,此上下文中的BaseDbContext没有任何相关代码。 BaseUser也一样,基本上只有IdentityUser。

创建DbContext的方法:

public static T GetDbContext<T>()
        where T : BaseDbContext<BaseUser<Guid>>
    {
        var optionBuilder = new DbContextOptionsBuilder<T>();
        optionBuilder.UseInMemoryDatabase(Guid.NewGuid().ToString());
        optionBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
        var obj = Activator.CreateInstance(typeof(T), optionBuilder.Options);
        if (obj == null)
        {
            throw new SystemException(typeof(T) + " was null!");
        }
        
        var ctx = (T)obj;
        ctx.Database.EnsureDeleted();
        ctx.Database.EnsureCreated();
        
        return ctx;
    }

测试失败:

    [Fact]
    public async void Test_UpdateSingle()
    {
        var dbContext = DbContextFactory.GetDbContext<SimpleDbContext>();
        var uow = UowFactory.GetUow<SimpleUow, SimpleDbContext>(dbContext);
        
        var id1 = Guid.NewGuid();
        var name1 = Guid.NewGuid().ToString();

        var testEntity1 = new DalSimpleEntity
        {
            Id = id1,
            Name = name1
        };

        uow.SimpleRepo.Add(testEntity1);
        await uow.SaveChangesAsync();

        var newName = Guid.NewGuid().ToString();
        testEntity1.Name = newName;

        //Fails here:
        uow.SimpleRepo.Update(testEntity1);
        await uow.SaveChangesAsync();
        Assert.Single(await uow.SimpleRepo.GetAllAsync());

        var getEntity1 = await uow.SimpleRepo.FirstOrDefaultAsync(id1);
        
        Assert.Equal(newName, getEntity1?.Name);
    }

UnitOfWork 用于将其用作 dbcontext 之上的层。 SaveChanges 方法直接调用 DbContext savechanges。

UnitOfWork 还包含对存储库的引用。

SimpleRepo 派生自 BaseRepository。 SimpleRepo 中没有任何更改。

public abstract class BaseRepository<TDbContext, TEntityIn, TEntityOut> : BaseRepositoryWebApp<TDbContext, TEntityIn, TEntityOut, Guid>, IBaseRepositoryWebApp<TEntityOut>
    where TEntityIn : class, IDomainEntityId, IDomainEntityId<Guid>
    where TEntityOut : class, IDomainEntityId, IDomainEntityId<Guid>
    where TDbContext : BaseDbContext<BaseUser<Guid>>
{
    protected readonly DbContext RepoDbContext;
    protected readonly DbSet<TEntityIn> RepoDbSet;
    protected readonly IBaseMapper<TEntityIn, TEntityOut> Mapper;

    public BaseRepository(TDbContext dbContext, IBaseMapper<TEntityIn, TEntityOut> mapper)
    {
        RepoDbContext = dbContext;
        RepoDbSet = dbContext.Set<TEntityIn>();
        Mapper = mapper;
    }

    public virtual async Task<IEnumerable<TEntityOut>> GetAllAsync(bool noTracking = true, Guid userId = default)
    {
        var entities = await InitQuery(noTracking, userId).ToListAsync();

        return entities.Select(e => Mapper.Map(e)!);
    }

    public virtual async Task<TEntityOut?> FirstOrDefaultAsync(TKey id, bool noTracking = true, Guid userId = default)
    {
        var query = InitQuery(noTracking, userId).FirstOrDefaultAsync(e => e.Id.Equals(id));

        return Mapper.Map(await query);
    }

    public virtual async Task<bool> ExistsAsync(TKey id, Guid userId = default)
    {
        return await InitQuery(userId: userId).AnyAsync(e => e.Id.Equals(id));
    }

    public virtual async Task<TEntityOut?> RemoveAsync(TKey id, Guid userId = default)
    {
        var entity = await InitQuery(userId: userId).FirstOrDefaultAsync(e => e.Id.Equals(id));
        if (entity == null) return null;
        return Mapper.Map(RepoDbSet.Remove(entity).Entity);
    }
    
    public TEntityOut Add(TEntityOut? entity)
    {
        return Mapper.Map(RepoDbSet.Add(Mapper.Map(entity)!).Entity)!;
    }

    public TEntityOut Update(TEntityOut? entity)
    {
        return Mapper.Map(RepoDbSet.Update(Mapper.Map(entity)!).Entity)!;
    }

    protected virtual IQueryable<TEntityIn> InitQuery(bool noTracking = true, Guid userId = default)
    {
        var query = RepoDbSet.AsQueryable();

        if (typeof(IDomainEntityUsers).IsAssignableFrom(typeof(TEntityIn)))
        {
            query = query.Where(e => (e as IDomainEntityUsers)!.UserId.Equals(userId));
        }
        
        if (noTracking)
        {
            query = query.AsNoTracking();
        }

        return query;
    }
}

我的问题是,我是否忘记了某个地方,我还应该声明使用 NoTracking 行为?

这个问题变得很困难,因为每次实体向下或向上移动层时(例如:从 uow 到 dbcontext 并返回,仅在此处包含 2 个映射)。 这确保调用

dbContext.Entity(entity).State = EntityState.Detached;

不是一个选项,因为 Savechanges 是使用 uow 对象而不是 dbContext 调用的。同样在实际用例中,dbContext 无法访问,因此根本无法从 dbContext 调用函数。

所以我搜索了分离所有对象的选项,这些对象在 saveChanges 之后仍然附加。

ChangeTracker.Clear(); 

满足这个要求。

这个问题的解决方法是:

 public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new())
    {
        var res = await base.SaveChangesAsync(cancellationToken);
        ChangeTracker.Clear();
        return res;
    }