2016-08-05 2 views
4

Мы создаем веб-API, который принимает массив строк в качестве входного параметра, который запрашивает базу данных oracle и возвращает результат в виде файла JSON.Работа с большими данными JSON, возвращаемыми веб-API

Так что код похож

namespace PDataController.Controllers 
{ 
    public class ProvantisDataController : ApiController 
    { 
    public HttpResponseMessage Getdetails([FromUri] string[] id) 
    { 

     List<OracleParameter> prms = new List<OracleParameter>(); 
     string connStr = ConfigurationManager.ConnectionStrings["PDataConnection"].ConnectionString; 
     using (OracleConnection dbconn = new OracleConnection(connStr)) 
     { 
      var inconditions = id.Distinct().ToArray(); 
      var srtcon = string.Join(",", inconditions); 
      DataSet userDataset = new DataSet(); 
      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("; 
      StringBuilder sb = new StringBuilder(strQuery); 
      for(int x = 0; x < inconditions.Length; x++) 
       { 
        sb.Append(":p" + x + ","); 
        OracleParameter p = new OracleParameter(":p" + x,OracleDbType.NVarchar2); 
        p.Value = inconditions[x]; 
        prms.Add(p); 
       } 
      if(sb.Length > 0) sb.Length--; 
      strQuery = sb.ToString() + ")"; 
      using (OracleCommand selectCommand = new OracleCommand(strQuery, dbconn)) 
       { 
       selectCommand.Parameters.AddRange(prms.ToArray()); 
       using (OracleDataAdapter adapter = new OracleDataAdapter(selectCommand)) 
       { 
        DataTable selectResults = new DataTable(); 
        adapter.Fill(selectResults); 
        var returnObject = new { data = selectResults }; 
        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; 
       } 
      } 

     } 
    } 
} 
} 

Данные, возвращаемые для API в указанном ниже формате

{"data":[{"CATEGORY":"Internal Study","SESSION_NUMBER":7,"SESSION_START_DATE":"2015-02-13T00:00:00","SESSION_START_TIME":"2015-02-13T10:33:59.288394","SESSION_END_DATE":"2015-02-13T00:00:00"}]} 

Мы иногда имеющие проблемы в возвращении большого количества данных, которые он бросает OutOfMemory Исключение. enter image description here Было предложено использовать свойство JSON, параллельное свойству «data»: например, «next_data», со значением значения, которое необходимо передать в SQL OFFSET (который работает в MySQL, я не уверен, что это работает в oracle), если нет данных, то установите значение «next_data» равным 0. Я не уверен, как реализовать это. Не уверен, что это можно реализовать. Любая помощь с этим очень ценится. enter image description here

+1

Вместо того, чтобы заполнять «DataTable», вы можете напрямую передавать данные из «OracleDataReader» в строках [Сериализация JSON.net непосредственно из oledbconnection] (https://stackoverflow.com/questions/33835729). Конвертеры работают для любого 'IDataReader'. Вы * можете * также периодически обновлять поток ответов, см. [Выход контроллера ASP.NET Web API всегда буферизуется] (http://stackoverflow.com/questions/31487247). См. Также http://www.strathweb.com/2012/09/dealing-with-large-files-in-asp-net-web-api/ – dbc

+0

Можете ли вы дать полный вывод 'ToString()' исключения, включая сообщение, тип исключения, ** traceback ** и внутреннее исключение, если таковые имеются? Это поможет диагностировать, где у вас заканчивается память. – dbc

+0

@dbc Я добавил изображение исключения, которое получаю. Я попытался добавить точки останова в коде, и в коде не было ошибок, но в браузере я вижу ошибку, когда я вставил выше. – user4912134

ответ

3

Ваша проблема заключается в том, что вы запускаете запрос Oracle, который возвращает очень большое количество результатов, а затем загружает весь этот массив в память перед его сериализацией до HttpResponseMessage.

Чтобы уменьшить объем использования памяти, вы должны найти и устранить все случаи, когда весь набор результатов запроса загружается во временное промежуточное представление (например, строка DataTable или JSON) и вместо этого передают данные с помощью DataReader. Это позволяет избежать вытаскивания всего в память сразу в соответствии с this answer.

Во-первых, с вашего следа, похоже, у вас есть Enable Browser Link. Поскольку это, по-видимому, пытается кэшировать весь ответ в MemoryStream, вы хотите отключить его, как описано в FilePathResult thrown an OutOfMemoryException with large file.

Далее, вы можете передавать содержимое IDataReader непосредственно в JSON, используя Json.NET с следующим классом и конвертером:

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

Затем измените свой код, чтобы выглядеть примерно так:

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

Это позволяет избежать отображения результатов в памяти DataSet.

Кстати, я считаю линию

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

вместо этого должен быть:

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

Я считаю, что вы пытаетесь слезть Запятая в запросе, который будет присутствовать, если и только если inconditions.Length > 0

Обратите внимание: я не разработчик Oracle, и у меня нет установленного Oracle. Для тестирования я высмеял классы OracleClient, используя базовый OleDbConnection, и он отлично работал.

+0

ThAnks a ton.Do Мне нужно создать отдельный класс в приложении под названием OracleDataTableJsonResponseConverter и вызвать их в текущем контроллере. Я прав. – user4912134

+0

@ user4912134 - Из моего ответа вам нужно добавить классы «OracleDataTableJsonResponse», «OracleDataTableJsonResponseConverter» и «JsonExtensions». OracleDataTableJsonResponse и связанный с ним конвертер - это просто тонкие обертки для вызова «OracleCommand» с указанной строкой соединения, строкой и параметрами запроса, потоком результатов и выводом соединения сразу после. – dbc

+0

@ user4912134 - пожалуйста, дайте мне знать, если это сработает для вас. Он работает (и сохраняет память) с моей тестовой установкой, но, как я писал, у меня нет установленного Oracle. – dbc

0

Вы можете изменить свой метод, чтобы получить эти данные? Я имею в виду, что если вы используете службы RESTful, это не очень хорошая идея, чтобы трафик так много данных по одному запросу. Возможно, загрузка файла для этой цели или, возможно, получение данных с разбивкой по страницам.

Вы также можете попытаться изменить максимальный запрос длину, как этот ответ: Change max request lenght

Но опять же, это не хорошо для веб-приложения для трафика и/или процесса, так много данных одновременно.

+0

Я пробовал использовать те теги в web.config, это не помогло мне. – user4912134

+0

Изменение метода в том смысле, вы предлагаете мне следовать чему-то еще? – user4912134

+0

Возможно, ваши данные настолько велики, что ваше приложение изо всех сил пытается разобрать его на строку JSON. Если бы у меня было такое требование, я бы построил json-файл на бэкэнд, используя Tasks, и когда файл будет готов, загрузите весь файл. Ваша проблема, скорее всего, будет решена в вашей стратегии получения данных. –

Смежные вопросы