Oracle
 sql >> Base de données >  >> RDS >> Oracle

Traitement des données JSON volumineuses renvoyées par l'API Web

Votre problème est que vous exécutez une requête Oracle qui renvoie un très grand nombre de résultats, puis que vous chargez l'intégralité de cet ensemble de résultats en mémoire avant de le sérialiser dans le HttpResponseMessage .

Pour réduire votre utilisation de la mémoire, vous devez rechercher et éliminer tous les cas où l'ensemble des résultats de la requête est chargé dans une représentation intermédiaire temporaire (par exemple, un DataTable ou chaîne JSON), et diffusez plutôt les données à l'aide d'un DataReader . Cela évite de tout mettre en mémoire en même temps selon cette réponse .

Tout d'abord, d'après votre retraçage, il semble que vous ayez Activer le lien du navigateur vérifié. Puisque cela essaie apparemment de mettre en cache la réponse entière dans un MemoryStream , vous voudrez le désactiver comme expliqué dans FilePathResult a lancé une exception OutOfMemoryException avec un fichier volumineux .

Ensuite, vous pouvez diffuser le contenu d'un IDataReader directement à JSON en utilisant Json.NET avec la classe et le convertisseur suivants :

[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();
    }
}

Modifiez ensuite votre code pour qu'il ressemble à :

    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;
    }

Cela évite le DataSet en mémoire représentation des résultats.

Au fait, je compte la ligne

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

à la place devrait être :

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

Je crois que vous essayez de décoller la virgule finale dans la requête, qui sera présente si et seulement si inconditions.Length > 0

Veuillez noter - je ne suis pas un développeur Oracle et je n'ai pas installé Oracle. Pour les tests, j'ai simulé le OracleClient classes utilisant un OleDbConnection sous-jacent et ça a bien fonctionné.