C# REST API: Umgang mit großer Liste

Konnte das Problem lösen, lag an einer Konvertierung von, die Dapper gemacht hat.
Die genau Problembeschreibung, falls das bei jemand anderem auch auftritt:
Ich habe ein Datum als Filter im Query, also ziehe mir nur Daten, die nach dem 10.04.2021 sind z.B.

In C# habe ich das dann wie folgt programmiert:
C#:
public async Task<List<Unit>> GetUnitsByPagination(DateTime startDate, int startingPoint, int rowAmount)
{
    var parameters = new DynamicParameters();
    parameters.Add("StartDate", startDate, DbType.Date, ParameterDirection.Input);
    parameters.Add("StartingPoint", startingPoint, DbType.Int32, ParameterDirection.Input);
    parameters.Add("RowAmount", rowAmount, DbType.Int32, ParameterDirection.Input);
    
    string sqlQuery = "SELECT DISTINCT " +
            "t.unit_id AS UnitId " +
        "FROM MYTABLE t " +
        "WHERE " +
            "t.created >= :StartDate " +
            "AND t.unit_id IS NOT NULL " +
            "AND t.unit_id_type = 'Unit' " +
            "AND t.equipment LIKE '000%' " +
        "ORDER BY t.created " +
        "OFFSET :StartingPoint ROWS " +
        "FETCH NEXT :RowAmount ROWS ONLY";
    
    var result = await _oracleRepository.LoadDataSqlAsync<Unit, dynamic>(sqlQuery, parameters, "ConString");
    
    return result;
}

Das Problem war, dass hier bei diesem Aufruf dann wohl ständig eine Konvertierung von System.DateTime nach DbType.DateTime nach DbType.Date gemacht wurde. Ich habe dann vor dem Absetzen des Queries das Mapping von Dapper für System.DateTime geändert, damit direkt nach DbType.Date gewandelt wird. Somit wird die Konvertierung nur einmalig und nicht bei jedem Eintrag gemacht.

Sieht dann also so aus:
C#:
public async Task<List<Unit>> GetUnitsByPagination(DateTime startDate, int startingPoint, int rowAmount)
{
    var parameters = new DynamicParameters();
    parameters.Add("StartDate", startDate, DbType.Date, ParameterDirection.Input);
    parameters.Add("StartingPoint", startingPoint, DbType.Int32, ParameterDirection.Input);
    parameters.Add("RowAmount", rowAmount, DbType.Int32, ParameterDirection.Input);
    
    string sqlQuery = "SELECT DISTINCT " +
            "t.unit_id AS UnitId " +
        "FROM MYTABLE t " +
        "WHERE " +
            "t.created >= :StartDate " +
            "AND t.unit_id IS NOT NULL " +
            "AND t.unit_id_type = 'Unit' " +
            "AND t.equipment LIKE '000%' " +
        "ORDER BY t.created " +
        "OFFSET :StartingPoint ROWS " +
        "FETCH NEXT :RowAmount ROWS ONLY";
    
    // Custom-Mapping for shorter query time
    SqlMapper.AddTypeMap(typeof(DateTime), DbType.Date);
    
    var result = await _oracleRepository.LoadDataSqlAsync<Unit, dynamic>(sqlQuery, parameters, "ConString");
    
    return result;
}

Am Query habe ich auch nochmal etwas optimiert, dauert jetzt ca. 1 Sekunde für 150 Einträge.
Das ursprüngliche Query, dass ich von den Datenbankleuten bekommen hatte, brauchte ca. 2 Minuten für 150 Einträge...
Wie ich unsere Datenbankspezialisten liebe
 
  • Gefällt mir
Reaktionen: Testa2014
Die Performance ist wirklich ziemlich madig. Ist MYTABLE eine View, die mehrere Dutzend finstere Joins und Full-Table-Scans macht? Muss das LIKE da sein? Das sieht so aus wie 'ne Kategorie ("Krempel, der mit 000 beginnt").
 
Ich werde den Eindruck nicht los, dass da wenigstens ein entscheidender Index fehlt. Bitte die Datenbankleute doch mal den Ausführungsplan deines / deiner Query zu erstellen und zu analysieren.

Jedenfalls ist das nicht schnell. Noch gerade so ausreichend schnell sind meine 120 Millisekunden für jeweils 50 Einträge, die ich aus einer Dokumentetabelle mit 3 Millionen Einträgen und ca. 20 Referenzen zu anderen Tabellen aus einen matschigen MS SQL Server lutsche, damit ich eine Auflistung zuletzt hinzugefügter Dateien einer oder aller Kategorien für
den Kontext bestimmter Anwender via lazy loading und endless scrolling laden kann, solange der User und dessen Mausrad Bock und berechtigte Einträge hat.

Zur Not, wenn man über Indizes nichts mehr erreichen kann, solltet ihr mal über den Einsatz von temporary in memory tables und / oder materialized views nachdenken. Weiß halt nicht, wie aktuell die Daten am Frontend sein müssen, ob man da mit Snapshots arbeiten könnte oder ob das OLTP oder gar Realtime sein muss und welche Oracle Version ihr da fahrt. Hab auch nicht mehr so den Plan von Oracle, die letzte in meinen Händen war 9i (und die war scheiße), das ist lange her, seitdem hat sich da sicherlich viel getan, was performance optimizing angeht.
 
Zuletzt bearbeitet:
Eine Sekunde ist extrem langsam für 150 Ids holen, das muss eigentlich ein Full Table Scan auf einer größeren Tabelle sein um die Performance so furchtbar hinzubekommen.

Da muss mindestens ein Index auf "created", aber der wird auch nur helfen wenn die anderen drei Filter nicht den Großteil der Ergebnisse rauswerfen. Und wieso kann die ID null werden, ist das nicht der primary key? Das LIKE Kriterium ist auch generell problematisch für Performance, wobei diese Variante theoretisch von einem Index beschleunigt werden kann (Details kenne ich bei Oracle aber nicht).

Und C# ist ziemlich schnell, wenn JSON bauen mehrere Minuten dauert läuft irgenwas komplett falsch.
 
Zuletzt bearbeitet:
Zurück
Oben