Evi*_*eon 6 c# entity-framework-core ef-core-3.1
如何防止使用 Entity Framework Core 进行同步数据库访问?例如,我如何确保我们调用的是 ToListAsync() 而不是 ToList()?
我一直在尝试在对调用同步 API 的方法进行单元测试时抛出异常。是否有配置选项或一些我们可以覆盖的方法来使其工作?
我尝试过使用 DbCommandInterceptor,但是在使用内存数据库进行测试时,没有调用任何拦截器方法。
解决方案是使用命令拦截器。
public class AsyncOnlyInterceptor : DbCommandInterceptor
{
public bool AllowSynchronous { get; set; } = false;
public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result)
{
ThrowIfNotAllowed();
return result;
}
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
{
ThrowIfNotAllowed();
return result;
}
public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> result)
{
ThrowIfNotAllowed();
return result;
}
private void ThrowIfNotAllowed()
{
if (!AllowSynchronous)
{
throw new NotAsyncException("Synchronous database access is not allowed. Use the asynchronous EF Core API instead.");
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果您想为此编写一些测试,可以使用 Sqlite 内存数据库。Database.EnsureCreatedAsync() 方法确实使用同步数据库访问,因此您需要一个选项来针对特定情况启用此功能。
public partial class MyDbContext : DbContext
{
private readonly AsyncOnlyInterceptor _asyncOnlyInterceptor;
public MyDbContext(IOptionsBuilder optionsBuilder)
: base(optionsBuilder.BuildOptions())
{
_asyncOnlyInterceptor = new AsyncOnlyInterceptor();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.AddInterceptors(_asyncOnlyInterceptor);
base.OnConfiguring(optionsBuilder);
}
public bool AllowSynchronous
{
get => _asyncOnlyInterceptor.AllowSynchronous;
set => _asyncOnlyInterceptor.AllowSynchronous = value;
}
}
Run Code Online (Sandbox Code Playgroud)
这里有一些测试助手。确保您没有使用序列 (modelBuilder.HasSequence),因为 Sqlite 不支持此功能。
public class InMemoryOptionsBuilder<TContext> : IOptionsBuilder
where TContext : DbContext
{
public DbContextOptions BuildOptions()
{
var optionsBuilder = new DbContextOptionsBuilder<TContext>();
var connection = new SqliteConnection("Filename=:memory:");
connection.Open();
optionsBuilder = optionsBuilder.UseSqlite(connection);
return optionsBuilder.Options;
}
}
public class Helpers
{
public static async Task<MyDbContext> BuildTestDbContextAsync()
{
var optionBuilder = new InMemoryOptionsBuilder<MyDbContext>();
var context = new MyDbContext(optionBuilder)
{
AllowSynchronous = true
};
await context.Database.EnsureCreatedAsync();
context.AllowSynchronous = false;
return context;
}
}
Run Code Online (Sandbox Code Playgroud)