Oto kolejny (szalony) pomysł, który przyszedł mi do głowy. Należy pamiętać, że podobnie jak w przypadku mojej poprzedniej odpowiedzi, nie gwarantuje to lepszej wydajności (w rzeczywistości może być gorsza). Po prostu przedstawia sposób wykonania tego, o co prosisz, za pomocą pojedynczego zapytania SQL.
Tutaj utworzymy zapytanie, które zwraca pojedynczą wartość string
o długości N składającej się ze znaków "0" i "1" z "1" oznaczającym dopasowanie (coś w rodzaju tablicy bitów ciągów znaków). Kwerenda będzie używać mojego ulubionego grupę o stałej techniki zbudować dynamicznie coś takiego:
var matchInfo = queryable
.GroupBy(e => 1)
.Select(g =>
(g.Max(Condition[0] ? "1" : "0")) +
(g.Max(Condition[1] ? "1" : "0")) +
...
(g.Max(Condition[N-1] ? "1" : "0")))
.FirstOrDefault() ?? "";
A oto kod:
var group = Expression.Parameter(typeof(IGrouping<int, TEntity>), "g");
var concatArgs = providers.Select(provider => Expression.Call(
typeof(Enumerable), "Max", new[] { typeof(TEntity), typeof(string) },
group, Expression.Lambda(
Expression.Condition(
provider.Condition.Body, Expression.Constant("1"), Expression.Constant("0")),
provider.Condition.Parameters)));
var concatCall = Expression.Call(
typeof(string).GetMethod("Concat", new[] { typeof(string[]) }),
Expression.NewArrayInit(typeof(string), concatArgs));
var selector = Expression.Lambda<Func<IGrouping<int, TEntity>, string>>(concatCall, group);
var matchInfo = queryable
.GroupBy(e => 1)
.Select(selector)
.FirstOrDefault() ?? "";
var matchingProviders = matchInfo.Zip(providers,
(match, provider) => match == '1' ? provider : null)
.Where(provider => provider != null)
.ToList();
Enjoy :)
PS: Moim zdaniem, to zapytanie będzie działać ze stałą prędkością (w odniesieniu do liczby i rodzaju warunków, tj.można uznać za O (N) w najlepszych, najgorszych i przeciętnych przypadkach, gdzie N jest liczbą rekordów w tabeli), ponieważ baza danych musi zawsze wykonywać pełne skanowanie tabeli. Wciąż będzie interesujące wiedzieć, jaka jest rzeczywista wydajność, ale najprawdopodobniej zrobienie czegoś takiego nie jest warte wysiłku.
Aktualizacja: chodzi o dobroci i zaktualizowanego Wymagania:
znaleźć szybką kwerendę czyta tylko rekord tabeli raz i kończy się zapytanie, czy są już spełnione wszystkie warunki
Nie ma standardowej konstrukcji SQL (nawet nie mówi się o translacji kwerend LINQ), która spełnia oba warunki. Konstrukty, które pozwalają na wczesny koniec, takie jak EXISTS
, mogą być używane dla pojedynczego warunku, więc gdy wykonywane dla wielu warunków będą naruszać pierwszą regułę czytania rekordu tabeli tylko jeden raz. Podczas gdy konstrukcje, które używają agregatów jak w tej odpowiedzi, spełniają pierwszą regułę, ale aby wytworzyć zagregowaną wartość, muszą odczytać wszystkie rekordy, a zatem nie mogą wyjść wcześniej.
Wkrótce nie ma zapytania spełniającego oba wymagania. A co z częścią? szybka, to naprawdę zależy od wielkości danych oraz liczby i rodzaju warunków, indeksów tabel itd., Więc znowu nie ma po prostu "najlepszego" ogólnego rozwiązania dla wszystkich przypadków.
To zależy w dużym stopniu od tego, czym jest 'provider.Condition'. Jeśli można to przetłumaczyć przez EF na kwerendę sql, wówczas możesz zebrać wszystkie warunki i użyć ich w jednym zapytaniu - na odwrocie 'queryable.Any (provider.Condition)'. – Will
Wszystkie warunki, których chcę użyć, mogą zostać przetłumaczone na zapytania SQL przez EF –
** To zależy w dużym stopniu od tego, co 'provider.Condition' jest **. Jeśli można to przetłumaczyć przez EF na kwerendę sql, wówczas możesz zebrać wszystkie warunki i użyć ich w jednym zapytaniu - na odwrocie 'queryable.Any (provider.Condition)'. – Will