Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Umgang mit großen JSON-Daten, die von der Web-API zurückgegeben werden

Ihr Problem besteht darin, dass Sie eine Oracle-Abfrage ausführen, die eine sehr große Anzahl von Ergebnissen zurückgibt, und dann das gesamte Resultset in den Speicher laden, bevor Sie es an HttpResponseMessage serialisieren .

Um Ihren Speicherverbrauch zu reduzieren, sollten Sie alle Fälle finden und eliminieren, in denen der gesamte Satz von Ergebnissen aus der Abfrage in eine temporäre Zwischendarstellung geladen wird (z. B. eine DataTable oder JSON-Zeichenfolge), und streamen Sie die Daten stattdessen mit einem DataReader . Dadurch wird vermieden, alles auf einmal in den Speicher zu ziehen, gemäß diese antwort .

Erstens, aus Ihrem Traceback geht hervor, dass Sie haben Browserlink aktivieren geprüft. Da dieser anscheinend versucht, die gesamte Antwort in einem MemoryStream zwischenzuspeichern , sollten Sie es wie in FilePathResult erklärt deaktivieren hat eine OutOfMemoryException mit einer großen Datei ausgelöst .

Als Nächstes können Sie den Inhalt eines IDataReader direkt zu JSON mit Json.NET mit folgender Klasse und folgendem Konverter:

[JsonConverter(typeof(OracleDataTableJsonResponseConverter))]
public sealed class OracleDataTableJsonResponse
{
    public string ConnectionString { get; private set; }
    public string QueryString { get; private set; }
    public OracleParameter[] Parameters { get; private set; }

    public OracleDataTableJsonResponse(string connStr, string strQuery, OracleParameter[] prms)
    {
        this.ConnectionString = connStr;
        this.QueryString = strQuery;
        this.Parameters = prms;
    }
}

class OracleDataTableJsonResponseConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(OracleDataTableJsonResponse);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException("OracleDataTableJsonResponse is only for writing JSON.  To read, deserialize into a DataTable");
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var response = (OracleDataTableJsonResponse)value;

        using (var dbconn = new OracleConnection(response.ConnectionString))
        {
            dbconn.Open();
            using (var selectCommand = new OracleCommand(response.QueryString, dbconn))
            {
                if (response.Parameters != null)
                    selectCommand.Parameters.AddRange(response.Parameters);
                using (var reader = selectCommand.ExecuteReader())
                {
                    writer.WriteDataTable(reader, serializer);
                }
            }
        }
    }
}

public static class JsonExtensions
{
    public static void WriteDataTable(this JsonWriter writer, IDataReader reader, JsonSerializer serializer)
    {
        if (writer == null || reader == null || serializer == null)
            throw new ArgumentNullException();
        writer.WriteStartArray();
        while (reader.Read())
        {
            writer.WriteStartObject();
            for (int i = 0; i < reader.FieldCount; i++)
            {
                writer.WritePropertyName(reader.GetName(i));
                serializer.Serialize(writer, reader[i]);
            }
            writer.WriteEndObject();
        }
        writer.WriteEndArray();
    }
}

Ändern Sie dann Ihren Code so, dass er in etwa so aussieht:

    public HttpResponseMessage Getdetails([FromUri] string[] id)
    {
        var prms = new List<OracleParameter>();
        var connStr = ConfigurationManager.ConnectionStrings["PDataConnection"].ConnectionString;
        var inconditions = id.Distinct().ToArray();
        var strQuery = @"SELECT 
                       STCD_PRIO_CATEGORY_DESCR.DESCR AS CATEGORY, 
                       STCD_PRIO_CATEGORY_DESCR.SESSION_NUM AS SESSION_NUMBER, 
                       Trunc(STCD_PRIO_CATEGORY_DESCR.START_DATE) AS SESSION_START_DATE, 
                       STCD_PRIO_CATEGORY_DESCR.START_DATE AS SESSION_START_TIME , 
                       Trunc(STCD_PRIO_CATEGORY_DESCR.END_DATE) AS SESSION_END_DATE, 
                         FROM 
                         STCD_PRIO_CATEGORY_DESCR, 
                         WHERE 
                        STCD_PRIO_CATEGORY_DESCR.STD_REF IN(";
        var sb = new StringBuilder(strQuery);
        for (int x = 0; x < inconditions.Length; x++)
        {
            sb.Append(":p" + x + ",");
            var p = new OracleParameter(":p" + x, OracleDbType.NVarchar2);
            p.Value = inconditions[x];
            prms.Add(p);
        }
        if (sb.Length > 0)// Should this be inconditions.Length > 0  ?
            sb.Length--;
        strQuery = sb.Append(")").ToString();

        var returnObject = new { data = new OracleDataTableJsonResponse(connStr, strQuery, prms.ToArray()) };
        var response = Request.CreateResponse(HttpStatusCode.OK, returnObject, MediaTypeHeaderValue.Parse("application/json"));
        ContentDispositionHeaderValue contentDisposition = null;
        if (ContentDispositionHeaderValue.TryParse("inline; filename=ProvantisStudyData.json", out contentDisposition))
        {
            response.Content.Headers.ContentDisposition = contentDisposition;
        }
        return response;
    }

Dadurch wird das speicherinterne DataSet vermieden Darstellung der Ergebnisse.

Übrigens rechne ich die Linie

        if (sb.Length > 0)
            sb.Length--;

stattdessen sollte sein:

        if (inconditions.Length > 0)
            sb.Length--;

Ich glaube, Sie versuchen, das abschließende Komma in der Abfrage abzuziehen, das nur dann vorhanden ist, wenn inconditions.Length > 0

Bitte beachten Sie, dass ich kein Oracle-Entwickler bin und Oracle nicht installiert habe. Zum Testen habe ich den OracleClient nachgebaut Klassen, die eine zugrunde liegende OleDbConnection verwenden und es hat gut funktioniert.