DEV Community

Welcome Bonginhlahla Sithole
Welcome Bonginhlahla Sithole

Posted on

Save documets or files using EntityFrameworkCore to database

Introduction

By default, EntityFrameworkCore doesn’t support storing files in the database.
As a reader of this article, I assume you have worked with Microsoft.EntityFrameworkCore before for saving data into the database.

There are times when applications or API needs to work with images, for example, you need to store user profile images or some documents submitted by a user.
You can use external sources to store those files, but what if you want to handle them yourself easier and more efficiently?
Well, the good news is this NuGet library Files.EntityFrameworkCore.Extensions

The solution to files inside the database & memory usage

This library solves the memory problem that comes with storing files inside the database. If you store a 100MB file as a base64 string in the database, you will need a memory of 100MB available so that the conversion of Stream to base64 string can happen. If 20 users upload about 100MB files all at once, then your API is in trouble because you now need memory that can handle 20*100MB which is about 2GB. Same as file downloading, you will need more memory for base64/Stream conversion.
This library solves this problem by storing bytes in smaller chunks, you can also set your chunk size, the default is 64k.

You can use this library on any database that is supported by Microsoft.EntityFrameworkCore.

Code examples

Enough talking, let's jump into code examples.

    // You must extend this IFileEntity interface on entity/class that represents the database table that will store files
    public class UserImage : IFileEntity 
    {
        public Guid Id { get; set; }
        public Guid FileId { get; set; }
        public string Name { get; set; }
        public string MimeType { get; set; }
        public DateTimeOffset TimeStamp { get; set; }
        public Guid? NextId { get; set; }
        public int ChunkBytesLength { get; set; }
        public long TotalBytesLength { get; set; }
        public byte[] Data { get; set; }
    }

    [HttpPost]
    public async Task<ActionResult<FilesExtensionsResponse>> UploadFile([FromForm] IFormFile file)
    {
         if (file.Length > 0)
         {
            //Save file chunks from stream to database, you can also save from filePath
             var fileDetails = await _context.SaveFileAsync<UserImage>(file.OpenReadStream(), file.FileName, file.ContentType);
             return Ok(fileDetails);
         }
         else
         {
             return BadRequest("File is required.");
         }
    }

    [HttpGet("{id}/download")]
    public async Task<IActionResult> DownLoadFile(Guid id)
    {
        var fileDetails = await _context.GetFileInfoAsync<UserImage>(id);
        //Create a Stream and download file chunks from the database into this stream.
        var stream = new MemoryStream();
        await _context.DownloadFileToStreamAsync<UserImage>(id, stream);
        return File(stream, fileDetails.MimeType, fileDetails.Name);
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteUserImage(Guid id)
    {
        await _context.DeleteFileAsync<UserImage>(id);
        return NoContent();
    }
Enter fullscreen mode Exit fullscreen mode

The image below shows chunks inside the database after saving.

Chunks inside the database after saving

The source code can be found here, there are more examples inside this repository: https://github.com/SitholeWB/Files.EntityFrameworkCore.Extensions

Conclusion

We have learned that we can save files inside a database with very minimum effort, we don't have to worry about handling chunks ourselves. The library will require about 64k of memory no matter how big the file is.
Thanks, happy coding!

Top comments (0)