/ / Używanie klauzuli IN z ExecuteQuery LINQ-SQL-sql-server, linq, linq-sql

Używanie klauzuli IN z ExecuteQuery LINQ-SQL-sql-server, linq, linq-sql

LINQ do SQL wykonał okropną pracę tłumacząc jedno z moich zapytań, więc przepisałem je ręcznie. Problem polega na tym, że przepisanie musi koniecznie obejmować IN i nie mogę dla mojego życia wymyślić, jak przekazać kolekcję ExecuteQuery w tym celu. Mogę wymyślić jedyną rzecz, którą widziałem tu sugerowaną string.Format na całym łańcuchu zapytania, aby go zablokować - ale to uniemożliwi wykonanie zapytania w pamięci podręcznej zapytań.

Jak to zrobić?

UWAGA: Proszę to zanotować Używam nieprzetworzonego SQL przekazanego do ExecuteQuery. Powiedziałem to w pierwszym zdaniu. Mówienie mi do użycia Contains nie jest pomocna, chyba że znasz sposób na połączenie Contains z surowym SQL.

Odpowiedzi:

7 dla odpowiedzi № 1

Wartości podane w tabeli

W witrynie Cheezburger.com często trzeba przekazać listę identyfikatorów składników lub identyfikatorów użytkowników do procedury składowanej lub kwerendy bazy danych.

Zły sposób: Dynamiczny SQL

Jednym ze sposobów przekazania tej listy było użycie dynamicznego SQL.

 IEnumerable<long> assetIDs = GetAssetIDs();
var myQuery = "SELECT Name FROM Asset WHERE AssetID IN (" + assetIDs.Join(",") + ")";
return Config.GetDatabase().ExecEnumerableSql(dr=>dr.GetString("Name"), myQuery);

Jest to bardzo złe:

  1. Dynamiczny SQL daje atakującym słabość, ułatwiając ataki SQL injection.
    Ponieważ zazwyczaj łączymy liczby, jest to mało prawdopodobne, ale jeśli zaczniesz łączyć łańcuchy razem, wystarczy jeden użytkownik do wpisania ";DROP TABLE Asset;SELECT " a nasza strona jest martwa.
  2. Procedury przechowywane nie mogą mieć dynamicznego SQL, więc zapytanie musiał być przechowywane w kodzie zamiast w schemacie DB.
  3. Za każdym razem, gdy uruchamiamy to zapytanie, plan zapytania musi zostać ponownie obliczony. Może to być bardzo kosztowne w przypadku skomplikowanych zapytań.

Ma jednak tę zaletę, że nie jest konieczne żadne dodatkowe dekodowanie po stronie DB, ponieważ AssetID są znalezione przez analizator składni zapytań.

Dobra droga: wartościowane w tabeli parametry

SQL Server 2008 dodaje nową umiejętność: użytkownicy mogą definiować typ bazy danych o wartości tabelarycznej. Większość innych typów jest skalarna (zwracają tylko jedną wartość), ale typy o wartościach tabelarycznych mogą zawierać wiele wartości, o ile wartości są tabelaryczne.

Określiliśmy trzy typy: varchar_array, int_array, i bigint_array.

CREATE TYPE bigint_array AS TABLE (Id bigint NOT NULL PRIMARY KEY)

Zarówno procedury składowane, jak i programowo zdefiniowane zapytania SQL mogą korzystać z tych typów o wartościach tabelarycznych.

  IEnumerable<long> assetIDs = GetAssetIDs();
return Config.GetDatabase().ExecEnumerableSql(dr=>dr.GetString("Name"),
"SELECT Name FROM Asset WHERE AssetID IN (SELECT Id FROM @AssetIDs)",
new Parameter("@AssetIDs", assetIDs));

Zalety

  1. Może być stosowany zarówno w procedurach przechowywanych, jak iw programowym języku SQL bez większego wysiłku
  2. Nie podatny na iniekcję SQL
  3. Buforowalne, stabilne zapytania
  4. Nie blokuje tabeli schematu
  5. Nie ogranicza się do 8 000 danych
  6. Mniej pracy wykonanej zarówno przez serwer bazy danych, jak i aplikacje kopalni, ponieważ nie ma konkatenacji ani dekodowania ciągów CSV.
  7. Statystyka "typowego zastosowania" może pochodzić od analizatora zapytań, co może prowadzić do jeszcze lepszej wydajności.

Niedogodności

  1. Działa tylko na SQL Server 2008 i wyżej.
  2. Plotki, że TVP są prebuffowane w całości przed wykonaniem zapytania, co oznacza, że ​​fenomenalnie duże TVP mogą zostać odrzucone przez serwer. Trwają dalsze badania tej plotki.

Dalsze czytanie

Ten artykuł jest świetnym źródłem informacji o TVP.


3 dla odpowiedzi № 2

Jeśli nie możesz użyć parametrów o wartości tabelarycznej, toOpcja jest trochę szybsza niż opcja xml, a jednocześnie pozwala trzymać się z daleka od dynamicznego sql: przekazuje połączoną listę wartości jako parametr łańcuchowy i analizuje rozdzielony ciąg z powrotem do wartości w zapytaniu. proszę zobaczyć Ten artykuł aby uzyskać instrukcje, jak wydajnie wykonać parsowanie.


2 dla odpowiedzi nr 3

Mam podejrzenie, że jesteś na serwerze SQL Server 2005. Parametry o wartości tabelarycznej nie zostały dodane do 2008 roku, ale nadal możesz używać Typ danych XML przekazywać zestawy między klientem a serwerem.


0 dla odpowiedzi nr 4

Działa to w przypadku SQL Server 2005 (i późniejszych):

create procedure IGetAListOfValues
@Ids xml -- This will recevie a List of values
as
begin
-- You can load then in a temp table or use it as a subquery:
create table #Ids (Id int);
INSERT INTO #Ids
SELECT DISTINCT params.p.value(".","int")
FROM @Ids.nodes("/params/p") as params(p);
...
end

Musisz wywołać tę procedurę za pomocą takiego parametru:

exec IGetAListOfValues
@Ids = "<params> <p>1</p> <p>2</p> </params>" -- xml parameter

Funkcja węzłów używa wyrażenia xPath. W tym przypadku jest /params/p i tak właśnie używa XML <params> jako root, i <p> jako element.

Funkcja wartości rzuca tekst w każdym z nich p element do int, ale możesz go łatwo używać z innymi typami danych. W tym przykładzie istnieje DISTINCT, aby uniknąć powtarzających się wartości, ale oczywiście możesz go usunąć w zależności od tego, co chcesz osiągnąć.

Mam metodę pomocniczą (rozszerzenie), która konwertuje IEnumerable<T> w łańcuchu, który wygląda jak ten pokazany wwykonaj przykład. Łatwo jest go utworzyć i zlecić mu pracę w dowolnym momencie (musisz przetestować typ danych T i przekonwertować go na odpowiedni ciąg, który można przeanalizować po stronie SQL Server). Kod C # jest czystszy, a twoje SP mają ten sam wzorzec, aby otrzymać parametry (w razie potrzeby można przekazać tyle list).

Jedną z korzyści jest to, że nie musisz robić nic specjalnego w bazie danych, aby działał.

Oczywiście nie musisz tworzyć tabeli tymczasowej, jak to jest zrobione w moim przykładzie, ale możesz użyć zapytania bezpośrednio jako podzapytanie wewnątrz IN orzec

    WHERE MyTableId IN (SELECT DISTINCT params.p.value(".","int")
FROM @Ids.nodes("/params/p") as params(p) )

0 dla odpowiedzi № 5

Nie jestem w 100% pewny, że rozumiem poprawnie problem, ale ExecuteQuery LinqToSql ma przeciążenie dla parametrów, a zapytanie powinno używać formatu podobnego do string.Format.

Korzystanie z tego przeciążenia jest bezpieczne przed iniekcją SQL, a za kulisami LinqToSql transaletyzuje użycie sp_executesql z parametrami.

Oto przykład:

string sql = "SELECT * FROM city WHERE city LIKE {0}";
db.ExecuteQuery(sql, "Lon%"); //Note that we don"t need the single quotes

W ten sposób można wykorzystać zalety sparametryzowanych zapytań, nawet przy użyciu dynamicznego sql.

Jednak jeśli chodzi o używanie IN z dynamiczną liczbą parametrów, istnieją dwie opcje:

  1. Zbuduj ciąg dynamicznie, a następnie przekazuj wartości jako tablicę, jak w:

    string sql = "SELECT * FROM city WHERE zip IN (";
    List<string> placeholders = new List<string>();
    for(int i = 0; i < zips.Length;i++)
    {
    placeholders.Add("{"+i.ToString()+"}");
    }
    sql += string.Join(",",placeholders.ToArray());
    sql += ")";
    db.ExecuteQuery(sql, zips.ToArray());
    
  2. Możemy użyć bardziej kompaktowego podejścia, używając metod rozszerzenia Linq, jak w

    string sql = "SELECT * FROM city WHERE zip IN ("+
    string.Join("," , zips.Select(z => "{" + zips.IndexOf(f).ToString() + "}"))
    +")";
    db.ExecuteQuery(sql, zips.ToArray());