diff --git a/include/class.file.php b/include/class.file.php
index 580330b64cc8731cd1031c0e5974f1655e281e6b..8e95bc08f7bae821e6a897211f3954e0aca240d1 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -806,31 +806,52 @@ class FileStorageBackend {
  * LOB fields in the MySQL database
  */
 define('CHUNK_SIZE', 500*1024); # Beware if you change this...
+class AttachmentFileChunk extends VerySimpleModel {
+    static $meta = array(
+        'table' => FILE_CHUNK_TABLE,
+        'pk' => array('file_id', 'chunk_id'),
+        'joins' => array(
+            'file' => array(
+                'constraint' => array('file_id' => 'AttachmentFile.id'),
+            ),
+        ),
+    );
+}
 class AttachmentChunkedData extends FileStorageBackend {
-    static $desc = "In the database";
+    static $desc = /* @trans */ "In the database";
     static $blocksize = CHUNK_SIZE;
 
     function __construct($file) {
         $this->file = $file;
         $this->_chunk = 0;
         $this->_buffer = false;
+        $this->eof = false;
     }
 
     function getSize() {
-        list($length) = db_fetch_row(db_query(
-             'SELECT SUM(LENGTH(filedata)) FROM '.FILE_CHUNK_TABLE
-            .' WHERE file_id='.db_input($this->file->getId())));
-        return $length;
+        $row = AttachmentFileChunk::objects()
+            ->filter(array('file' => $this->file))
+            ->aggregate(array('length' => SqlAggregate::SUM(SqlFunction::LENGTH(new SqlField('filedata')))))
+            ->one();
+        return $row['length'];
     }
 
     function read($amount=CHUNK_SIZE, $offset=0) {
         # Read requested length of data from attachment chunks
+        if ($this->eof)
+            return false;
+
         while (strlen($this->_buffer) < $amount + $offset) {
-            list($buf) = @db_fetch_row(db_query(
-                'SELECT filedata FROM '.FILE_CHUNK_TABLE.' WHERE file_id='
-                .db_input($this->file->getId()).' AND chunk_id='.$this->_chunk++));
-            if (!$buf)
+            try {
+                list($buf) = AttachmentFileChunk::objects()
+                    ->filter(array('file' => $this->file, 'chunk_id' => $this->_chunk++))
+                    ->values_flat('filedata')
+                    ->one();
+            }
+            catch (DoesNotExist $e) {
+                $this->eof = true;
                 break;
+            }
             $this->_buffer .= $buf;
         }
         $chunk = substr($this->_buffer, $offset, $amount);
@@ -840,23 +861,27 @@ class AttachmentChunkedData extends FileStorageBackend {
 
     function write($what, $chunk_size=CHUNK_SIZE) {
         $offset=0;
-        for (;;) {
-            $block = bin2hex(substr($what, $offset, $chunk_size));
-            if (!$block) break;
-            if (!db_query('REPLACE INTO '.FILE_CHUNK_TABLE
-                    .' SET filedata=0x'.$block.', file_id='
-                    .db_input($this->file->getId()).', chunk_id='.db_input($this->_chunk++)))
+        while ($block = substr($what, $offset, $chunk_size)) {
+            // Chunks are considered immutable. Importing chunks should
+            // forceable remove the contents of a file before write()ing new
+            // chunks. Therefore, inserts should be safe.
+            $chunk = AttachmentFileChunk::create(array(
+                'file' => $this->file,
+                'chunk_id' => $this->_chunk++,
+                'filedata' => $block
+            ));
+            if (!$chunk->save())
                 return false;
-            $offset += strlen($block)/2;
+            $offset += strlen($block);
         }
 
         return $this->_chunk;
     }
 
     function unlink() {
-        db_query('DELETE FROM '.FILE_CHUNK_TABLE
-            .' WHERE file_id='.db_input($this->file->getId()));
-        return db_affected_rows() > 0;
+        return AttachmentFileChunk::objects()
+            ->filter(array('file' => $this->file))
+            ->delete();
     }
 }
 FileStorageBackend::register('D', 'AttachmentChunkedData');