虽然设置了 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;
}
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;
}