George Kosmidis

Microsoft MVP | Cloud Solutions Architect | .NET Software Engineer | Organizer of Munich .NET Meetup | Speaker

Using LiteDB in an ASP.NET Core API

by George Kosmidis / Published 3 years ago, modified 2 years and 4 months ago
Using LiteDB in an ASP.NET Core API

LiteDB is serverless MongoDB-like database delivered in a single DLL (less than 350kb) fully written in .NET C# managed code (compatible with .NET 3.5, 4.x, NETStandard 1.3 and 2.0). It is ideal for mobile apps or for small desktop/web apps, and its API is very similar to MongoDB C# Official Driver.

In this post, we will see how to use LiteDB as a storage for an ASP.NET API and do simple CRUD operations with it.

Setting up the project

Assuming basic knowledge on how to create a new .NET Core api, I will just list the necessary commands and continue to the interesting parts:

dotnet new webapi
dotnet add package LiteDB
dotnet build

You could also choose to use Visual Studio to create a new ASP.NET Core Web Application, choose “API” as the project template and finally add the LiteDB nuget package.

Now that we have a project to work and the LiteDB package installed, we should add the actual database file, right? Well no! LiteDB will create a database automatically upon the first request for getting a collection, so we don’t need to worry about that. The only thing we should do though, is keep somewhere the path to the database, and there is no better place for it than the appsettings:

"LiteDbOptions": {
  "DatabaseLocation": "LiteDb/LiteDbTest.db"
}

Setting up the DbContext

Mauricio David, talking about thread-safety and process-safety of LiteDb, advises:

If your application works in a single process (like mobile apps, asp.net websites) prefer to use a single database instance and share across all threads.

Since we are not planning anything big, let’s just do that! Create a DbContext class that instantiates a LiteDatabase in its constructor and inject it into the build-in IoC container as a singleton:

public class LiteDbContext : ILiteDbContext
{
    public LiteDatabase Database { get; }

    public LiteDbContext(IOptions<LiteDbOptions> options)
    {
        Database = new LiteDatabase(options.Value.DatabaseLocation);
    }
}

And in the ConfigureServices in Startup.cs:
services.AddSingleton<ILiteDbContext, LiteDbContext>();

Unfamiliar with the IOptions<LiteDbOptions> options? Read more about the options pattern here

Setting up a service for LiteDb

The webapi project we created contains the WeatherForecast sample which we could use as a sample too. Before we do though, we need to modify the model included; LiteDB needs -as expected- an Id for each model, so we need to add it:

public class WeatherForecast
{
    public int Id { get; set; }//New addition, Id wasn't there 
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    public string Summary { get; set; }
}

In a real life application we should create different models for the database and for all requests but now, for the sake of simplicity, we are going to use WeatherForecast model all over.

Next step would be to create the service that stores and retrieves that model from the database. In LiteDB, besides dropping and renaming a collection (and a few other operations like accessing logs), all operations are done by getting a collection – even creating one! That means that you don’t need to worry about creating the collection, you can just use GetCollection<WeatherForecast>("WeatherForecast") and you are done!
So let’s create a service with a few sample operations and leave the rest to LiteDB.

public class LiteDbWeatherForecastService : ILiteDbWeatherForecastService
{
    private LiteDatabase _liteDb;

    public LiteDbWeatherForecastService(ILiteDbContext liteDbContext)
    {
        _liteDb = liteDbContext.Database;
    }

    public IEnumerable<WeatherForecast> FindAll()
    {
        return _liteDb.GetCollection<WeatherForecast>("WeatherForecast")
            .FindAll();
    }

    public WeatherForecast FindOne(int id)
    {
        return _liteDb.GetCollection<WeatherForecast>("WeatherForecast")
            .Find(x => x.Id == id).FirstOrDefault();
    }

    public bool Insert(WeatherForecast forecast)
    {
        return _liteDb.GetCollection<WeatherForecast>("Api")
            .Insert(forecast);
    }

    public bool Update(WeatherForecast forecast)
    {
        return _liteDb.GetCollection<WeatherForecast>("Api")
            .Update(forecast);
    }

    public int Delete(int id)
    {
        return _liteDb.GetCollection<WeatherForecast>("Api")
            .Delete(x => x.Id == id);
    }
}

And again add it as a service to the IoC container:
services.AddTransient<ILiteDbWeatherForecastService, LiteDbWeatherForecastService>();

Setting up the controller

Having done all the hard work, it is now time to connect our service with the WeatherForecastController. The one included as a sample contains just a hard-coded GET action, so we need to rewrite all of it and support the operations we wrote in the ILiteDbWeatherForecastService:

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly ILogger<WeatherForecastController> _logger;
    private readonly ILiteDbWeatherForecastService _forecastDbService;

    public WeatherForecastController(ILogger<WeatherForecastController> logger, ILiteDbWeatherForecastService forecastDbService)
    {
        _forecastDbService = forecastDbService;
        _logger = logger;
    }

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        return _forecastDbService.FindAll();
    }

    [HttpGet("{id}", Name = "FindOne")]
    public ActionResult<WeatherForecast> Get(int id)
    {
        var result = _forecastDbService.FindOne(id);
        if (result != default)
            return Ok(_forecastDbService.FindOne(id));
        else
            return NotFound();
    }

    [HttpPost]
    public ActionResult<WeatherForecast> Insert(WeatherForecast dto)
    {
        var id = _forecastDbService.Insert(dto);
        if (id != default)
            return CreatedAtAction("FindOne", _forecastDbService.FindOne(id));
        else
            return BadRequest();
    }

    [HttpPut]
    public ActionResult<WeatherForecast> Update(WeatherForecast dto)
    {
        var result = _forecastDbService.Update(dto);
        if (result)
            return NoContent();
        else
            return NotFound();
    }

    [HttpDelete("{id}")]
    public ActionResult<WeatherForecast> Delete(int id)
    {
        var result = _forecastDbService.Delete(id);
        if (result > 0)
            return NoContent();
        else
            return NotFound();
    }
}

The end!

LiteDb is a very easy to use NoSQL database that can be included in small projects very fast. It is also a very nice solution to start playing around with Document DBs so to get familiarize with the different concepts of a relational database.

My playground with LiteDB, including postman calls and the complete API, you can find it in github!

One last thing to conclude this post, would be a way (or two) to check or manipulate data in the database. For this, there is a project called LiteDbExplorer maintained by Josef Nemec that does a pretty decent job, but you could also try the official LiteDB.Studio where you can write SQL-like syntax to check your data.

This page is open source. Noticed a typo? Or something unclear?
Edit Page Create Issue Discuss
Microsoft MVP - George Kosmidis

My next events!

Online event
Online event
Online
Mindspace Salvatorplatz (Salvatorplatz 3, München, BY, Germany 80333)
Online