带有参数列表的 Dapper 查询

Posted

技术标签:

【中文标题】带有参数列表的 Dapper 查询【英文标题】:Dapper query with list of parameters 【发布时间】:2012-10-28 19:11:52 【问题描述】:

我正在尝试使用 Dapper 运行具有一组已知参数的查询,但具有这些参数的值列表。我正在尝试做的一个简单示例是:

DateTime endDate = DateTime.Now;
DateTime startDate = endDate.AddHours(-24);

string query = "select COUNT(*) from Test where Status = @Status AND DateCreated <= @Hour;";
var stuff = con.Query(query, (startDate).ByHourTo(endDate).Select(hour => new

     Status = 1,
     Hour = hour,
));

Dapper 抛出异常,“必须定义参数 '@Status'”。我知道 Dapper 可以在进行批量插入和更新时处理参数列表,但它不能对选择执行此操作吗?

【问题讨论】:

您是否在 dapper 主页上看到了自定义“in”示例?这确实是一个得到很好支持的场景 @Marc 是的,我知道如何使用 Dapper 轻松使用“IN”来完成上面的示例。让我修改我的问题,希望能说明为什么 IN 在我的情况下不起作用。我试图提炼出我想做的事情,但显然我的例子太简单了。 @Marc,我已将示例修改为更接近我实际尝试做的事情,即在时间跨度内生成报告数据。 Dapper 在插入中支持列表的原因是因为每个插入都是一个单独的命令。您真的要执行 24 个单独的选择吗?对于使用日期范围对数据进行分区,我的首选答案是使用我的范围生成一个临时表并加入它。这只是普通的 T-SQL,与 Dapper 无关,但 Dapper 显然可以为您映射最终 Select 的结果。如果我找到时间,我会添加一个例子作为答案。我意识到这是一个三年前的问题,但我是从谷歌来到这里的。其他人可能会欣赏这个答案。 我按照承诺添加了答案。 【参考方案1】:

试试这个:

List<string> names = new List<string>  "Bob", "Fred", "Jack" ;
string query = "select * from people where Name in @names";
var stuff = connection.Query<ExtractionRecord>(query, new names);

【讨论】:

应该 where "where Name in @names",是吗? 见上面的 cmets。正如我在原始问题中规定的那样,ID 在我的查询中不起作用。 现在看来您的问题在某些方面是错误的,但您的意图并不那么明确(例如,您将 DateCreated 与 @Hour 进行比较?)。我不确定你被困在哪里。从阅读 Dapper 文档开始,您没有正确传递参数。 @Jarrod 我认为提供示例数据和所需的输出会有所帮助。您也许可以使用GROUP BY 来获得您想要的。 如果你想在 PostgreSQL 中使用它并得到类似于 operator does not exist: integer = integer[] 的错误,那么试试这个:select * from people where Name = ANY(@names)【参考方案2】:

啊,我想我明白你的意思了……

是的,我们支持Execute 不支持 Query 的场景,特别是:使用一系列不同的参数值顺序运行相同的操作。这对Execute 有意义,但对于查询,这可能意味着您应该使用in 查看不同的查询。或者,只需循环和连接。

相反,它正在查看单个参数对象并寻找公共值 - 可枚举没有任何适合 dapper 的参数值。

【讨论】:

是的,这就是我害怕的。我的实际查询非常复杂,但涉及生成一个结果集,其中每行包含基于时间戳的各种事物的 COUNT。时间戳必须在几个 JOIN 中使用,所以我认为没有任何方式可以使用 IN。我不知道有什么方法可以创建一个在两个时间戳之间每小时进行一次选择的 SQL 查询,所以我不得不使用 C# 来生成它们。我当前的解决方案按照您的建议执行多个独立查询。谢谢 如果 Dapper 可以将 IEnumerable 参数转换为派生表(在JOINs 中使用),那就太酷了。列名可以基于参数名。 @RedFilter 你的意思是像一个表变量吗? @MarcGravell 我正在考虑一个值列表(例如,1,2,3),它会变成:(select 1 as val union all select 2 union all select 3)。语法可以是select * from MyTable t inner join @vals v on t.id = v.val @RedFilter 您可以使用专门的“in”来做到这一点 - “@vals 中的 t.id”,将 new vals 作为参数传递【参考方案3】:

我知道我迟到了,但我想我理解这个请求意味着您只想传递一些属性并根据这些动态属性生成查询。

使用下面的代码,我可以使用任何类型,然后只需填充并传入该类型的对象并设置一些值(我称之为我的查询对象),然后将生成查询以查找匹配的对象您在查询对象中设置的值。

*注意布尔值和具有默认值的东西。

动态查询示例

    public IEnumerable<T> Query<T>(T templateobject) 
        var sql = "SELECT * From " + typeof(T).Name + " Where ";

        var list = templateobject.GetType().GetProperties()
             .Where(p => p.GetValue(templateobject) != null)
             .ToList();

        int i = 0;

        Dictionary<string, object> dbArgs = new Dictionary<string, object>();

        list.ForEach(x =>
        
            sql += x.Name + " = @" +  x.Name;

            dbArgs.Add(x.Name, x.GetValue(templateobject));

            if (list.Count > 1 && i < list.Count - 1) 
                sql += " AND ";
                i++;
            
        );

        Debug.WriteLine(sql);

        return _con.Query<T>(sql, dbArgs).ToList();
    

用法

*repo 是包含上述函数的类

var blah = repo.Query<Domain>(new Domain()  Id = 1, IsActive=true );

输出

SELECT * From Domain Where Id = @Id AND IsActive = @IsActive

然后它会吐出任何与上述查询匹配的“域”。

【讨论】:

不要大惊小怪何时添加 AND,尝试string.Join(" AND ", listOfParamEqualValue) 我仍然建议不要使用这种模式,因为它确实使用动态 sql,并且以后误用或滥用的可能性增加了。 【参考方案4】:
DECLARE @Now datetime
SET @Now = getdate()

SELECT
    DATEADD( hh, -n, @Now ) AS StartDate,
    DATEADD( hh, -n+1, @Now ) AS EndDate
INTO
    #DateRanges
FROM 
    Numbers
WHERE
    n <= 24

SELECT
    COUNT(*) AS [Count],
    #DateRanges.StartDate
FROM
    Test
        JOIN
    #DateRanges
        ON Test.DateCreated >= #DateRanges.StartDate
        AND Test.DateCreated < #DateRanges.EndDate
GROUP BY
    #DateRanges.StartDate

我会这样做,但这假设一件事:您的数据库中有一个名为“Numbers”的表,其中包含任意数量的整数,每行一个,从 1 开始,至少有 24 个数字在里面。

也就是说,表格是这样的:

n
-----
1
2
3
4
5
...

如果你没有这样的表,只为这个命令制作一个非常快速和容易:

CREATE TABLE #Numbers
(
    n int
)

SET NOCOUNT ON

INSERT #Numbers values (1);
GO
INSERT #Numbers SELECT n + (SELECT COUNT(*) FROM #Numbers) FROM #Numbers
GO 16 --execute batch 16 times to create 2^16 integers.

在一个存储过程中不能有多个批处理,但可以在一个文本命令中。 GO 16 运行前一批 16 次。如果您在存储过程中需要此功能,则可以多次重复第二个INSERT 命令,而不是使用批处理。 2^16 整数对于这个特定的查询来说太过分了,但它是我在需要时复制和粘贴的命令,2^16 通常就足够了,而且速度如此之快,以至于我通常不会费心去改变它。 GO 5 会产生 32 个整数,这对于 24 个日期范围来说已经足够了。

这里有一个完整的脚本来说明这个工作:

--Create a temp table full of integers. This could also be a static 
--table in your DB. It's very handy.
--The table drops let us run this whole script multiple times in SSMS without issue.
IF OBJECT_ID( 'tempdb..#Numbers' ) IS NOT NULL
    DROP TABLE #Numbers

CREATE TABLE #Numbers
(
    n int
)

SET NOCOUNT ON

INSERT #Numbers values (1);
GO
INSERT #Numbers SELECT n + (SELECT COUNT(*) FROM #Numbers) FROM #Numbers
GO 16 --execute batch 16 times to create 2^16 integers.

--Create our Test table. This would be the real table in your DB, 
-- so this would not go into your SQL command.
IF OBJECT_ID( 'tempdb..#Test' ) IS NOT NULL
    DROP TABLE #Test

CREATE TABLE #Test
(
    [Status] int,
    DateCreated datetime
)

INSERT INTO 
    #Test 
SELECT 
    1, 
    DATEADD( hh, -n, getdate() )
FROM 
    #Numbers
WHERE
    n <= 48

--#Test now has 48 records in it with one record per hour for 
--the last 48 hours.

--This drop would not be needed in your actual command, but I 
--add it here to make testing this script easier in SSMS.
IF OBJECT_ID( 'tempdb..#DateRanges' ) IS NOT NULL
    DROP TABLE #DateRanges

--Everything that follows is what would be in your SQL you send through Dapper 
--if you used a static Numbers table, or you might also want to include
--the creation of the #Numbers temp table.
DECLARE @Now datetime
SET @Now = getdate()

SELECT
    DATEADD( hh, -n, @Now ) AS StartDate,
    DATEADD( hh, -n+1, @Now ) AS EndDate
INTO
    #DateRanges
FROM 
    #Numbers
WHERE
    n <= 24

/* #DateRanges now contains 24 rows that look like this:

StartDate               EndDate
2016-08-04 15:22:26.223 2016-08-04 16:22:26.223
2016-08-04 14:22:26.223 2016-08-04 15:22:26.223
2016-08-04 13:22:26.223 2016-08-04 14:22:26.223
2016-08-04 12:22:26.223 2016-08-04 13:22:26.223
...

Script was run at 2016-08-04 16:22:26.223. The first row's end date is that time. 
This table expresses 24 one-hour datetime ranges ending at the current time. 
It's also  easy to make 24 one-hour ranges for one calendar day, or anything
similar.
*/

--Now we just join that table to our #Test table to group the rows those date ranges.

SELECT
    COUNT(*) AS [Count],
    #DateRanges.StartDate
FROM
    #Test
        JOIN
    #DateRanges
        ON #Test.DateCreated >= #DateRanges.StartDate
        AND #Test.DateCreated < #DateRanges.EndDate
GROUP BY
    #DateRanges.StartDate

/*
Since we used two different getdate() calls to populate our two tables, the last record of 
our #Test table is outside of the range of our #DateRange's last row by a few milliseconds,
so we only get 23 results from this query. This script is just an illustration.
*/

【讨论】:

以上是关于带有参数列表的 Dapper 查询的主要内容,如果未能解决你的问题,请参考以下文章

DB2 - 使用带有字符串数组参数的 Dapper 选择查询将不会返回正确的结果

dapper 多对多查询对象和对象列表

使用dapper进行参数化查询

使用列表参数批量插入/更新调用到 Dapper 中对数据库的单个操作/请求

Dapper 多参数存储过程查询不会从数据库返回任何内容

使用 DateTime 类型参数的 Dapper 查询速度慢?