2012-05-15 15 views
5

Używam Entity Framework z POCO First approach. Podążałem za wzorcem opisanym przez Steve Sandersona w jego książce "Pro ASP.NET MVC 3 Framework", używając kontenera DI i klasy DbContext do połączenia z serwerem SQL.Poprawa wydajności dzięki Entity Framework

Podstawowe tabele w serwerze SQL zawierają bardzo duże zestawy danych używane przez różne aplikacje. Z tego powodu musiałem utworzyć widoki dla podmiotów, których potrzebuję w mojej aplikacji:

class RemoteServerContext : DbContext 
{ 
    public DbSet<Customer> Customers { get; set; } 
    public DbSet<Order> Orders { get; set; } 
    public DbSet<Contact> Contacts { get; set; } 
    ... 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Entity<Customer>().ToTable("vw_Customers"); 
     modelBuilder.Entity<Order>().ToTable("vw_Orders"); 
     ... 
    } 
} 

i to wydaje się dobrze dla większości moich potrzeb.

Mam problem jest, że niektóre z tych poglądów mają wiele danych w nich tak, że gdy zgłoszę coś takiego:

var customers = _repository.Customers().Where(c => c.Location == location).Where(...); 

wydaje się być przywrócenie całego zestawu danych, które mogą poświęć trochę czasu, zanim zapytanie LINQ zmniejszy zestaw do tych, których potrzebuję. Wydaje się to bardzo nieefektywne, gdy kryteria mają zastosowanie tylko do kilku rekordów i otrzymuję cały zestaw danych z serwera SQL.

Próbowałem to obejść za pomocą procedur przechowywanych, takich jak

public IEnumerable<Customer> CustomersThatMatchACriteria(string criteria1, string criteria2, ...) //or an object passed in! 
{ 
    return Database.SqlQuery<Customer>("Exec pp_GetCustomersForCriteria @crit1 = {0}, @crit2 = {1}...", criteria1, criteria2,...); 
} 

podczas gdy jest to o wiele szybciej, problemem jest to, że nie zwraca DbSet i tak stracę wszystkie z łączność między moimi obiektami, np Nie mogę odwoływać się do żadnych powiązanych obiektów, takich jak zamówienia lub kontakty, nawet jeśli zawieram ich identyfikatory, ponieważ typem zwracanym jest kolekcja "Klienci", a nie DbSet z nich.

Czy ktoś ma lepszy sposób na uzyskanie serwera SQL do wykonywania zapytań, tak aby nie przekazywać mnóstwa nieużywanych danych?

Odpowiedz

4
var customers = _repository.Customers().Where(c => c.Location == location).Where(... 

Jeśli Customers() powraca IQueryable, samo to stwierdzenie nie będzie faktycznie być „przywracanie” w ogóle nic - wywołanie Where na IQueryable daje inny IQueryable, a to nie jest aż coś zrobić, że powoduje wykonanie kwerendy (takiej jak ToList lub FirstOrDefault), że wszystko zostanie faktycznie wykonane, a wyniki zwrócone.

Jeśli jednak ta metoda Customers zwraca kolekcję instancji obiektów, to tak, ponieważ pytasz o wszystkie obiekty, które je wszystkie.

Nigdy nie używałam ani pierwszego kodu, ani nawet wzoru repozytorium, więc nie wiem, co doradzić, poza jak najdłuższym pozostaniem w królestwie IQueryable i tylko raz wykonałem zapytanie zastosowałeś wszystkie odpowiednie filtry.

+0

+1. Aby uzyskać bardziej "rozszerzalne podejście", możesz napisać funkcję, która pobiera predykat, i zwraca '_repository.Customers(). Gdzie (predicate)' lub (jeśli nie jest już IQueryable) napisać osobną funkcję z 'context.CreateQuery ("Klienci"). Gdzie (predykat) ', z możliwością wywołania' .ToList() 'na końcu. Powinien stworzyć ładną, zoptymalizowaną ekspresję. –

+1

Witam. Byłeś na miejscu z twoją sugestią pozostania w królestwie IQueryable. Używałem IEnumerable, który nie przekazuje kwerendy do serwera, ale pobiera wszystkie rekordy, a następnie filtruje je. Zobacz artykuł tutaj: http://www.fascinatedwithsoftware.com/blog/post/2011/06/27/IEnumerable-IQueryable-and-the-Entity-Framework-40.aspx Sprawdziłem z SQL Profiler i ma rację, IQueryable przekazuje parametry jako zapytanie – GrahamJRoy

0

Potrzebujesz kwerendy LINQ do zwrócenia mniej danych, takich jak stronicowanie sql jak top funkcji sql lub do ręcznego kwerendy za pomocą procedur przechowywanych. W obu przypadkach trzeba przepisać mechanizm zapytania. Jest to jeden z powodów, dla których nie korzystałem z EF, ponieważ nie masz dużej kontroli nad kodem, jaki się wydaje.

2

Co bym zrobił, aby powrócić tylko zbiór danych byłby następujący:

var customers = (from x in Repository.Customers where <boolean statement> &&/|| <boolean statement select new {variableName = x.Name , ...).Take(<integer amount for amount of records you need>); 

tak na przykład:

var customers = (from x in _repository.Customers where x.ID == id select new {variableName = x.Name}).take(1000); 

następnie iterację wyników, aby uzyskać dane: (pamiętaj, że oświadczenie LINQ zwraca IQueryable) ...

foreach (var data in customers) 
{ 
    string doSomething = data.variableName; //to get data from your query. 
} 

nadzieję, że to pomoże, nie dokładnie te same metody, ale uważam, że jest to przydatne w moim kodzie

1

Prawdopodobnie dzieje się tak, ponieważ twoja metoda Cusomters() w twoim repozytorium robi coś typu GetAll() i pobiera najpierw całą listę. Zabrania to tworzenia LINQ i SQL Server przy tworzeniu inteligentnych zapytań.

Nie wiem, czy jest to dobry obejście repozytorium, ale jeśli chcesz zrobić coś takiego:

using(var db = new RemoteServerContext()) 
{ 
    var custs = db.Customers.Where(...); 
} 

myślę, że będzie dużo szybciej. Jeśli twój projekt jest wystarczająco mały, możesz zrobić to bez repozytorium. Jasne, stracisz warstwę abstrakcji, ale w przypadku małych projektów może to nie być duży problem.

Z drugiej strony można załadować wszystkich klientów w repozytorium jeden raz i użyć wynikowej kolekcji bezpośrednio (zamiast wywołania metody, która wypełnia listę). Uważaj jednak na dodawanie, usuwanie i modyfikowanie klientów.