/ / Използване на IN клауза с ExecuteQuery на LINQ към SQL - sql-сървър, linq, linq-to-sql

Използване на клауза IN с ExecuteQuery на LINQ-to-SQL - sql-сървър, linq, linq-to-sql

LINQ to SQL направи ужасна работа, превеждаща един от моите запитвания, така че го пренаписах на ръка. Проблемът е, че пренаписването задължително включва IN клауза и не мога за живота си да разбера как да мине колекция ExecuteQuery за тази цел. Единственото нещо, с което мога да измисля, което съм виждал тук, е да го използвам string.Format върху целия низ на заявката, за да го заобиколите - но това ще попречи на заявката да завърши в кеша за заявки.

Какъв е правилният начин да направите това?

ЗАБЕЛЕЖКА: Моля, имайте предвид, че Аз използвам суров SQL, преминал към ExecuteQuery, Казах го в първото изречение. Казвайки ми да използвам Contains не е полезно, освен ако не знаете начин за смесване Contains със суров SQL.

Отговори:

7 за отговор № 1

Параметри, оценени в таблицата

На Cheezburger.com често трябва да предаваме списък на AssetIDs или UserIDs в записана процедура или заявка за база данни.

Лошият начин: Динамичен SQL

Един от начините за предаване на този списък е да се използва динамичен 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);

Това е много лошо нещо, което трябва да направите:

  1. Динамичният SQL дава на атакуващите слабост, като улесняват внедряването на SQL инжекции.
    Тъй като ние обикновено просто свързваме числа заедно, това е много малко вероятно, но ако започнете да обединявате струни заедно, всичко, което е необходимо, е един потребител да напишете ";DROP TABLE Asset;SELECT " и сайтът ни е мъртъв.
  2. Запазените процедури не могат да имат динамичен SQL, така че заявката трябваше да бъде съхранявани в кода вместо в схемата на DB.
  3. Всеки път, когато изпълняваме тази заявка, планът на заявката трябва да бъде преизчислен. Това може да бъде много скъпо за сложни заявки.

Това обаче има предимството, че не е необходимо допълнително декодиране от страна на DB, тъй като AssetIDs се намират от анализатора на заявки.

Добрият начин: Параметри, оценени в таблицата

SQL Server 2008 добавя нова възможност: потребителите могат да определят тип таблица с база данни. Повечето други типове са скаларни (връщат се само една стойност), но табличните стойности могат да съдържат няколко стойности, стига стойностите да са таблични.

Ние определихме три типа: varchar_array, int_array, и bigint_array.

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

Както съхранените процедури, така и програмно дефинираните SQL заявки могат да използват тези типови таблици.

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

Предимства

  1. Може да се използва както при съхранени процедури, така и при програмни SQL без много усилия
  2. Не е уязвим за SQL инжекция
  3. Кешируеми, стабилни заявки
  4. Не блокира таблицата на схемата
  5. Не се ограничава до 8k данни
  6. По-малко работа, извършена както от DB сървъра, така и от приложенията Mine, тъй като няма конкатенация или декодиране на CSV низове.
  7. статистиката "типична употреба" може да бъде получена от анализатора на заявки, което може да доведе до още по-добри резултати.

Недостатъци

  1. Работи само на SQL Server 2008 и по-нови версии.
  2. Слухове, че TVP са предварително подбрани в тяхната цялост преди изпълнението на заявката, което означава, че феноменално големи TVPs могат да бъдат отхвърлени от сървъра. По-нататъшно разследване на този слух продължава.

Допълнителна информация

тази статия е страхотен ресурс, за да научите повече за TVP.


3 за отговор № 2

Ако не можете да използвате параметри от таблицата, товаопцията е малко по-бърза от опцията xml, като същевременно ви позволява да стоите настрана от динамичния sql: да предавате обединения списък със стойности като низ параметър и да анализирате разделения низ на стойностите в заявката си. Моля виж тази статия за инструкции за ефективно извършване на анализа.


2 за отговор № 3

Имам скрито подозрение, че сте на SQL Server 2005. Параметри, които са били оценени, не са били добавени до 2008 г., но все още можете да използвате XML тип данни да предава записи между клиента и сървъра.


0 за отговор № 4

Това работи за SQL Server 2005 (и по-късно):

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

Трябва да извикате тази процедура с параметър като този:

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

Функцията на възлите използва израз xPath. В този случай това е /params/p и това е начина, по който XML се използва <params> като корен, и <p> като елемент.

Функцията за стойността отразява текста във всяка от тях p елемент към int, но можете да го използвате с други типове данни лесно. В тази извадка има DISTINCT, за да се избегнат повтарящи се стойности, но, разбира се, можете да я премахнете в зависимост от това, което искате да постигнете.

Имам помощен (разширен) метод, който преобразува един IEnumerable<T> в низ, който изглежда като този, показан визпълнете пример. Това е лесно да се създаде един и да го направи работата за вас, когато имате нужда от него. (Трябва да тествате типа данни на Т и да се превърне в адекватни низ, които могат да бъдат анализирани на SQL Server страна). C # кодът е по-чист и вашите SPs следват същия модел, за да получат параметрите (можете да минете в толкова списъци, колкото е необходимо).

Едно предимство е, че не е нужно да правите нищо специално във вашата база данни, за да работи.

Разбира се, няма нужда да създавате временна таблица, както е направено в моя пример, но можете да използвате заявката директно като подзаявка вътре в IN предикат

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

0 за отговор № 5

Аз не съм 100% сигурен, че разбирам правилно проблема, но ExecuteQuery на LinqToSql има претоварване за параметрите и заявката трябва да използва формат, подобен на string.Format.

Използването на това претоварване е безопасно срещу SQL инжектиране, а зад сцената LinqToSql транселтите го използва за sp_executesql с параметри.

Ето един пример:

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

По този начин може да се използва ползата от параметризирани заявки, дори когато се използва динамичен sql.

Все пак, когато става дума за използване на IN с динамичен брой параметри, има две възможности:

  1. Конструирайте низа динамично и след това предавайте стойностите като масив, както в:

    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. Можем да използваме по-компактен подход, като използваме методите за разширение на Linq, както в

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