mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-16 01:25:41 -05:00
Add serialization service support (#840)
Added a method that handles serialization requests Support splitting save over multiple requests to reduce overall message size Added unit tests String changes used a new version of the string tool for generation. Will publish PR separately for the changes to build & localization so this can run on Mac without .Net Core 1.0
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
@@ -93,104 +94,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
DataType = column.DataType;
|
||||
DataTypeName = column.DataTypeName.ToLowerInvariant();
|
||||
|
||||
// Determine the SqlDbType
|
||||
SqlDbType type;
|
||||
if (Enum.TryParse(DataTypeName, true, out type))
|
||||
{
|
||||
SqlDbType = type;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (DataTypeName)
|
||||
{
|
||||
case "numeric":
|
||||
SqlDbType = SqlDbType.Decimal;
|
||||
break;
|
||||
case "sql_variant":
|
||||
SqlDbType = SqlDbType.Variant;
|
||||
break;
|
||||
case "timestamp":
|
||||
SqlDbType = SqlDbType.VarBinary;
|
||||
break;
|
||||
case "sysname":
|
||||
SqlDbType = SqlDbType.NVarChar;
|
||||
break;
|
||||
default:
|
||||
SqlDbType = DataTypeName.EndsWith(".sys.hierarchyid") ? SqlDbType.NVarChar : SqlDbType.Udt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We want the display name for the column to always exist
|
||||
ColumnName = string.IsNullOrEmpty(column.ColumnName)
|
||||
? SR.QueryServiceColumnNull
|
||||
: column.ColumnName;
|
||||
|
||||
switch (DataTypeName)
|
||||
{
|
||||
case "varchar":
|
||||
case "nvarchar":
|
||||
IsChars = true;
|
||||
|
||||
Debug.Assert(ColumnSize.HasValue);
|
||||
if (ColumnSize.Value == int.MaxValue)
|
||||
{
|
||||
//For Yukon, special case nvarchar(max) with column name == "Microsoft SQL Server 2005 XML Showplan" -
|
||||
//assume it is an XML showplan.
|
||||
//Please note this field must be in sync with a similar field defined in QESQLBatch.cs.
|
||||
//This is not the best fix that we could do but we are trying to minimize code impact
|
||||
//at this point. Post Yukon we should review this code again and avoid
|
||||
//hard-coding special column name in multiple places.
|
||||
const string yukonXmlShowPlanColumn = "Microsoft SQL Server 2005 XML Showplan";
|
||||
if (column.ColumnName == yukonXmlShowPlanColumn)
|
||||
{
|
||||
// Indicate that this is xml to apply the right size limit
|
||||
// Note we leave chars type as well to use the right retrieval mechanism.
|
||||
IsXml = true;
|
||||
}
|
||||
IsLong = true;
|
||||
}
|
||||
break;
|
||||
case "text":
|
||||
case "ntext":
|
||||
IsChars = true;
|
||||
IsLong = true;
|
||||
break;
|
||||
case "xml":
|
||||
IsXml = true;
|
||||
IsLong = true;
|
||||
break;
|
||||
case "binary":
|
||||
case "image":
|
||||
IsBytes = true;
|
||||
IsLong = true;
|
||||
break;
|
||||
case "varbinary":
|
||||
case "rowversion":
|
||||
IsBytes = true;
|
||||
|
||||
Debug.Assert(ColumnSize.HasValue);
|
||||
if (ColumnSize.Value == int.MaxValue)
|
||||
{
|
||||
IsLong = true;
|
||||
}
|
||||
break;
|
||||
case "sql_variant":
|
||||
IsSqlVariant = true;
|
||||
break;
|
||||
default:
|
||||
if (!AllServerDataTypes.Contains(DataTypeName))
|
||||
{
|
||||
// treat all UDT's as long/bytes data types to prevent the CLR from attempting
|
||||
// to load the UDT assembly into our process to call ToString() on the object.
|
||||
|
||||
IsUdt = true;
|
||||
IsBytes = true;
|
||||
IsLong = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
DetermineSqlDbType();
|
||||
AddNameAndDataFields(column.ColumnName);
|
||||
|
||||
if (IsUdt)
|
||||
{
|
||||
@@ -215,6 +120,19 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
}
|
||||
}
|
||||
|
||||
public DbColumnWrapper(ColumnInfo columnInfo)
|
||||
{
|
||||
DataTypeName = columnInfo.DataTypeName.ToLowerInvariant();
|
||||
DetermineSqlDbType();
|
||||
DataType = TypeConvertor.ToNetType(this.SqlDbType);
|
||||
if (DataType == typeof(String))
|
||||
{
|
||||
this.ColumnSize = int.MaxValue;
|
||||
}
|
||||
AddNameAndDataFields(columnInfo.Name);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor, used for deserializing JSON RPC only
|
||||
/// </summary>
|
||||
@@ -299,5 +217,176 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
private void DetermineSqlDbType()
|
||||
{
|
||||
// Determine the SqlDbType
|
||||
SqlDbType type;
|
||||
if (Enum.TryParse(DataTypeName, true, out type))
|
||||
{
|
||||
SqlDbType = type;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (DataTypeName)
|
||||
{
|
||||
case "numeric":
|
||||
SqlDbType = SqlDbType.Decimal;
|
||||
break;
|
||||
case "sql_variant":
|
||||
SqlDbType = SqlDbType.Variant;
|
||||
break;
|
||||
case "timestamp":
|
||||
SqlDbType = SqlDbType.VarBinary;
|
||||
break;
|
||||
case "sysname":
|
||||
SqlDbType = SqlDbType.NVarChar;
|
||||
break;
|
||||
default:
|
||||
SqlDbType = DataTypeName.EndsWith(".sys.hierarchyid") ? SqlDbType.NVarChar : SqlDbType.Udt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddNameAndDataFields(string columnName)
|
||||
{
|
||||
// We want the display name for the column to always exist
|
||||
ColumnName = string.IsNullOrEmpty(columnName)
|
||||
? SR.QueryServiceColumnNull
|
||||
: columnName;
|
||||
|
||||
switch (DataTypeName)
|
||||
{
|
||||
case "varchar":
|
||||
case "nvarchar":
|
||||
IsChars = true;
|
||||
|
||||
Debug.Assert(ColumnSize.HasValue);
|
||||
if (ColumnSize.Value == int.MaxValue)
|
||||
{
|
||||
//For Yukon, special case nvarchar(max) with column name == "Microsoft SQL Server 2005 XML Showplan" -
|
||||
//assume it is an XML showplan.
|
||||
//Please note this field must be in sync with a similar field defined in QESQLBatch.cs.
|
||||
//This is not the best fix that we could do but we are trying to minimize code impact
|
||||
//at this point. Post Yukon we should review this code again and avoid
|
||||
//hard-coding special column name in multiple places.
|
||||
const string yukonXmlShowPlanColumn = "Microsoft SQL Server 2005 XML Showplan";
|
||||
if (columnName == yukonXmlShowPlanColumn)
|
||||
{
|
||||
// Indicate that this is xml to apply the right size limit
|
||||
// Note we leave chars type as well to use the right retrieval mechanism.
|
||||
IsXml = true;
|
||||
}
|
||||
IsLong = true;
|
||||
}
|
||||
break;
|
||||
case "text":
|
||||
case "ntext":
|
||||
IsChars = true;
|
||||
IsLong = true;
|
||||
break;
|
||||
case "xml":
|
||||
IsXml = true;
|
||||
IsLong = true;
|
||||
break;
|
||||
case "binary":
|
||||
case "image":
|
||||
IsBytes = true;
|
||||
IsLong = true;
|
||||
break;
|
||||
case "varbinary":
|
||||
case "rowversion":
|
||||
IsBytes = true;
|
||||
|
||||
Debug.Assert(ColumnSize.HasValue);
|
||||
if (ColumnSize.Value == int.MaxValue)
|
||||
{
|
||||
IsLong = true;
|
||||
}
|
||||
break;
|
||||
case "sql_variant":
|
||||
IsSqlVariant = true;
|
||||
break;
|
||||
default:
|
||||
if (!AllServerDataTypes.Contains(DataTypeName))
|
||||
{
|
||||
// treat all UDT's as long/bytes data types to prevent the CLR from attempting
|
||||
// to load the UDT assembly into our process to call ToString() on the object.
|
||||
|
||||
IsUdt = true;
|
||||
IsBytes = true;
|
||||
IsLong = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Convert a base data type to another base data type
|
||||
/// </summary>
|
||||
public sealed class TypeConvertor
|
||||
{
|
||||
private static Dictionary<SqlDbType,Type> _typeMap = new Dictionary<SqlDbType,Type>();
|
||||
|
||||
static TypeConvertor()
|
||||
{
|
||||
_typeMap[SqlDbType.BigInt] = typeof(Int64);
|
||||
_typeMap[SqlDbType.Binary] = typeof(Byte);
|
||||
_typeMap[SqlDbType.Bit] = typeof(Boolean);
|
||||
_typeMap[SqlDbType.Char] = typeof(String);
|
||||
_typeMap[SqlDbType.DateTime] = typeof(DateTime);
|
||||
_typeMap[SqlDbType.Decimal] = typeof(Decimal);
|
||||
_typeMap[SqlDbType.Float] = typeof(Double);
|
||||
_typeMap[SqlDbType.Image] = typeof(Byte[]);
|
||||
_typeMap[SqlDbType.Int] = typeof(Int32);
|
||||
_typeMap[SqlDbType.Money] = typeof(Decimal);
|
||||
_typeMap[SqlDbType.NChar] = typeof(String);
|
||||
_typeMap[SqlDbType.NChar] = typeof(String);
|
||||
_typeMap[SqlDbType.NChar] = typeof(String);
|
||||
_typeMap[SqlDbType.NText] = typeof(String);
|
||||
_typeMap[SqlDbType.NVarChar] = typeof(String);
|
||||
_typeMap[SqlDbType.Real] = typeof(Single);
|
||||
_typeMap[SqlDbType.UniqueIdentifier] = typeof(Guid);
|
||||
_typeMap[SqlDbType.SmallDateTime] = typeof(DateTime);
|
||||
_typeMap[SqlDbType.SmallInt] = typeof(Int16);
|
||||
_typeMap[SqlDbType.SmallMoney] = typeof(Decimal);
|
||||
_typeMap[SqlDbType.Text] = typeof(String);
|
||||
_typeMap[SqlDbType.Timestamp] = typeof(Byte[]);
|
||||
_typeMap[SqlDbType.TinyInt] = typeof(Byte);
|
||||
_typeMap[SqlDbType.VarBinary] = typeof(Byte[]);
|
||||
_typeMap[SqlDbType.VarChar] = typeof(String);
|
||||
_typeMap[SqlDbType.Variant] = typeof(Object);
|
||||
// Note: treating as string
|
||||
_typeMap[SqlDbType.Xml] = typeof(String);
|
||||
_typeMap[SqlDbType.TinyInt] = typeof(Byte);
|
||||
_typeMap[SqlDbType.TinyInt] = typeof(Byte);
|
||||
_typeMap[SqlDbType.TinyInt] = typeof(Byte);
|
||||
_typeMap[SqlDbType.TinyInt] = typeof(Byte);
|
||||
}
|
||||
|
||||
private TypeConvertor()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Convert TSQL type to .Net data type
|
||||
/// </summary>
|
||||
/// <param name="sqlDbType"></param>
|
||||
/// <returns></returns>
|
||||
public static Type ToNetType(SqlDbType sqlDbType)
|
||||
{
|
||||
Type netType;
|
||||
if (!_typeMap.TryGetValue(sqlDbType, out netType))
|
||||
{
|
||||
netType = typeof(String);
|
||||
}
|
||||
return netType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,77 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Data;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
{
|
||||
|
||||
|
||||
public class ColumnInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of this column
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
public string DataTypeName { get; set; }
|
||||
|
||||
public ColumnInfo()
|
||||
{
|
||||
}
|
||||
|
||||
public ColumnInfo(string name, string dataTypeName)
|
||||
{
|
||||
this.Name = name;
|
||||
this.DataTypeName = dataTypeName;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ISerializationParams
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Path to file that the serialized results will be stored in
|
||||
/// </summary>
|
||||
string FilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Results that are to be serialized into 'SaveFormat' format
|
||||
/// </summary>
|
||||
DbCellValue[][] Rows { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the current set of Rows passed in is the last for this file
|
||||
// </summary>
|
||||
bool IsLastBatch { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Class used for storing results and how the results are to be serialized
|
||||
/// </summary>
|
||||
public class SaveResultsInfo
|
||||
public class SerializeDataContinueRequestParams : ISerializationParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to file that the serialized results will be stored in
|
||||
/// </summary>
|
||||
public string FilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Results that are to be serialized into 'SaveFormat' format
|
||||
/// </summary>
|
||||
public DbCellValue[][] Rows { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the current set of Rows passed in is the last for this file
|
||||
// </summary>
|
||||
public bool IsLastBatch { get; set; }
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Class used for storing results and how the results are to be serialized
|
||||
/// </summary>
|
||||
public class SerializeDataStartRequestParams : GeneralRequestDetails, ISerializationParams
|
||||
{
|
||||
/// <summary>
|
||||
/// String representation of the type that service is supposed to serialize to
|
||||
@@ -21,37 +84,97 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
||||
/// <summary>
|
||||
/// Path to file that the serialized results will be stored in
|
||||
/// </summary>
|
||||
public string SavePath { get; set; }
|
||||
public string FilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Results that are to be serialized into 'SaveFormat' format
|
||||
/// </summary>
|
||||
public DbCellValue[][] Rows { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the current set of Rows passed in is the last for this file
|
||||
// </summary>
|
||||
public bool IsLast { get; set; }
|
||||
public ColumnInfo[] Columns { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this is the only request expected for this file.
|
||||
// </summary>
|
||||
public bool IsLastBatch { get; set; }
|
||||
|
||||
public SerializeDataStartRequestParams()
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public SaveResultsInfo(string saveFormat,
|
||||
public SerializeDataStartRequestParams(string saveFormat,
|
||||
string savePath,
|
||||
DbCellValue[][] rows,
|
||||
bool isLast)
|
||||
{
|
||||
this.SaveFormat = saveFormat;
|
||||
this.SavePath = savePath;
|
||||
this.FilePath = savePath;
|
||||
this.Rows = Rows;
|
||||
this.IsLast = isLast;
|
||||
this.IsLastBatch = isLast;
|
||||
}
|
||||
|
||||
internal bool IncludeHeaders
|
||||
{
|
||||
get { return this.GetOptionValue<bool>(SerializationOptionsHelper.IncludeHeaders); }
|
||||
set { this.SetOptionValue<bool>(SerializationOptionsHelper.IncludeHeaders, value); }
|
||||
}
|
||||
|
||||
internal string Delimiter
|
||||
{
|
||||
get { return this.GetOptionValue<string>(SerializationOptionsHelper.Delimiter); }
|
||||
set { this.SetOptionValue<string>(SerializationOptionsHelper.Delimiter, value); }
|
||||
}
|
||||
|
||||
internal string LineSeparator
|
||||
{
|
||||
get { return this.GetOptionValue<string>(SerializationOptionsHelper.LineSeparator); }
|
||||
set { this.SetOptionValue<string>(SerializationOptionsHelper.LineSeparator, value); }
|
||||
}
|
||||
|
||||
internal string TextIdentifier
|
||||
{
|
||||
get { return this.GetOptionValue<string>(SerializationOptionsHelper.TextIdentifier); }
|
||||
set { this.SetOptionValue<string>(SerializationOptionsHelper.TextIdentifier, value); }
|
||||
}
|
||||
|
||||
internal string Encoding
|
||||
{
|
||||
get { return this.GetOptionValue<string>(SerializationOptionsHelper.Encoding); }
|
||||
set { this.SetOptionValue<string>(SerializationOptionsHelper.Encoding, value); }
|
||||
}
|
||||
|
||||
internal bool Formatted
|
||||
{
|
||||
get { return this.GetOptionValue<bool>(SerializationOptionsHelper.Formatted); }
|
||||
set { this.SetOptionValue<bool>(SerializationOptionsHelper.Formatted, value); }
|
||||
}
|
||||
}
|
||||
|
||||
public class SaveAsRequest
|
||||
public class SerializeDataResult
|
||||
{
|
||||
public static readonly
|
||||
RequestType<SaveResultsInfo, SaveResultRequestResult> Type =
|
||||
RequestType<SaveResultsInfo, SaveResultRequestResult>.Create("query/saveAs");
|
||||
public string Messages { get; set; }
|
||||
|
||||
public bool Succeeded { get; set; }
|
||||
}
|
||||
|
||||
public class SerializeStartRequest
|
||||
{
|
||||
public static readonly RequestType<SerializeDataStartRequestParams, SerializeDataResult> Type = RequestType<SerializeDataStartRequestParams, SerializeDataResult>.Create("serialize/start");
|
||||
}
|
||||
public class SerializeContinueRequest
|
||||
{
|
||||
public static readonly RequestType<SerializeDataContinueRequestParams, SerializeDataResult> Type = RequestType<SerializeDataContinueRequestParams, SerializeDataResult>.Create("serialize/continue");
|
||||
}
|
||||
|
||||
class SerializationOptionsHelper
|
||||
{
|
||||
internal const string IncludeHeaders = "includeHeaders";
|
||||
internal const string Delimiter = "delimiter";
|
||||
internal const string LineSeparator = "lineSeparator";
|
||||
internal const string TextIdentifier = "textIdentifier";
|
||||
internal const string Encoding = "encoding";
|
||||
internal const string Formatted = "formatted";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Composition;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.Extensibility;
|
||||
using Microsoft.SqlTools.Hosting;
|
||||
using Microsoft.SqlTools.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
||||
{
|
||||
|
||||
[Export(typeof(IHostedService))]
|
||||
public class SerializationService : HostedService<SerializationService>, IComposableService
|
||||
{
|
||||
private ConcurrentDictionary<string, DataSerializer> inProgressSerializations;
|
||||
|
||||
public SerializationService()
|
||||
{
|
||||
inProgressSerializations = new ConcurrentDictionary<string, DataSerializer>();
|
||||
}
|
||||
|
||||
public override void InitializeService(IProtocolEndpoint serviceHost)
|
||||
{
|
||||
Logger.Write(TraceEventType.Verbose, "SerializationService initialized");
|
||||
serviceHost.SetRequestHandler(SerializeStartRequest.Type, HandleSerializeStartRequest);
|
||||
serviceHost.SetRequestHandler(SerializeContinueRequest.Type, HandleSerializeContinueRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin to process request to save a resultSet to a file in CSV format
|
||||
/// </summary>
|
||||
internal async Task HandleSerializeStartRequest(SerializeDataStartRequestParams serializeParams,
|
||||
RequestContext<SerializeDataResult> requestContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
Validate.IsNotNull(nameof(serializeParams), serializeParams);
|
||||
Validate.IsNotNullOrWhitespaceString("FilePath", serializeParams.FilePath);
|
||||
|
||||
DataSerializer serializer = null;
|
||||
bool hasSerializer = inProgressSerializations.TryGetValue(serializeParams.FilePath, out serializer);
|
||||
if (hasSerializer)
|
||||
{
|
||||
throw new Exception(SR.SerializationServiceRequestInProgress(serializeParams.FilePath));
|
||||
}
|
||||
|
||||
serializer = new DataSerializer(serializeParams);
|
||||
if (!serializeParams.IsLastBatch)
|
||||
{
|
||||
inProgressSerializations.AddOrUpdate(serializer.FilePath, serializer, (key, old) => serializer);
|
||||
}
|
||||
Func<Task<SerializeDataResult>> writeData = () =>
|
||||
{
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
var result = serializer.ProcessRequest(serializeParams);
|
||||
return result;
|
||||
});
|
||||
};
|
||||
await HandleRequest(writeData, requestContext, "HandleSerializeStartRequest");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await requestContext.SendError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process request to save a resultSet to a file in CSV format
|
||||
/// </summary>
|
||||
internal async Task HandleSerializeContinueRequest(SerializeDataContinueRequestParams serializeParams,
|
||||
RequestContext<SerializeDataResult> requestContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
Validate.IsNotNull(nameof(serializeParams), serializeParams);
|
||||
Validate.IsNotNullOrWhitespaceString("FilePath", serializeParams.FilePath);
|
||||
|
||||
DataSerializer serializer = null;
|
||||
bool hasSerializer = inProgressSerializations.TryGetValue(serializeParams.FilePath, out serializer);
|
||||
if (!hasSerializer)
|
||||
{
|
||||
throw new Exception(SR.SerializationServiceRequestNotFound(serializeParams.FilePath));
|
||||
}
|
||||
|
||||
Func<Task<SerializeDataResult>> writeData = () =>
|
||||
{
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
var result = serializer.ProcessRequest(serializeParams);
|
||||
if (serializeParams.IsLastBatch)
|
||||
{
|
||||
// Cleanup the serializer
|
||||
this.inProgressSerializations.TryRemove(serializer.FilePath, out serializer);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
};
|
||||
await HandleRequest(writeData, requestContext, "HandleSerializeContinueRequest");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await requestContext.SendError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleRequest<T>(Func<Task<T>> handler, RequestContext<T> requestContext, string requestType)
|
||||
{
|
||||
Logger.Write(TraceEventType.Verbose, requestType);
|
||||
|
||||
try
|
||||
{
|
||||
T result = await handler();
|
||||
await requestContext.SendResult(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await requestContext.SendError(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
class DataSerializer
|
||||
{
|
||||
private IFileStreamWriter writer;
|
||||
private SerializeDataStartRequestParams requestParams;
|
||||
private IList<DbColumnWrapper> columns;
|
||||
|
||||
public string FilePath { get; private set; }
|
||||
|
||||
public DataSerializer(SerializeDataStartRequestParams requestParams)
|
||||
{
|
||||
this.requestParams = requestParams;
|
||||
this.columns = this.MapColumns(requestParams.Columns);
|
||||
this.FilePath = requestParams.FilePath;
|
||||
}
|
||||
|
||||
private IList<DbColumnWrapper> MapColumns(ColumnInfo[] columns)
|
||||
{
|
||||
List<DbColumnWrapper> columnWrappers = new List<DbColumnWrapper>();
|
||||
foreach (ColumnInfo column in columns)
|
||||
{
|
||||
DbColumnWrapper wrapper = new DbColumnWrapper(column);
|
||||
columnWrappers.Add(wrapper);
|
||||
}
|
||||
return columnWrappers;
|
||||
}
|
||||
|
||||
|
||||
public SerializeDataResult ProcessRequest(ISerializationParams serializeParams)
|
||||
{
|
||||
SerializeDataResult result = new SerializeDataResult();
|
||||
try
|
||||
{
|
||||
this.WriteData(serializeParams.Rows, serializeParams.IsLastBatch);
|
||||
if (serializeParams.IsLastBatch)
|
||||
{
|
||||
this.CloseStreams();
|
||||
}
|
||||
result.Succeeded = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Messages = ex.Message;
|
||||
result.Succeeded = false;
|
||||
this.CloseStreams();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void WriteData(DbCellValue[][] rows, bool isComplete)
|
||||
{
|
||||
this.EnsureWriterCreated();
|
||||
foreach (var row in rows)
|
||||
{
|
||||
SetRawObjects(row);
|
||||
writer.WriteRow(row, this.columns);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetRawObjects(DbCellValue[] row)
|
||||
{
|
||||
for (int i = 0; i < row.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try to set as the "correct" type
|
||||
var value = Convert.ChangeType(row[i].DisplayValue, columns[i].DataType);
|
||||
row[i].RawObject = value;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
row[i].RawObject = row[i].DisplayValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureWriterCreated()
|
||||
{
|
||||
if (this.writer == null)
|
||||
{
|
||||
IFileStreamFactory factory;
|
||||
switch (this.requestParams.SaveFormat.ToLowerInvariant())
|
||||
{
|
||||
case "json":
|
||||
factory = new SaveAsJsonFileStreamFactory()
|
||||
{
|
||||
SaveRequestParams = CreateJsonRequestParams()
|
||||
};
|
||||
break;
|
||||
case "csv":
|
||||
factory = new SaveAsCsvFileStreamFactory()
|
||||
{
|
||||
SaveRequestParams = CreateCsvRequestParams()
|
||||
};
|
||||
break;
|
||||
case "xml":
|
||||
factory = new SaveAsXmlFileStreamFactory()
|
||||
{
|
||||
SaveRequestParams = CreateXmlRequestParams()
|
||||
};
|
||||
break;
|
||||
case "excel":
|
||||
factory = new SaveAsExcelFileStreamFactory()
|
||||
{
|
||||
SaveRequestParams = CreateExcelRequestParams()
|
||||
};
|
||||
break;
|
||||
default:
|
||||
throw new Exception(SR.SerializationServiceUnsupportedFormat(this.requestParams.SaveFormat));
|
||||
}
|
||||
this.writer = factory.GetWriter(requestParams.FilePath);
|
||||
}
|
||||
}
|
||||
private void CloseStreams()
|
||||
{
|
||||
this.writer.Dispose();
|
||||
}
|
||||
|
||||
private SaveResultsAsJsonRequestParams CreateJsonRequestParams()
|
||||
{
|
||||
return new SaveResultsAsJsonRequestParams
|
||||
{
|
||||
FilePath = this.requestParams.FilePath,
|
||||
BatchIndex = 0,
|
||||
ResultSetIndex = 0
|
||||
};
|
||||
}
|
||||
private SaveResultsAsExcelRequestParams CreateExcelRequestParams()
|
||||
{
|
||||
return new SaveResultsAsExcelRequestParams
|
||||
{
|
||||
FilePath = this.requestParams.FilePath,
|
||||
BatchIndex = 0,
|
||||
ResultSetIndex = 0,
|
||||
IncludeHeaders = this.requestParams.IncludeHeaders
|
||||
};
|
||||
}
|
||||
private SaveResultsAsCsvRequestParams CreateCsvRequestParams()
|
||||
{
|
||||
return new SaveResultsAsCsvRequestParams
|
||||
{
|
||||
FilePath = this.requestParams.FilePath,
|
||||
BatchIndex = 0,
|
||||
ResultSetIndex = 0,
|
||||
IncludeHeaders = this.requestParams.IncludeHeaders,
|
||||
Delimiter = this.requestParams.Delimiter,
|
||||
LineSeperator = this.requestParams.LineSeparator,
|
||||
TextIdentifier = this.requestParams.TextIdentifier,
|
||||
Encoding = this.requestParams.Encoding
|
||||
};
|
||||
}
|
||||
private SaveResultsAsXmlRequestParams CreateXmlRequestParams()
|
||||
{
|
||||
return new SaveResultsAsXmlRequestParams
|
||||
{
|
||||
FilePath = this.requestParams.FilePath,
|
||||
BatchIndex = 0,
|
||||
ResultSetIndex = 0,
|
||||
Formatted = this.requestParams.Formatted,
|
||||
Encoding = this.requestParams.Encoding
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user