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