Шикарный запрос со списком параметров

Я пытаюсь выполнить запрос с помощью 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 может обрабатывать списки параметров при массовых вставках и обновлениях, но может ли он не делать этого для выборки?


person Jarrod    schedule 08.11.2012    source источник
comment
Вы видели обычай в примерах на главной странице dapper? Это действительно хорошо поддерживаемый сценарий   -  person Marc Gravell    schedule 08.11.2012
comment
@Marc Марк Да, я знаю, как легко сделать приведенный выше пример с Dapper, используя «IN». Позвольте мне изменить свой вопрос, чтобы показать, почему IN не работает в моей ситуации. Я попытался выделить то, что хочу сделать, но, очевидно, мой пример слишком упрощен.   -  person Jarrod    schedule 08.11.2012
comment
@Marc, я изменил пример, чтобы он был ближе к тому, что я на самом деле пытаюсь сделать, а именно к созданию данных отчетов за определенный период времени.   -  person Jarrod    schedule 08.11.2012
comment
Причина, по которой Dapper поддерживает списки во вставке, заключается в том, что каждая вставка — это отдельная команда. Вы действительно хотите выполнить 24 отдельных выбора? Мой ответ для разделения данных с использованием диапазонов дат — создать временную таблицу с моими диапазонами и присоединиться к ней. Это обычный T-SQL и не имеет ничего общего с Dapper, но Dapper, очевидно, может отобразить для вас результаты окончательного выбора. Если найду время, добавлю пример в качестве ответа. Я понимаю, что это вопрос трехлетней давности, но я пришел сюда из Google. Другие люди могут оценить ответ.   -  person Glazed    schedule 03.08.2016
comment
Я добавил свой ответ, как и обещал.   -  person Glazed    schedule 05.08.2016


Ответы (4)


А, кажется, я понимаю, что ты имеешь в виду...

Да, есть сценарий, который мы поддерживаем для Execute и который не поддерживается для Query, а именно: последовательное выполнение одной и той же операции с диапазоном различных значений параметров. Это имеет смысл для Execute, но для запроса это, вероятно, означает, что вы должны искать другой запрос, используя in. В качестве альтернативы, просто зациклите и объедините.

Вместо этого он просматривает объект с одним параметром и ищет общедоступные значения — у перечисляемого нет подходящих значений параметров для dapper.

person Marc Gravell    schedule 08.11.2012
comment
Да, этого я и боялся. Мой фактический запрос довольно сложен, но включает в себя создание набора результатов, где каждая строка содержит COUNT различных вещей на основе метки времени. Временная метка должна использоваться в паре JOIN, поэтому я не думаю, что есть какой-либо способ использовать IN. Я не знаю никакого способа создать SQL-запрос, который будет выбирать каждый час между двумя временными метками, поэтому мне пришлось использовать C # для их создания. Мое текущее решение выполняет несколько независимых запросов, как вы предлагаете. Спасибо - person Jarrod; 08.11.2012
comment
Было бы здорово, если бы Dapper мог преобразовать параметр IEnumerable в производную таблицу (для использования в JOINs). Имена столбцов могут быть основаны на имени параметра. - person D'Arcy Rittich; 08.11.2012
comment
@RedFilter ты имеешь в виду табличную переменную? - person Marc Gravell; 08.11.2012
comment
@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 - person D'Arcy Rittich; 08.11.2012
comment
@RedFilter вы можете сделать это с помощью специализированного - где t.id в @vals, передав new { vals } в качестве аргументов - person Marc Gravell; 08.11.2012
comment
@MarcGravell Подумайте о ситуации, когда вы хотите предоставить Dapper список значений (например, дат), которые будут использоваться в качестве левой таблицы в LEFT OUTER JOIN, и где нет существующей таблицы для их запроса (или которая, как известно, содержит все даты), поэтому использование IN невозможно. Это тот случай, с которым я бы хотел справиться. Вот почему вам нужно построить таблицу, используя UNION ALL. К сожалению, это не будет платформо-нейтральным, поскольку Oracle потребует from dual. - person D'Arcy Rittich; 08.11.2012
comment
@redfilter поможет ли там TVP? - person Marc Gravell; 08.11.2012
comment
@MarcGravell Конечно, для баз данных, которые их поддерживают. - person D'Arcy Rittich; 08.11.2012

Попробуй это:

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});
person D'Arcy Rittich    schedule 08.11.2012
comment
Должен где где Name в @names, да? - person Marc Gravell; 08.11.2012
comment
Смотрите комментарии выше. Как я указал в своем первоначальном вопросе, идентификатор не работает в моем запросе. - person Jarrod; 08.11.2012
comment
Похоже, что ваш вопрос сейчас несколько неверен, но ваше намерение не так ясно (например, вы сравниваете DateCreated с @Hour?). Я не совсем уверен, где вы застряли. Начните с чтения документации Dapper, вы неправильно передаете параметры. - person D'Arcy Rittich; 08.11.2012
comment
Я думаю, что ответ Марка подтверждает мой вывод о том, что существует разница между тем, как Dapper будет обрабатывать параметры между Execute и Query. Я сделал дополнительное редактирование, чтобы, надеюсь, было понятнее. - person Jarrod; 08.11.2012
comment
@Jarrod Я думаю, что предоставление образцов данных и желаемого результата поможет. Вы можете использовать GROUP BY, чтобы получить то, что хотите. - person D'Arcy Rittich; 08.11.2012
comment
@RedFilter Если вы считаете, что это возможно, я найду время, чтобы настроить вопрос, предоставляющий схему и тестовые данные для моего сценария, однако, основываясь на других вопросах, которые я видел, я не думаю, что возможно создать один запрос в MySQL, чтобы делать все, что я хочу. Вот два запроса, которые я запускаю. gist.github.com/4040312 Первый дает мне максимальное и минимальное время, и я использую C# для вычислять каждый час между двумя временами для выполнения второго запроса. - person Jarrod; 08.11.2012
comment
давайте продолжим это обсуждение в чате - person Jarrod; 08.11.2012
comment
Если вы хотите использовать его с PostgreSQL и получить ошибку, похожую на operator does not exist: integer = integer[], попробуйте следующее: select * from people where Name = ANY(@names) - person rotman; 02.10.2013

Я знаю, что опоздал на эту вечеринку, но я думаю, что понимаю, что этот запрос означает, что вы просто хотите передать некоторые свойства и сгенерировать свой запрос на основе этих динамических свойств.

с приведенным ниже кодом я могу использовать любой тип, а затем просто заполнить и передать объект этого типа с несколькими наборами значений (я называю это моим объектом запроса), и запрос будет сгенерирован для поиска объектов, которые соответствуют значениям, которые вы установили в своем объекте запроса.

* будьте осторожны с логическими значениями и вещами, которые имеют значения по умолчанию.

Пример динамического запроса

    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

затем он выдает любые «домены», соответствующие приведенному выше запросу.

person Dylan Hayes    schedule 11.12.2015
comment
вместо того, чтобы возиться с тем, когда добавить И, попробуйте string.Join(" AND ", listOfParamEqualValue) Я бы все же не советовал использовать этот шаблон, потому что он использует динамический sql, и вероятность последующего неправильного использования или злоупотребления увеличивается. - person StingyJack; 08.12.2019

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

Вот как бы я это сделал, но это предполагает одно: у вас есть таблица в вашей базе данных с именем «Числа», в которой есть произвольное количество целых чисел, по одному в строке, начиная с 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.
*/
person Glazed    schedule 04.08.2016