Problem ograniczenie 2100 parametrów.

Przy problemie 2100 parametrów w LinqToSQL zrodziły się we mnie wątpliwości czy powiedzenie że każdy kod został już napisany i wystarczy go znaleźć jest prawdziwe.
Ale w czym rzecz. Komu by przyszło do głowy wpisywać przeszło dwa tysiące parametrów? Być może nie jest to wygodne rozwiązanie jeśli piszemy warunek
Name like 'a' Or Name like 'b'
Ale jeśli weźmiemy Contains (czyli po Sqlowemu IN) i np zestaw IDków lub GUIDów to już nie jest takie niedorzeczne.
private IQueryable GetList()
{
     ContactDataContext cdc = new ContactDataContext(GlobalVariables.ConnectionString);
     cdc.ObjectTrackingEnabled = false;

     long[] ids = new long[]{1,2,...,2200};            
     var result = from cl in cldc.ContactLists
                  where !cl.IsDeleted
                        && ids.Contains(cl.ID)
                  select cl;
          return result;
}
KaBum.
Rozwiązanie znalazłam tutaj - jedyne znalezione rozwiązanie.
Idea
No przecież idealnie było by mieć nasz zbiór idków w jakiejś tabeli i się do niej join'ąć.
Problem
Listę idków mamy po stronie aplikacji a przecież zapytanie wykona się po stronie bazy danych więc listę trzeba przetransportować do bazy.
Rozwiązanie

Banał - wysłać listę do bazy, ale skoro nie możemy jako parametry to jak?
Stwórzmy jeden parametr typu XML i zapiszmy w tabelce tymczasowej - dzięki temu będziemy mieć się do czego join'ować z aplikacji.
Great. Rozwiązanie działa.
Ale
Okazało się że operacja dominująca w tym rozwiązaniu czyli Insert działa bardzo wolno. Praktycznie przy 2,5 tys rowków był to czas rzędu 20 sekund co praktycznie wyklucza rozwiązanie.

Dzięki wsparciu kolegi o wiedzy z SQLa znacznie większej niż moja, okazało się że wystarczy pominąć tabelkę tymczasową.
Oto pełne rozwiązanie.
ALTER FUNCTION [dbo].[udf_ParseIDs]
(@ids xml)
RETURNS --@temp
--TABLE(Id bigint)
--BEGIN
--  INSERT INTO @temp(id) -- ten insert był zbyt kosztowny!

table as return(
  SELECT
   Id.value('.', 'bigint') as ID
  FROM
   @ids.nodes('/ArrayOfLong/long')  list(id)
  --RETURN
)
private IQueryable GetList()
{
     ContactDataContext cdc = new 
                             ContactDataContext(GlobalVariables.ConnectionString);
     cdc.ObjectTrackingEnabled = false;
       
     long[] ids = new long[]{1,2,...,2200};  
     System.Xml.Linq.XElement  serialized = GetIDsAsXml(ids);
     var result = from c in cdc.ContactLists
                  join ids in cdc.udf_ParseIDsMediaPlan(serialized)
                  on c.ID equals ids.ID
                  where !c.IsDeleted &&
                  select c;
     return result;
}

///Stworzenie elementu xml z listy longów
public static XElement SerializeGuidList(IList ids)
{
    using (var sw = new System.IO.StringWriter())
    {
        var guidArraySerializer = new XmlSerializer(typeof(long[]));
        guidArraySerializer.Serialize(sw, ids.ToArray());
        using (var sr = new System.IO.StringReader(sw.ToString()))
        {
            return XElement.Load(sr);
        }
    }
}

A to już efekt przechwycony profilerem (troszkę przycięty xml):
declare
@p3 xml 
set
@p3=convert(xml,N'135101213141517182225262829303233343536383940414445474850525457586061626364656673747778798083848586878889909192939495969798') 
exec
sp_executesql N'SELECT [t0].[ID], [t0].[CreatedBy], [t0].[CreateDate], [t0].[IsDeleted] 
FROM [dbo].[Contact] AS [t0]
INNER JOIN [dbo].[udf_ParseIDs](@p0) AS [t1] ON ([t0].[ID]) = [t1].[Id]
ORDER BY [t0].[ID]'
,N'@p0 xml',@p0=@p3
Ciekawy problem i ciekawe rozwiązanie (i szybkie).

Komentarze

Popularne posty