/// <summary>
        /// Marks orphaned file records (the ones that do not have a referencing binary record anymore) as Deleted.
        /// </summary>
        public void CleanupFilesSetDeleteFlag()
        {
            var isLocalTransaction = false;

            if (!TransactionScope.IsActive)
            {
                TransactionScope.Begin();
                isLocalTransaction = true;
            }

            try
            {
                using (var proc = new SqlProcedure {
                    CommandText = CleanupFileSetIsdeletedScript, CommandType = CommandType.Text
                })
                {
                    proc.CommandType = CommandType.Text;
                    proc.ExecuteNonQuery();
                }

                if (isLocalTransaction && TransactionScope.IsActive)
                {
                    TransactionScope.Commit();
                }
            }
            catch (Exception ex)
            {
                if (isLocalTransaction && TransactionScope.IsActive)
                {
                    TransactionScope.Rollback();
                }

                throw new DataException("Error during setting deleted flag on files.", ex);
            }
        }
        /// <summary>
        /// Finalizes a chunked save operation.
        /// </summary>
        /// <param name="versionId">Content version id.</param>
        /// <param name="propertyTypeId">Binary property type id.</param>
        /// <param name="fileId">File identifier.</param>
        /// <param name="fullSize">Full size (stream length) of the binary value.</param>
        /// <param name="source">Binary data containing metadata (e.g. content type).</param>
        public void CommitChunk(int versionId, int propertyTypeId, int fileId, long fullSize, BinaryDataValue source = null)
        {
            // start a new transaction here if needed
            var isLocalTransaction = !TransactionScope.IsActive;

            if (isLocalTransaction)
            {
                TransactionScope.Begin();
            }

            try
            {
                // commit the process: set the final full size and checksum
                using (var cmd = new SqlProcedure {
                    CommandText = CommitChunkScript, CommandType = CommandType.Text
                })
                {
                    cmd.Parameters.Add("@FileId", SqlDbType.Int).Value            = fileId;
                    cmd.Parameters.Add("@VersionId", SqlDbType.Int).Value         = versionId;
                    cmd.Parameters.Add("@PropertyTypeId", SqlDbType.Int).Value    = propertyTypeId;
                    cmd.Parameters.Add("@Size", SqlDbType.BigInt).Value           = fullSize;
                    cmd.Parameters.Add("@Checksum", SqlDbType.VarChar, 200).Value = DBNull.Value;

                    cmd.Parameters.Add("@ContentType", SqlDbType.NVarChar, 50).Value = source != null ? source.ContentType : string.Empty;
                    cmd.Parameters.Add("@FileNameWithoutExtension", SqlDbType.NVarChar, 450).Value = source != null
                        ? source.FileName.FileNameWithoutExtension == null
                            ? DBNull.Value
                            : (object)source.FileName.FileNameWithoutExtension
                        : DBNull.Value;

                    cmd.Parameters.Add("@Extension", SqlDbType.NVarChar, 50).Value = source != null?ValidateExtension(source.FileName.Extension) : string.Empty;

                    cmd.ExecuteNonQuery();
                }
            }
            catch (Exception ex)
            {
                // rollback the transaction if it was opened locally
                if (isLocalTransaction && TransactionScope.IsActive)
                {
                    TransactionScope.Rollback();
                }

                throw new DataException("Error during committing binary chunk to file stream.", ex);
            }
            finally
            {
                // commit the transaction if it was opened locally
                if (isLocalTransaction && TransactionScope.IsActive)
                {
                    TransactionScope.Commit();
                }
            }
        }
        /// <summary>
        /// Starts a chunked save operation on an existing content. It does not write any binary data
        /// to the storage, it only makes prerequisite operations - e.g. allocates a new slot in the storage.
        /// </summary>
        /// <param name="blobProvider">Blob storage provider.</param>
        /// <param name="versionId">Content version id.</param>
        /// <param name="propertyTypeId">Binary property type id.</param>
        /// <param name="fullSize">Full size (stream length) of the binary value.</param>
        /// <returns>A token containing all the information (db record ids) that identify a single entry in the blob storage.</returns>
        public string StartChunk(IBlobProvider blobProvider, int versionId, int propertyTypeId, long fullSize)
        {
            var isLocalTransaction = !TransactionScope.IsActive;

            if (isLocalTransaction)
            {
                TransactionScope.Begin();
            }

            var ctx = new BlobStorageContext(blobProvider)
            {
                VersionId = versionId, PropertyTypeId = propertyTypeId, FileId = 0, Length = fullSize
            };
            string blobProviderName = null;
            string blobProviderData = null;
            bool   useSqlFileStream;

            if (IsBuiltInOrSqlFileStreamProvider(blobProvider))
            {
                useSqlFileStream = SqlFileStreamBlobProvider.UseFileStream(blobProvider, fullSize);
            }
            else
            {
                useSqlFileStream = false;
                blobProvider.Allocate(ctx);
                blobProviderName = blobProvider.GetType().FullName;
                blobProviderData = BlobStorageContext.SerializeBlobProviderData(ctx.BlobProviderData);
            }

            try
            {
                using (var cmd = new SqlProcedure {
                    CommandText = InsertStagingBinaryScript, CommandType = CommandType.Text
                })
                {
                    cmd.Parameters.Add("@VersionId", SqlDbType.Int).Value              = versionId;
                    cmd.Parameters.Add("@PropertyTypeId", SqlDbType.Int).Value         = propertyTypeId;
                    cmd.Parameters.Add("@Size", SqlDbType.BigInt).Value                = fullSize;
                    cmd.Parameters.Add("@UseSqlFileStream", SqlDbType.TinyInt).Value   = useSqlFileStream ? 2 : 0;
                    cmd.Parameters.Add("@BlobProvider", SqlDbType.NVarChar, 450).Value = blobProviderName != null ? (object)blobProviderName : DBNull.Value;
                    cmd.Parameters.Add("@BlobProviderData", SqlDbType.NVarChar, int.MaxValue).Value = blobProviderData != null ? (object)blobProviderData : DBNull.Value;

                    int binaryPropertyId;
                    int fileId;

                    using (var reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            binaryPropertyId = reader.GetSafeInt32(0);
                            fileId           = reader.GetSafeInt32(1);
                        }
                        else
                        {
                            throw new DataException("File row could not be inserted.");
                        }
                    }

                    ctx.FileId = fileId;

                    return(new ChunkToken
                    {
                        VersionId = versionId,
                        PropertyTypeId = propertyTypeId,
                        BinaryPropertyId = binaryPropertyId,
                        FileId = fileId
                    }.GetToken());
                }
            }
            catch (Exception ex)
            {
                if (isLocalTransaction && TransactionScope.IsActive)
                {
                    TransactionScope.Rollback();
                }

                throw new DataException("Error during saving binary chunk to SQL Server.", ex);
            }
            finally
            {
                if (isLocalTransaction && TransactionScope.IsActive)
                {
                    TransactionScope.Commit();
                }
            }
        }