web-dev-qa-db-fra.com

Requête Dapper avec liste de paramètres

J'essaie d'exécuter une requête avec Dapper avec un jeu de paramètres connu, 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 lève une exception avec 'Parameter' @Status 'doit être défini'. Je sais que Dapper peut traiter des listes de paramètres lors de l'insertion en bloc et des mises à jour, mais ne peut-il pas le faire pour les sélections?

12
Jarrod

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

Oui, nous prenons en charge un scénario pour Execute qui n'est pas pris en charge pour Query, à savoir: exécuter la même opération de manière séquentielle avec une plage de valeurs de paramètre différentes. Cela a du sens pour Execute, mais pour requête cela signifie probablement que vous devriez examiner une requête différente en utilisant in. Alternativement, juste en boucle et concat.

Au lieu de cela, il examine l'objet à paramètre unique et recherche des valeurs publiques - un énumérable ne possède pas de valeurs de paramètre appropriées pour dapper.

10
Marc Gravell

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
RedFilter

Je sais que je suis bien en retard pour cette soirée, mais je pense avoir compris que cette demande signifiait que vous souhaitiez 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 juste remplir et transmettre un objet de ce type avec quelques valeurs définies (j’appelle cet 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();
    }

Utilisation

* 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 tout "Domaines" correspondant à la requête ci-dessus.

2
Dylan Hayes
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 ce que je 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, en 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 possédez pas une telle table, il est très rapide et facile d'en créer une juste 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 le faire dans une commande de texte. GO 16 exécute le lot précédent 16 fois. Si vous en avez besoin dans une procédure stockée, vous pouvez répéter la deuxième commande INSERT plusieurs fois au lieu d’utiliser des lots. 2 ^ 16 nombres entiers est excessif pour cette requête particulière, mais c’est une commande que je copie et colle à la demande et 2 ^ 16 est généralement suffisant, et si rapide que je n’ai généralement pas la peine de le changer. GO 5 donnerait 32 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
Glazed