A continuation of sort of Does LLBLGen support accessing SQL Server blob fields as C# IO.Streams
I have successfully experimented with code to read and write to blob fields with streams, but it seems a bit harder than it needs to be so wondering if there is a better way?
Code is here: (https://github.com/JeremyThomas/LLBLStreaming)
Firstly writing:
/// <summary>
/// Inserts an Entity in the database asynchronously, with the specified field streamed.
/// </summary>
/// <param name="dataAccessAdapter">The data access adapter.</param>
/// <param name="entity">The entity to be inserted.</param>
/// <param name="indexOfFieldtoBeStreamed">Index of the field in the entity to be streamed.</param>
/// <param name="stream">The stream.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The number of rows affected or the integer primary key of inserted row (if applicable)</returns>
static async Task<long> InsertEntityWithOneFieldStreamedAsync(DataAccessAdapter dataAccessAdapter, EntityBase2 entity, int indexOfFieldtoBeStreamed, Stream stream,
CancellationToken cancellationToken)
{
Logger.DebugMethod(dataAccessAdapter.ConnectionString, entity, indexOfFieldtoBeStreamed, stream.GetType(), cancellationToken);
entity.Fields[indexOfFieldtoBeStreamed].CurrentValue = stream;
var actionQuery = dataAccessAdapter.CreateInsertDQ(entity);
var numRowsAffected = await dataAccessAdapter.ExecuteActionQueryAsync(actionQuery, cancellationToken);
var id = actionQuery.ParameterFieldRelations.FirstOrDefault()?.Field.CurrentValue;
return id switch
{
int intID => intID,
long longID => longID,
_ => numRowsAffected
};
}
// __LLBLGENPRO_USER_CODE_REGION_START CustomDataAccessAdapterCode
/// <summary>
/// Creates a new Insert Query object which is ready to use.
/// </summary>
/// <returns>IActionQuery Instance which is ready to be used.</returns>
/// <remarks>Generic version.</remarks>
/// <exception cref="T:System.ArgumentNullException">When fields is null or fieldsPersistenceInfo is null</exception>
/// <exception cref="T:System.ArgumentException">When fields contains no EntityFieldCore instances or fieldsPersistenceInfo is empty.</exception>
/// <exception cref="T:SD.LLBLGen.Pro.ORMSupportClasses.ORMQueryConstructionException">When there are no fields to insert in the fields list. This exception is to prevent
/// INSERT INTO table () VALUES () style queries.</exception>
public IActionQuery CreateInsertDQ(IEntity2 entity)
{
return CreateDynamicQueryEngine().CreateInsertDQ(entity.Fields.GetAsEntityFieldCoreArray(), GetAllFieldPersistenceInfos(entity), GetActiveConnection());
}
/// <summary>
/// Gets all field persistence infos.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
IFieldPersistenceInfo[] GetAllFieldPersistenceInfos(IEntity2 entity)
{
return PersistenceInfoProviderSingleton.GetInstance().GetAllFieldPersistenceInfos(entity);
}
// __LLBLGENPRO_USER_CODE_REGION_END
Issues:
1. I had to add code to the DataAccessAdapter to allow this
2. I'm not sure how to make an update version of it
3. I'm uncomfortable working such low level
As for reading:
public static async Task<long> StreamLargePhotoToFileAsync(IDataAccessAdapter portalAdapterToUse, long productPhotoID, string filePath, CancellationToken cancellationToken,
IProgress<long> progress)
{
using var dateReader = await GetProductLargePhotoReader(portalAdapterToUse, productPhotoID, cancellationToken);
if (dateReader is DbDataReader reader && await reader.ReadAsync(cancellationToken).ConfigureAwait(GeneralHelper.ContinueOnCapturedContext))
{
var stream = GetStream(reader, 1);
if (stream.CanRead)
{
using var file = new FileStream(filePath, FileMode.Create, FileAccess.Write);
using var streamProgressWrapper = new StreamProgressWrapper(stream, progress);
await streamProgressWrapper.CopyToAsync(file).ConfigureAwait(GeneralHelper.ContinueOnCapturedContext);
return file.Length;
}
}
return 0;
}
public static Task<IDataReader> GetProductLargePhotoReader(IDataAccessAdapter portalAdapterToUse, long productPhotoID, CancellationToken cancellationToken)
{
Logger.DebugMethod(portalAdapterToUse.ConnectionString, productPhotoID);
var qf = new QueryFactory();
var q = qf.Create()
.Select(ProductPhotoFields.ProductPhotoID, ProductPhotoFields.LargePhoto, ProductPhotoFields.LargePhotoFileName)
.Where(ProductPhotoFields.ProductPhotoID == productPhotoID);
return portalAdapterToUse.FetchAsDataReaderAsync(q, CommandBehavior.SequentialAccess, cancellationToken);
}
/// <summary>
/// Gets the stream. Should be System.Data.SqlClient.SqlSequentialStream
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="ordinal"></param>
/// <returns>The stream</returns>
public static Stream GetStream(DbDataReader reader, int ordinal)
{
var readerType = reader.GetType();
Logger.DebugMethod(readerType);
if (reader.IsDBNull(ordinal))
return null;
var stream = reader.GetStream(ordinal);
Logger.DebugFormat("{0} returned {1} which has CanRead: {2}", readerType, stream.GetType(), stream.CanRead);
return stream;
}
It seems clunky and I had to use queryspec as I can't see how to do it with Linq?