2015-10-15 10 views
12

Entity Framework 5+ ma na celu wstępne skompilowanie wszystkich zapytań. Jednak dla zapytań takich jakEntity Framework: Precompiled Query dla Enumerable.Contains

List<Guid> ids; 
var entities = context.MyEntities.Where(x => ids.Contains(x.Id)).ToArray(); 

Entity Framework nie może precompile zapytania, w zależności od stopnia skomplikowania całego zapytania, parsowanie drzewa wyraz SQL mogą spożywać kilka sekund. Czy ktoś znalazł obejście, aby mimo to uzyskać prekompilowane zapytanie? Naprawdę nie rozumiem, dlaczego byłoby to takie trudne; Oczywiście trudno jest zrobić z paramters, ponieważ liczba elementów może się różnić, ale to byłoby na tyle dobrze, że SQL jak

SELECT a, b, c from MyEntities 
WHERE c in __PLACEHOLDER__ 

a następnie zastąpić zastępczy z rzeczywistych elementów listy. Oczywiście nie jest tak przyjemny, jak przekazywanie parametrów, ale byłby o wiele lepszy niż czekanie na sekundy do analizowania całego drzewa wyrażeń w kółko.

+0

Listy nie mogą być używane jako parametry, więc robi to, co sugerujesz, to jest, że konstruuje nowe zapytanie, ponieważ twoja lista może zawierać różne elementy za każdym razem, gdy go wywołasz. W rzeczywistości jest to ograniczenie SQL. –

+0

Niezupełnie; jak już wspomniano, za każdym razem rozpoczyna parsowanie drzewa ekspresji od zera.mamy kwerendę z kilkoma złączeniami, która trwa 5 sekund do przeanalizowania (z kilkoma ms na serwerze sql), dlatego szukam obejścia. – Roland

+1

@rolandJak duża jest ta lista? Co więcej, czy będziesz już używać? Problem może zależeć od tego, jak długo każdy wpis jest ....... ile on musi porównać. Miałem podobny problem z .StartsWith, dopóki nie użyłem StringComparison.Ordinal tam, który przyspieszył znacznie (w porównaniu do .Contains). Problem może polegać tylko na tym, że musi on iterować przez zbyt duże struny (na dużą skalę wymaga to czasu). Jeśli możesz go zmienić na startwith i porządkowy, powinien on znacznie się rozluźnić (ale zależy od twojego dokładnego przypadku użycia). – Thomas

Odpowiedz

6

Musisz najpierw zrozumieć, w jaki sposób działa operator "IN" w sparametryzowanym zapytaniu SQL.

nie działa, parametr polecenia SQL nie akceptuje tablicę jako wartości parametru, zamiast kwerenda jest tłumaczona na

SELECT A FROM B WHERE C IN (@p1, @p2, @p3 ... etc) 

To zapytanie jest zmienna liczba parametrów i tego powodu istnieje nie ma możliwości wstępnego skompilowania tego zapytania z IEnumerable.Contains.

Jedyną alternatywą (długa droga) jest użycie Xml lub Json (pojawia się w Sql 2016).

Zapisz swój IEnumerable jako xml.

[10,20,20,50] can be translated to 
<data> 
    <int value="10"/> 
    <int value="20"/> 
    <int value="20"/> 
    <int value="50"/> 
</data> 

a następnie można zdefiniować VIEW z parametrami jak

wybrać z B gdzie C IN (SELECT INT z XML (@ P1))

i można korzystać z tym poglądem, jednak W EF istnieje więcej wyzwań związanych z tym, jak wystrzelić to zapytanie, ale zapytanie to może zostać wstępnie skompilowane, ponieważ ma tylko jeden parametr.

klienta SQL do wykonania Hack

Dla bardzo prostej kwerendy jak,

List<Guid> ids; 
var entities = context.MyEntities.Where(x => ids.Contains(x.Id)).ToArray(); 

mogę po prostu użyć niestandardowego SQL i ogień,

var parameterList = ids.Select( 
    (x,i)=> new SqlCommandParameter(
     "@p"+i, x)); 

var pnames = String.Join(",", parameterList.Select(x=> x.ParameterName)); 

var entities = 
    context.SqlQuery<MyEntity>(
     "SELECT * FROM TABLE WHERE Id in (" + pnames + ")", 
     parameterList.ToArray()); 

tabeli tymczasowej

Można również użyć tabeli tymczasowej, ale zwiększa to liczbę aktywnych transakcji w bazie danych.

+0

Dzięki za tę odpowiedź; to niestety nadal nie jest tym, czego szukam. Jak wspomniano powyżej, mamy wiele takich zapytań, a pisanie wielu niestandardowych SQL/widoków nie jest tak naprawdę rozwiązaniem, ponieważ wtedy zepsuć będziemy łatwość obsługi aplikacji. Zastanawiam się, czy istnieje jakiś sposób dodania tymczasowego typu do EF. Pomysł: Możemy dodać tabelę tymczasową do przechowywania listy parametrów Contains, a następnie wprowadzić tymczasowy typ do sformułowania zapytania. Czy ktoś wie, czy EF pozwala wprowadzać tymczasowe typy? – Roland

+0

Tak, zamiast XML lub JSON, można również użyć tabeli tymczasowej. ale doda to obciążenie do bazy danych, zwiększy liczbę aktywnych transakcji, co doprowadzi do niskiej wydajności silnika bazy danych. –

+0

Tak, ale czy mogę dodać tymczasowy typ C# do EF? Potrzebuję go do sformułowania zapytania, takiego jak context.MyEntities.Where (x => context.Set (typeof (TemporaryType)) Zawiera (x.Id)). Nie miałbym nic przeciwko zrobieniu całej refleksji potrzebnej w tle. – Roland