如何为实体框架 Sql 提供程序编写测试并访问生成的 Sql 命令
Posted
技术标签:
【中文标题】如何为实体框架 Sql 提供程序编写测试并访问生成的 Sql 命令【英文标题】:How to write tests for Entity Framework Sql providers and access the generated Sql command 【发布时间】:2021-11-21 08:28:03 【问题描述】:我有一个动态 LINQ 库,可以为 anyIQueryable
支持列表和 EntityFramework 生成查询表达式...
我想进行一些测试来检查实体框架 SQL 提供程序生成的 SQL 查询。主要问题是我不能使用In-Memory
提供程序,因为它没有向我显示SQL 输出。在没有安装实际数据库的情况下如何编写这些测试?
有没有我可以使用的包? (我需要测试 EF SqlServer 和 mysql 提供程序)。
【问题讨论】:
【参考方案1】:使用Interceptor 并禁止实际执行,遵循文档中的此示例:
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default)
if (command.CommandText.StartsWith("-- Get_Daily_Message", StringComparison.Ordinal))
lock (_lock)
if (_message != null
&& DateTime.UtcNow < _queriedAt + new TimeSpan(0, 0, 10))
command.CommandText = "-- Get_Daily_Message: Skipping DB call; using cache.";
result = InterceptionResult<DbDataReader>.SuppressWithResult(new CachedDailyMessageDataReader(_id, _message));
return new ValueTask<InterceptionResult<DbDataReader>>(result);
Interceptors - Before Execution
【讨论】:
谢谢你一个小问题,我需要生成迁移才能使用此功能还是无需迁移即可工作? 迁移完全是可选的。很多人从不使用它们,而且这个功能与迁移无关。 这个答案真的很接近我的需要,但没有回答单元测试部分。例如,也许我也应该使用DbConnectionInterceptor
来抑制数据库连接,以及如何从单元测试中访问提供程序生成的命令。等等......无论如何,谢谢大卫,至少现在我知道在哪里寻找和研究。【参考方案2】:
感谢@David Browne - 我最终得到了以下代码。只是想分享一下,如果以后有人遇到类似的问题。
unit test project link
// interceptors.cs
public class SuppressConnectionInterceptor : DbConnectionInterceptor
public override ValueTask<InterceptionResult> ConnectionOpeningAsync(DbConnection connection, ConnectionEventData eventData,
InterceptionResult result,
CancellationToken cancellationToken = new())
result = InterceptionResult.Suppress();
return base.ConnectionOpeningAsync(connection, eventData, result, cancellationToken);
public override InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
result = InterceptionResult.Suppress();
return base.ConnectionOpening(connection, eventData, result);
public class SuppressCommandResultInterceptor : DbCommandInterceptor
public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
result = InterceptionResult<DbDataReader>.SuppressWithResult(new EmptyMessageDataReader());
return result;
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default)
result = InterceptionResult<DbDataReader>.SuppressWithResult(new EmptyMessageDataReader());
return new ValueTask<InterceptionResult<DbDataReader>>(result);
public class EmptyMessageDataReader : DbDataReader
private readonly List<User> _users = new List<User>();
public EmptyMessageDataReader()
public override int FieldCount
=> 0;
public override int RecordsAffected
=> 0;
public override bool HasRows
=> false;
public override bool IsClosed
=> true;
public override int Depth
=> 0;
public override bool Read()
=> false;
public override int GetInt32(int ordinal)
=> 0;
public override bool IsDBNull(int ordinal)
=> false;
public override string GetString(int ordinal)
=> "suppressed message";
public override bool GetBoolean(int ordinal)
=> true;
public override byte GetByte(int ordinal)
=> 0;
public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
=> 0;
public override char GetChar(int ordinal)
=> '\0';
public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
=> 0;
public override string GetDataTypeName(int ordinal)
=> string.Empty;
public override DateTime GetDateTime(int ordinal)
=> DateTime.Now;
public override decimal GetDecimal(int ordinal)
=> 0;
public override double GetDouble(int ordinal)
=> 0;
public override Type GetFieldType(int ordinal)
=> typeof(User);
public override float GetFloat(int ordinal)
=> 0;
public override Guid GetGuid(int ordinal)
=> Guid.Empty;
public override short GetInt16(int ordinal)
=> 0;
public override long GetInt64(int ordinal)
=> 0;
public override string GetName(int ordinal)
=> "";
public override int GetOrdinal(string name)
=> 0;
public override object GetValue(int ordinal)
=> new object();
public override int GetValues(object[] values)
=> 0;
public override object this[int ordinal]
=> new object();
public override object this[string name]
=> new object();
public override bool NextResult()
=> false;
public override IEnumerator GetEnumerator()
=> _users.GetEnumerator();
// DbContext configuration
public class MyDbContext : DbContext
public DbSet<User> Users get; set;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
optionsBuilder.UseSqlServer("Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;");
optionsBuilder.AddInterceptors(new SuppressCommandResultInterceptor());
optionsBuilder.AddInterceptors(new SuppressConnectionInterceptor());
base.OnConfiguring(optionsBuilder);
// access queryString in unit test
var actual = _dbContext.Users.Where(q => q.Name == "John").ToQueryString();
【讨论】:
以上是关于如何为实体框架 Sql 提供程序编写测试并访问生成的 Sql 命令的主要内容,如果未能解决你的问题,请参考以下文章