7 Stimmen

C# Entity Framework + Linq - wie kann man eine langsame Abfrage beschleunigen?

Dies ist eine Frage im Zusammenhang mit C# MVC2 Jqgrid - was ist der richtige Weg zu tun, Server-Seite paging? wo ich nachfragte und herausfand, wie man die Leistung einer Abfrage für eine Tabelle mit etwa 2000 Zeilen verbessern kann. Die Leistung wurde von 10 Sekunden auf 1 Sekunde verbessert.

Jetzt versuche ich, genau dieselbe Abfrage durchzuführen, wobei die Tabelle 20.000 Zeilen hat - und die Abfrage dauert 30 Sekunden. Wie kann ich sie weiter verbessern? 20.000 Zeilen sind immer noch keine große Zahl.

Einige mögliche Ideen, die ich habe, sind:

  • Sie kann durch De-Normalisierung verbessert werden, um Verbindungen und Summen zu entfernen.
  • Erstellen Sie eine Ansicht und fragen Sie diese ab, anstatt die Tabelle abzufragen und zu verbinden
  • Nicht die gesamte Tabelle abfragen, sondern den Benutzer zuerst einen Filter auswählen lassen (z. B. einen Filter A | B | C usw.)
  • Weitere Indizes zu den Tabellen hinzufügen
  • Etwas anderes?

Dies ist die MVC-Aktion, die 30 Sekunden für 20 Tausend Zeilen benötigt: (die Parameter werden von jqgrid geliefert, wobei sidx = welche Sortierspalte und sord = die Sortierreihenfolge)

public ActionResult GetProductList(int page, int rows, string sidx, string sord, 
string searchOper, string searchField, string searchString)
{
    if (sidx == "Id") { sidx = "ProductCode"; }
    var pagedData = _productService.GetPaged(sidx, sord, page, rows);
    var model = (from p in pagedData.Page<Product>()
            select new
            {
                p.Id, p.ProductCode, p.ProductDescription,
                Barcode = p.Barcode ?? string.Empty, 
                UnitOfMeasure = p.UnitOfMeasure != null ? p.UnitOfMeasure.Name : "",
                p.PackSize, 
                AllocatedQty = p.WarehouseProducts.Sum(wp => wp.AllocatedQuantity),
                QtyOnHand = p.WarehouseProducts.Sum(wp => wp.OnHandQuantity)
            });

    var jsonData = new
    {
        Total = pagedData.TotalPages, Page = pagedData.PageNumber,
        Records = pagedData.RecordCount, Rows = model
    };

    return Json(jsonData, JsonRequestBehavior.AllowGet);
}

ProductService.GetPaged() ruft ProductRepository.GetPaged auf, das wiederum genericRepository.GetPaged() aufruft, das dies tut:

public ListPage GetPaged(string sidx, string sord, int page, int rows)
{
    var list = GetQuery().OrderBy(sidx + " " + sord);
    int totalRecords = list.Count();

    var listPage = new ListPage
    {
        TotalPages = (totalRecords + rows - 1) / rows,
        PageNumber = page,
        RecordCount = totalRecords,
    };

    listPage.SetPageData(list
        .Skip((page > 0 ? page - 1 : 0) * rows)
        .Take(rows).AsQueryable());

    return listPage;
}

Die .OrderBy()-Klausel verwendet LinqExtensions, so dass ich in eine Zeichenfolge anstelle eines Prädikats übergeben kann - könnte dies verlangsamen es?

Und schließlich ist ListPage nur eine Klasse, um die Eigenschaften, die jqgrid für das Paging benötigt, bequem zu verpacken:

public class ListPage
{
    private IQueryable _data;
    public int TotalPages { get; set; }
    public int PageNumber { get; set; }
    public int RecordCount { get; set; }

    public void SetPageData<T>(IQueryable<T> data) 
    {
        _data = data;
    }

    public IQueryable<T> Page<T>()
    {
        return (IQueryable<T>)_data;
    }
}

GetQuery ist:

public IQueryable<T> GetQuery()
{
    return ObjectSet.AsQueryable();
}

Die benutzerdefinierte Methode .OrderBy besteht aus diesen beiden Methoden:

public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, 
    string ordering, params object[] values)
{
    return (IQueryable<T>)OrderBy((IQueryable)source, ordering, values);
}

public static IQueryable OrderBy(this IQueryable source, string ordering, 
    params object[] values)
{
    if (source == null) throw new ArgumentNullException("source");
    if (ordering == null) throw new ArgumentNullException("ordering");
    ParameterExpression[] parameters = new ParameterExpression[] {
        Expression.Parameter(source.ElementType, "") };
    ExpressionParser parser = new ExpressionParser(parameters, ordering, values);
    IEnumerable<DynamicOrdering> orderings = parser.ParseOrdering();
    Expression queryExpr = source.Expression;
    string methodAsc = "OrderBy";
    string methodDesc = "OrderByDescending";
    foreach (DynamicOrdering o in orderings)
    {
        queryExpr = Expression.Call(
            typeof(Queryable), o.Ascending ? methodAsc : methodDesc,
            new Type[] { source.ElementType, o.Selector.Type },
            queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters)));
        methodAsc = "ThenBy";
        methodDesc = "ThenByDescending";
    }
    return source.Provider.CreateQuery(queryExpr);
}

6voto

Marc Gravell Punkte 970173

Der Teil, der mich beunruhigt, ist:

.Take(rows).AsQueryable()

die Tatsache, dass Sie AsQueryable() lässt mich vermuten, dass es sich derzeit IEnumerable<T> Das bedeutet, dass Sie die Seitenumschaltung am falschen Ende der Abfrage vornehmen könnten (indem Sie die chemin zu viele Daten über das Netz). Ohne GetQuery() und der Brauch OrderBy() ist es schwer, sicher zu sein - aber wie immer sollte man als erstes ein Profil der Abfrage erstellen. Sehen Sie, welche Abfrage ausgeführt wird und welche Daten zurückgegeben werden. EFProf könnte dies vereinfachen, aber ein SQL-Trace ist wahrscheinlich ausreichend.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X