using System.Data;
using NPoco;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
namespace Umbraco.Cms.Persistence.SqlServer.Services;
///
/// A data reader used for reading collections of PocoData entity types
///
///
/// We are using a custom data reader so that tons of memory is not consumed when rebuilding this table, previously
/// we'd generate SQL insert statements, but we'd have to put all of the XML structures into memory first.
/// Alternatively
/// we can use .net's DataTable, but this also requires putting everything into memory. By using a DataReader we don't
/// have to
/// store every content item and it's XML structure in memory to get it into the DB, we can stream it into the db with
/// this
/// reader.
///
internal class PocoDataDataReader : BulkDataReader
where TSyntax : ISqlSyntaxProvider
{
private readonly ColumnDefinition[] _columnDefinitions;
private readonly IEnumerator _enumerator;
private readonly PocoColumn[] _readerColumns;
private readonly MicrosoftSqlSyntaxProviderBase _sqlSyntaxProvider;
private readonly TableDefinition _tableDefinition;
private int _recordsAffected = -1;
public PocoDataDataReader(
IEnumerable dataSource,
PocoData pd,
MicrosoftSqlSyntaxProviderBase sqlSyntaxProvider)
{
if (dataSource == null)
{
throw new ArgumentNullException(nameof(dataSource));
}
if (sqlSyntaxProvider == null)
{
throw new ArgumentNullException(nameof(sqlSyntaxProvider));
}
_tableDefinition = DefinitionFactory.GetTableDefinition(pd.Type, sqlSyntaxProvider);
if (_tableDefinition == null)
{
throw new InvalidOperationException("No table definition found for type " + pd.Type);
}
// only real columns, exclude result/computed columns
// Like NPoco does: https://github.com/schotime/NPoco/blob/5117a55fde57547e928246c044fd40bd00b2d7d1/src/NPoco.SqlServer/SqlBulkCopyHelper.cs#L59
_readerColumns = pd.Columns
.Where(x => x.Value.ResultColumn == false && x.Value.ComputedColumn == false)
.Select(x => x.Value)
.ToArray();
_sqlSyntaxProvider = sqlSyntaxProvider;
_enumerator = dataSource.GetEnumerator();
_columnDefinitions = _tableDefinition.Columns.ToArray();
}
protected override string SchemaName => _tableDefinition.SchemaName;
protected override string TableName => _tableDefinition.Name;
public override int RecordsAffected => _recordsAffected <= 0 ? -1 : _recordsAffected;
///
/// This will automatically add the schema rows based on the Poco table definition and the columns passed in
///
protected override void AddSchemaTableRows()
{
//var colNames = _readerColumns.Select(x => x.ColumnName).ToArray();
//foreach (var col in _columnDefinitions.Where(x => colNames.Contains(x.Name, StringComparer.OrdinalIgnoreCase)))
foreach (ColumnDefinition col in _columnDefinitions)
{
SqlDbType sqlDbType;
if (col.CustomDbType.HasValue)
{
//get the SqlDbType from the 'special type'
switch (col.CustomDbType)
{
case var x when x == SpecialDbType.NTEXT:
sqlDbType = SqlDbType.NText;
break;
case var x when x == SpecialDbType.NCHAR:
sqlDbType = SqlDbType.NChar;
break;
case var x when x == SpecialDbType.NVARCHARMAX:
sqlDbType = SqlDbType.NVarChar;
break;
default:
throw new ArgumentOutOfRangeException("The custom DB type " + col.CustomDbType +
" is not supported for bulk import statements.");
}
}
else if (col.Type.HasValue)
{
//get the SqlDbType from the DbType
sqlDbType = _sqlSyntaxProvider.GetSqlDbType(col.Type.Value);
}
else
{
//get the SqlDbType from the CLR type
sqlDbType = _sqlSyntaxProvider.GetSqlDbType(col.PropertyType);
}
AddSchemaTableRow(
col.Name,
col.Size > 0 ? col.Size : null,
col.Precision > 0 ? (short?)col.Precision : null,
null,
col.IsUnique,
col.IsIdentity,
col.IsNullable,
sqlDbType,
null,
null,
null,
null,
null);
}
}
///
/// Get the value from the column index for the current object
///
///
///
public override object GetValue(int i) =>
_enumerator.Current == null ? null! : _readerColumns[i].GetValue(_enumerator.Current);
///
/// Advance the cursor
///
///
public override bool Read()
{
var result = _enumerator.MoveNext();
if (result)
{
if (_recordsAffected == -1)
{
_recordsAffected = 0;
}
_recordsAffected++;
}
return result;
}
///
/// Ensure the enumerator is disposed
///
///
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_enumerator.Dispose();
}
}
}