SELEZIONA * DA X DOVE ID IN (...) con Dapper ORM


231

Qual è il modo migliore per scrivere una query con la clausola IN utilizzando Dapper ORM quando l'elenco di valori per la clausola IN proviene dalla logica aziendale? Ad esempio diciamo che ho una query:

SELECT * 
  FROM SomeTable 
 WHERE id IN (commaSeparatedListOfIDs)

Il commaSeparatedListOfIDsviene passato dalla logica di business e può essere qualsiasi tipo di IEnumerable(of Integer). Come costruirò una query in questo caso? Devo fare quello che ho fatto finora, che è fondamentalmente la concatenazione di stringhe o esiste una sorta di tecnica avanzata di mappatura dei parametri di cui non sono a conoscenza?

Risposte:


366

Dapper lo supporta direttamente. Per esempio...

string sql = "SELECT * FROM SomeTable WHERE id IN @ids"
var results = conn.Query(sql, new { ids = new[] { 1, 2, 3, 4, 5 }});

47
Penso che sia importante notare che esiste un limite finito al numero di articoli che è possibile inviare nel proprio array. L'ho capito nel modo più duro quando ho passato troppi ID. Non ricordo il numero esatto, ma dalla mia memoria penso che siano 200 elementi prima che Dapper smetta di funzionare / eseguire la query.
Marko

8
Marko, questo è importante. E, se lo stai facendo in questo modo, potresti considerare di trovare un altro modo di interrogare i tuoi dati, come fare un join o un anti-join piuttosto che passare un elenco di ID. La clausola IN non è la query più performante e può spesso essere sostituita da una clausola esistente, che sarà più veloce.
Don Rolling,

24
Cordiali saluti - SQL Server 2008 R2 ha un limite di 2100 voci nella INclausola.
Jesse,

6
E SQLite ha un limite predefinito di 999 variabili.
Cameron,

8
Attenzione: in SQL Server questo non riesce se si hanno più elementi nell'array e si racchiude il parametro tra parentesi. La rimozione delle parentesi risolve il problema.
ajbeaven

66

Direttamente dalla homepage del progetto GitHub :

Dapper ti consente di passare a IEnumerable e parametrizza automaticamente la tua query.

connection.Query<int>(
    @"select * 
      from (select 1 as Id union all select 2 union all select 3) as X 
      where Id in @Ids", 
    new { Ids = new int[] { 1, 2, 3 });

Sarà tradotto in:

select * 
from (select 1 as Id union all select 2 union all select 3) as X 
where Id in (@Ids1, @Ids2, @Ids3)

// @Ids1 = 1 , @Ids2 = 2 , @Ids2 = 3

43

Se la tua INclausola è troppo grande per essere gestita da MSSQL, puoi usare un TableValueParameter con Dapper abbastanza facilmente.

  1. Crea il tuo tipo TVP in MSSQL:

    CREATE TYPE [dbo].[MyTVP] AS TABLE([ProviderId] [int] NOT NULL)
  2. Crea un DataTablecon le stesse colonne del TVP e popolalo con i valori

    var tvpTable = new DataTable();
    tvpTable.Columns.Add(new DataColumn("ProviderId", typeof(int)));
    // fill the data table however you wish
  3. Modifica la tua query Dapper per eseguire un'operazione INNER JOINsulla tabella TVP:

    var query = @"SELECT * FROM Providers P
        INNER JOIN @tvp t ON p.ProviderId = t.ProviderId";
  4. Passa la DataTable nella tua chiamata di query Dapper

    sqlConn.Query(query, new {tvp = tvpTable.AsTableValuedParameter("dbo.MyTVP")});

Questo funziona anche in modo fantastico quando si desidera eseguire un aggiornamento di massa di più colonne: è sufficiente creare un TVP e farlo UPDATEcon un collegamento interno al TVP.


Ottima soluzione, tuttavia non funziona su .Net Core, vedi questa domanda: stackoverflow.com/questions/41132350/… . Vedi anche questa pagina: github.com/StackExchange/Dapper/issues/603
pcdev

3
Si consiglia inoltre di considerare la possibilità ProviderIdin MyTVPessere PRIMARY KEY CLUSTERED, come questo appena risolto un problema di prestazioni per noi (i valori che stavamo passando non conteneva duplicati).
Richardissimo,

@Richardissimo Puoi mostrare un esempio di come farlo? Non riesco a ottenere la sintassi corretta.
Mike Cole,


14

Ecco forse il modo più veloce per interrogare un gran numero di righe con Dapper usando un elenco di ID. Ti prometto che questo è più veloce di quasi tutti gli altri modi a cui riesci a pensare (con la possibile eccezione dell'uso di un TVP come indicato in un'altra risposta e che non ho testato, ma sospetto che potrebbe essere più lento perché devi ancora popolare il TVP). È i pianeti più veloce di Dapper usando la INsintassi e gli universi più velocemente di Entity Framework riga per riga. Ed è anche i continenti più veloce del passaggio in un elenco di VALUESo UNION ALL SELECTelementi. Può essere facilmente esteso per usare una chiave multi-colonna, basta aggiungere le colonne extra alla DataTable, la tabella temporanea e le condizioni di join.

public IReadOnlyCollection<Item> GetItemsByItemIds(IEnumerable<int> items) {
   var itemList = new HashSet(items);
   if (itemList.Count == 0) { return Enumerable.Empty<Item>().ToList().AsReadOnly(); }

   var itemDataTable = new DataTable();
   itemDataTable.Columns.Add("ItemId", typeof(int));
   itemList.ForEach(itemid => itemDataTable.Rows.Add(itemid));

   using (SqlConnection conn = GetConnection()) // however you get a connection
   using (var transaction = conn.BeginTransaction()) {
      conn.Execute(
         "CREATE TABLE #Items (ItemId int NOT NULL PRIMARY KEY CLUSTERED);",
         transaction: transaction
      );

      new SqlBulkCopy(conn, SqlBulkCopyOptions.Default, transaction) {
         DestinationTableName = "#Items",
         BulkCopyTimeout = 3600 // ridiculously large
      }
         .WriteToServer(itemDataTable);
      var result = conn
         .Query<Item>(@"
            SELECT i.ItemId, i.ItemName
            FROM #Items x INNER JOIN dbo.Items i ON x.ItemId = i.ItemId
            DROP TABLE #Items;",
            transaction: transaction,
            commandTimeout: 3600
         )
         .ToList()
         .AsReadOnly();
      transaction.Rollback(); // Or commit if you like
      return result;
   }
}

Tieni presente che devi imparare qualcosa sugli inserti in serie. Esistono opzioni sull'attivazione dei trigger (il valore predefinito è no), il rispetto dei vincoli, il blocco della tabella, la possibilità di inserimenti simultanei e così via.


Sì, sono d'accordo con la tua idea generale di creare una tabella temporanea con ID e quindi unioni interne su quella tabella. Lo abbiamo fatto internamente e ha drasticamente migliorato le prestazioni della query. Non sono sicuro che utilizzerei la classe DataTable per qualsiasi cosa, ma la tua soluzione è totalmente valida. Questo è un modo molto più veloce.
Marko,

Il DataTableè richiesto per l'inserimento di massa. Come si inseriscono nella tabella temporanea 50.000 valori?
ErikE,

1
A pezzi di 1000 se ricordo correttamente il limite? Ad ogni modo non sapevo che si potesse bypassare il limite con DataTable quindi ho imparato qualcosa di nuovo oggi ...
Marko

1
Questa è una quantità ridicola di lavoro da svolgere quando è possibile utilizzare un parametro valore tabella. Dapper supporta in modo pulito il passaggio di una DataTable come TVP, che consente di rinunciare alla creazione e alla distruzione di una tabella temporanea e al popolamento di tale tabella temporanea tramite BulkCopy. Usiamo la soluzione basata su TVP di routine nei casi in cui il numero di parametri per la clausola IN sarebbe troppo.
Mr. T

3
Questo non è una quantità ridicola di lavoro, specialmente se uno lo astrae un po 'con una classe di aiuto o un metodo di estensione.
ErikE,

11

Assicurati inoltre di non avvolgere le parentesi attorno alla stringa della query in questo modo:

SELECT Name from [USER] WHERE [UserId] in (@ids)

Ho avuto questo causa un errore di sintassi SQL utilizzando Dapper 1.50.2, risolto rimuovendo le parentesi

SELECT Name from [USER] WHERE [UserId] in @ids

7

Non è necessario aggiungere ()la clausola WHERE come facciamo in un normale SQL. Perché Dapper lo fa automaticamente per noi. Ecco il syntax: -

const string SQL = "SELECT IntegerColumn, StringColumn FROM SomeTable WHERE IntegerColumn IN @listOfIntegers";

var conditions = new { listOfIntegers };

var results = connection.Query(SQL, conditions);

6

Esempio per Postgres:

string sql = "SELECT * FROM SomeTable WHERE id = ANY(@ids)"
var results = conn.Query(sql, new { ids = new[] { 1, 2, 3, 4, 5 }});

3

Nel mio caso ho usato questo:

var query = "select * from table where Id IN @Ids";
var result = conn.Query<MyEntity>(query, new { Ids = ids });

la mia variabile "ids" nella seconda riga è un IEnumerable di stringhe, anche loro possono essere numeri interi.


List<string>?
Kiquenet,

2

Nella mia esperienza, il modo più amichevole di gestirlo è avere una funzione che converte una stringa in una tabella di valori.

Ci sono molte funzioni splitter disponibili sul web, ne troverai facilmente una per qualunque sia il tuo sapore di SQL.

Puoi quindi fare ...

SELECT * FROM table WHERE id IN (SELECT id FROM split(@list_of_ids))

O

SELECT * FROM table INNER JOIN (SELECT id FROM split(@list_of_ids)) AS list ON list.id = table.id

(O simili)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.