Question Requête Dapper avec liste de paramètres


J'essaie d'exécuter une requête avec Dapper avec un ensemble connu de paramètres, mais avec une liste de valeurs pour ces paramètres. Un exemple simple de ce que j'essaie de faire serait:

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 lance une exception avec 'Parameter' @Status 'doit être défini'. Je sais que Dapper peut traiter des listes de paramètres lors des insertions en vrac et des mises à jour, mais ne peut-il pas le faire pour les sélections?


12
2017-11-08 15:03


origine


Réponses:


Ah, je pense que je vois ce que tu veux dire ...

Oui, il y a un scénario pour lequel nous soutenons Execute qui n'est pas pris en charge pour Query, en particulier: pour exécuter la même opération de manière séquentielle avec une plage de valeurs de paramètres différentes. Cela a du sens pour Execute, mais pour interroger cela signifie probablement que vous devriez regarder une requête différente en utilisant in. Alternativement, il suffit de boucler et de concaténer.

Au lieu de cela, il examine l'objet de paramètre unique et recherche des valeurs publiques - un énumérable n'a aucune valeur de paramètre appropriée pour dapper.


10
2017-11-08 16:46



Essaye ça:

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});

17
2017-11-08 15:08



Je sais que je suis très en retard pour cette soirée, mais je crois comprendre que cette demande signifie que vous voulez simplement transmettre certaines propriétés et générer votre requête en fonction de ces propriétés dynamiques.

avec le code ci-dessous, je peux utiliser n'importe quel type, puis simplement remplir et passer un objet de ce type avec quelques valeurs (j'appelle cela mon objet de requête), et la requête sera générée pour rechercher des objets correspondant aux valeurs vous définissez dans votre objet de requête.

* faites attention aux bools et aux choses qui ont des valeurs par défaut.

Exemple de requête dynamique

    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();
    }

Usage

* repo est la classe qui contient la fonction ci-dessus

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

Sortie

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

puis il crache tous les "domaines" qui correspondent à la requête ci-dessus.


2
2017-12-11 19:16



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

C'est comme ça que je le ferais, mais cela suppose une chose: vous avez dans votre base de données une table nommée "Numbers" qui contient un nombre arbitraire d'entiers, un par ligne, commençant par 1, avec au moins 24 nombres.

C'est-à-dire que la table ressemble à ceci:

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

Si vous ne disposez pas d'une telle table, il est très facile et rapide d'en créer une pour cette commande:

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.

Vous ne pouvez pas avoir plusieurs lots dans une procédure stockée, mais vous pouvez dans une commande de texte. GO 16 exécute le lot précédent 16 fois. Si vous en aviez besoin dans une procédure stockée, vous pouvez répéter la seconde INSERT commande plusieurs fois au lieu d'utiliser des lots. 2 ^ 16 entiers sont exagérés pour cette requête particulière, mais c'est une commande que je copie et collez en cas de besoin et 2 ^ 16 est généralement suffisant, et si vite que je ne le fais généralement pas. GO 5 donnerait 32 nombres entiers, ce qui est suffisant pour 24 plages de dates.

Voici un script complet qui illustre ce travail:

--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.
*/

0
2017-08-04 20:59