在 C# 中使用 SQLite 的 Dapper 查询抛出错误“'必须为以下参数添加值”

Dapper query with SQLite in C# throws error "'Must add values for the following parameters"

我正在尝试对 SQLite 3 数据库执行以下 Dapper 查询:

conn.QuerySingleOrDefault<DalTableConfig>
     ("select id, modelId, tableName, schemaName 
      from tables where tableName = $tableName;", new {tableName});

执行这一行,我得到这个错误:

System.InvalidOperationException: 'Must add values for the following parameters: $tableName'

Google 得出了相当多的结果,一个常见的结果是参数区分大小写,但这在此处不适用,大小写匹配。

Whosebug 有一个 other question 有一个未被接受但有效的答案,解决方法如下:

var parameters = new DynamicParameters();
parameters.Add("@tableName" , tableName);
conn.QuerySingleOrDefault<DalTableConfig>("select id, modelId, tableName, schemaName from tables where tableName = $tableName;", parameters);

这没有我之前得到的异常,但我仍然想知道为什么更简单的匿名对象参数不起作用。

我调试了 Dapper 源代码,发现了问题出现的地方。 SqlMapper class 的 QuerySingleOrDefault 方法的第二行抛出异常:

var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None);
return QueryRowImpl<T>(cnn, Row.SingleOrDefault, ref command, typeof(T));

此处 param 包含正确的 tableName 属性,command 在其 Parameters 集合中包含此内容。当我检查 QueryRowImpl 方法时,我发现这段代码:

private static T QueryRowImpl<T>(IDbConnection cnn, Row row, ref CommandDefinition command, Type effectiveType)
{
    object param = command.Parameters;
    var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType());
    var info = GetCacheInfo(identity, param, command.AddToCache);

    IDbCommand cmd = null;
    IDataReader reader = null;

    bool wasClosed = cnn.State == ConnectionState.Closed;
    try
    {
        cmd = command.SetupCommand(cnn, info.ParamReader);

        if (wasClosed) cnn.Open();
        reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, (row & Row.Single) != 0
            ? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition
            : CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow);

下面这行失败了。就是这个方法抛出了异常:

ExecuteReaderWithFlagsFallback

在上面的代码中,CommandDefinition command 参数仍然有一个有效的 Parameters 集合,但是局部变量 IDbCommand cmd 来自 SetupCommand,奇怪的是,两个空 Parameters 集合。所以 SetupCommandinfo.ParamReader 似乎有一些错误导致参数丢失

我能够在测试中重现这个问题。下面的测试表明,当在 SQL.

中使用 @tableName 而不是 $tableName 时,OP 的原始代码有效
public class DalTableConfig
{
    public int Id { get; set; }
    public string ModelId { get; set; }
    public string TableName { get; set; }
    public string SchemaName { get; set; }
}

public class DapperTests
{
    [Fact]
    public void WhosebugDapperTest()
    {
        using var conn = new SqliteConnection("Data Source=test.db");

        conn.Execute("drop table if exists tables");
        conn.Execute("create table tables(id int, modelId varchar(255), tableName varchar(255), schemaName varchar(255))");
        conn.Execute("insert into tables select 1, 'modelId1', 'tableName1', 'schemaName1'");

        const string sql = @"
            select 
                id
                ,modelId
                ,tableName
                ,schemaName 
            from 
                tables 
            where 
               tableName = @tableName"; // here's the fix

        const string tableName = "tableName1";

        var actual = conn.QuerySingleOrDefault<DalTableConfig>(sql, new {tableName});

        Assert.NotNull(actual);
        Assert.Equal(tableName, actual.TableName);
    }
}