Próbuję określić, jak policzyć pasujące wiersze w tabeli przy użyciu EntityFramework.
Problem polega na tym, że każdy wiersz może zawierać wiele megabajtów danych (w polu binarnym). Oczywiście SQL wyglądałby mniej więcej tak:
SELECT COUNT(*) FROM [MyTable] WHERE [fkID] = '1';
Mogłem załadować wszystkie wiersze, a następnie znaleźć Count z:
var owner = context.MyContainer.Where(t => t.ID == '1');
owner.MyTable.Load();
var count = owner.MyTable.Count();
Ale to jest rażąco nieefektywne. Czy jest prostszy sposób?
EDYCJA: Dziękuję wszystkim. Przeniosłem bazę danych z dołączonego prywatnego, więc mogę uruchomić profilowanie; to pomaga, ale powoduje zamieszanie, którego się nie spodziewałem.
I moje prawdziwe dane jest nieco głębiej, użyję Trucks przewożących Palety o sprawach z pozycji - i nie chcą Truck opuścić chyba że istnieje co najmniej jedna pozycja w nim.
Moje próby są pokazane poniżej. Część, której nie rozumiem, to to, że CASE_2 nigdy nie uzyskuje dostępu do serwera DB (MSSQL).
var truck = context.Truck.FirstOrDefault(t => (t.ID == truckID));
if (truck == null)
return "Invalid Truck ID: " + truckID;
var dlist = from t in ve.Truck
where t.ID == truckID
select t.Driver;
if (dlist.Count() == 0)
return "No Driver for this Truck";
var plist = from t in ve.Truck where t.ID == truckID
from r in t.Pallet select r;
if (plist.Count() == 0)
return "No Pallets are in this Truck";
#if CASE_1
/// This works fine (using 'plist'):
var list1 = from r in plist
from c in r.Case
from i in c.Item
select i;
if (list1.Count() == 0)
return "No Items are in the Truck";
#endif
#if CASE_2
/// This never executes any SQL on the server.
var list2 = from r in truck.Pallet
from c in r.Case
from i in c.Item
select i;
bool ok = (list.Count() > 0);
if (!ok)
return "No Items are in the Truck";
#endif
#if CASE_3
/// Forced loading also works, as stated in the OP...
bool ok = false;
foreach (var pallet in truck.Pallet) {
pallet.Case.Load();
foreach (var kase in pallet.Case) {
kase.Item.Load();
var item = kase.Item.FirstOrDefault();
if (item != null) {
ok = true;
break;
}
}
if (ok) break;
}
if (!ok)
return "No Items are in the Truck";
#endif
SQL wynikający z CASE_1 jest przesyłany potokiem przez sp_executesql , ale:
SELECT [Project1].[C1] AS [C1]
FROM ( SELECT cast(1 as bit) AS X ) AS [SingleRowTable1]
LEFT OUTER JOIN (SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(cast(1 as bit)) AS [A1]
FROM [dbo].[PalletTruckMap] AS [Extent1]
INNER JOIN [dbo].[PalletCaseMap] AS [Extent2] ON [Extent1].[PalletID] = [Extent2].[PalletID]
INNER JOIN [dbo].[Item] AS [Extent3] ON [Extent2].[CaseID] = [Extent3].[CaseID]
WHERE [Extent1].[TruckID] = '....'
) AS [GroupBy1] ) AS [Project1] ON 1 = 1
[ Naprawdę nie mam ciężarówek, kierowców, palet, skrzyń ani przedmiotów; jak widać z SQL, relacje Ciężarówka-Paleta i Paleta-Skrzynia to wiele do wielu - chociaż nie sądzę, żeby to miało znaczenie. Moje prawdziwe przedmioty są niematerialne i trudniejsze do opisania, więc zmieniłem nazwy. ]
źródło
Odpowiedzi:
Składnia zapytania:
Składnia metody:
Obie generują to samo zapytanie SQL.
źródło
SelectMany()
? Czy to potrzebne? Czy bez niego nie działałoby to dobrze?MyContainer.Where(o => o.ID == '1')
)Myślę, że chcesz czegoś takiego
(zredagowane w celu odzwierciedlenia komentarzy)
źródło
var count = context.MyTable.Count(t => t.MyContainer.ID == '1');
nie długie i brzydkie:var count = (from o in context.MyContainer where o.ID == '1' from t in o.MyTable select t).Count();
Ale to zależy od stylu kodowania ...Jak rozumiem, wybrana odpowiedź nadal ładuje wszystkie powiązane testy. Według tego bloga msdn jest lepszy sposób.
http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx
konkretnie
źródło
Find(1)
wniosków. Po prostu stwórz jednostkę i dołącz do kontekstu:var princess = new PrincessEntity{ Id = 1 }; context.Princesses.Attach(princess);
To jest mój kod:
Upewnij się, że zmienna jest zdefiniowana jako IQueryable, a następnie gdy użyjesz metody Count (), EF wykona coś podobnego
W przeciwnym razie, jeśli rekordy są zdefiniowane jako IEnumerable, wygenerowany plik sql będzie sprawdzał całą tabelę i policzy zwracane wiersze.
źródło
Cóż, nawet
SELECT COUNT(*) FROM Table
będzie dość nieefektywne, szczególnie w przypadku dużych tabel, ponieważ SQL Server naprawdę nie może zrobić nic poza pełnym skanowaniem tabeli (skanowanie indeksu klastrowego).Czasami wystarczy znać przybliżoną liczbę wierszy z bazy danych, a w takim przypadku wystarczy taka instrukcja:
Spowoduje to sprawdzenie dynamicznego widoku zarządzania i wyodrębnienie z niego liczby wierszy i rozmiaru tabeli dla określonej tabeli. Czyni to poprzez zsumowanie wpisów dla stosu (id_indeksu = 0) lub indeksu klastrowego (id_indeksu = 1).
Jest szybki, łatwy w użyciu, ale nie gwarantuje 100% dokładności ani aktualności. Jednak w wielu przypadkach jest to „wystarczająco dobre” (i znacznie mniej obciąża serwer).
Może to też zadziała w twoim przypadku? Oczywiście, aby użyć go w EF, musiałbyś zawrzeć to w przechowywanym procencie lub użyć prostego wywołania „Execute SQL query”.
Marc
źródło
Użyj metody ExecuteStoreQuery kontekstu jednostki. Pozwala to uniknąć pobierania całego zestawu wyników i deserializacji do obiektów w celu wykonania prostej liczby wierszy.
źródło
int count = context.MyTable.Count(m => m.MyContainerID == '1')
wygenerowany kod SQL będzie dokładnie przypominał to, co robisz, ale kod jest znacznie ładniejszy. Żadne jednostki nie są ładowane do pamięci jako takie. Jeśli chcesz, wypróbuj go w LINQPad - pokaże ci SQL używany pod okładkami.Myślę, że to powinno działać ...
źródło