Using EF Core's InMemory Provider To Store A "Database" In Memory

I've had several situations arise recently where I needed a database, but I didn't particularly want to go through the process of creating one on our test servers. On this latest occasion, I started wondering if it was possible to create a database in memory, so that I could just dispose of it when I no longer needed it.

Turns out, it IS possible! A bit of googling later and I was smirking like a squirrel that stole your peanuts.

I mean, did you see that guy? He wasn't even looking! What a chump. [1]

We're going to use Entity Framework Core's InMemory Provider to create a working "database" in memory, which we will then use in a sample Core MVC application. I've also created a sample ASP.NET Core MVC app which shows how we could use this in-memory "database" in a real application.  

Let's get started!

DbContext and Data Generation

First off, we need to write a DBContext (which allows us to query the datastore) and a data generator which will create some sample data.  Here's the code for our DbContext:

public class BoardGamesDBContext : DbContext
{
    public BoardGamesDBContext(DbContextOptions<BoardGamesDBContext> options)
        : base(options)
    {
    }

    public DbSet<Models.BoardGame> BoardGames { get; set; }
}

Because this database will be stored in memory, we require some sample data with which the database can be populated.  But there's a problem: how do we get said data into our memory "database", and when?

The simplest way to create sample data for our memory "database" is to inject the data into said database on application startup.  To do that, our new class (which I called DataGenerator) will need to search the Services layer for the context and use it to insert new board games into our "database". Here's how we do that:

public class DataGenerator
{

    public static void Initialize(IServiceProvider serviceProvider)
    {
        using (var context = new BoardGamesDBContext(
            serviceProvider.GetRequiredService<DbContextOptions<BoardGamesDBContext>>()))
        {
            // Look for any board games.
            if (context.BoardGames.Any())
            {
                return;   // Data was already seeded
            }

            context.BoardGames.AddRange(
                new BoardGame
                {
                    ID = 1,
                    Title = "Candy Land",
                    PublishingCompany = "Hasbro",
                    MinPlayers = 2,
                    MaxPlayers = 4
                },
                new BoardGame
                {
                    ID = 2,
                    Title = "Sorry!",
                    PublishingCompany = "Hasbro",
                    MinPlayers = 2,
                    MaxPlayers = 4
                },
                new BoardGame
                {
                    ID = 3,
                    Title = "Ticket to Ride",
                    PublishingCompany = "Days of Wonder",
                    MinPlayers = 2,
                    MaxPlayers = 5
                },
                new BoardGame
                {
                    ID = 4,
                    Title = "The Settlers of Catan (Expanded)",
                    PublishingCompany = "Catan Studio",
                    MinPlayers = 2,
                    MaxPlayers = 6
                },
                new BoardGame
                {
                    ID = 5,
                    Title = "Carcasonne",
                    PublishingCompany = "Z-Man Games",
                    MinPlayers = 2,
                    MaxPlayers = 5
                },
                new BoardGame
                {
                    ID = 6,
                    Title = "Sequence",
                    PublishingCompany = "Jax Games",
                    MinPlayers = 2,
                    MaxPlayers = 6
                });

            context.SaveChanges();
        }
    }
}

NOTE: For those of you who might be interested, the games we are adding to our database are Candy Land, Sorry!, Ticket to Ride, Settlers of Catan (5th Edition), Carcassonne, and Sequence.  You can buy each of these from Amazon (those are affiliate links), and I've played every one of them.

Now that we have a context through which we can access data, and a class which will generate some sample data, we need to wire them into our ASP.NET Core applications's Startup.cs and Program.cs files to get our data populated.

Startup.cs and Program.cs

First, in our Startup.cs file, we will add the BoardGamesDBContext into our Services layer, like so:

public class Startup
{
    ...

    public void ConfigureServices(IServiceCollection services)
    {
        ...

        services.AddDbContext<BoardGamesDBContext>(options => options.UseInMemoryDatabase(databaseName: "BoardGames"));
    }
}

We now need to make some modifications to Program.cs to get our data loaded into memory at application startup.  We need to:

  1. Find the instance of IWebHost that will run the application.
  2. Find the services layer in that host.
  3. Find the instance of BoardGamesDBContext in that layer.
  4. Call DataGenerator to initialize our sample data.

All that together makes the Program.cs file look like this:

public class Program
{
    public static void Main(string[] args)
    {
        //1. Get the IWebHost which will host this application.
        var host = CreateWebHostBuilder(args).Build();

        //2. Find the service layer within our scope.
        using (var scope = host.Services.CreateScope())
        {
            //3. Get the instance of BoardGamesDBContext in our services layer
            var services = scope.ServiceProvider;
            var context = services.GetRequiredService<BoardGamesDBContext>();

            //4. Call the DataGenerator to create sample data
            DataGenerator.Initialize(services);
        }

        //Continue to run the application
        host.Run();
    }
}

With this step, our data is now loaded into our memory "database" and ready for use in an application!

Building an Application

As an example of how we could use this data in a "real" application, here's a condensed version of the BoardGameController class in the sample project on GitHub.  

public class BoardGameController : Controller
{
    //We inject the DBContext into the controller...
    private BoardGamesDBContext _context;

    public BoardGameController(BoardGamesDBContext context)
    {
        _context = context;
    }

    //...and can access it in our actions.
    [HttpGet]
    public IActionResult Index()
    {
        var games = _context.BoardGames.ToList();
        return View(games);
    }
    
    ...

    [HttpPost]
    public IActionResult Add(BoardGame game)
    {
        //Determine the next ID
        var newID = _context.BoardGames.Select(x => x.ID).Max() + 1;
        game.ID = newID;

        _context.BoardGames.Add(game);
        _context.SaveChanges();
        return RedirectToAction("Index");
    }

    ...

    [HttpGet]
    public IActionResult Edit(int id)
    {
        var game = _context.BoardGames.Find(id);
        return View(game);
    }

    [HttpPost]
    public IActionResult Edit(BoardGame game)
    {
        _context.BoardGames.Update(game);
        _context.SaveChanges();

        return RedirectToAction("Index");
    }
}

Applications of This Technique

Though our sample app uses an in-memory "database" to provide a datastore to a "real" MVC application, the most common real-world use case for the InMemory provider would be likely be testing.  

In such a test scenario, what we might do is create a set of tests (after raging about it for a bit) which use this in-memory, fully-controlled, known-good set of data to test the code, rather than being bound to an out-of-our-control, probably-good-but-also-possibly-squirrels external database.  This gets us better tests, which can be run without relying on any external inputs or sources.

For a full example of how you might use the InMemory provider to do testing, check out the article "Testing with InMemory" on the ASP.NET site.

Important Caveats

There's a couple of important things to remember when creating a "database" using the InMemory provider.  

First, and most importantly, remember to check for squirrels.

Mmmmm I love these nuts that I found WAIT WTF IS THAT?!

Once that's done, it's important to remember that the thing that's created when using the InMemory provider isn't a relational database. It provides no relational checking; it will allow you to save things that violate referential integrity; it doesn't support concurrency; it can only loosely be considered a "database" at all (hence why I put that word in quotes throughout this post).  It does make it very easy to create and store test data, but at these costs.

Second, and hopefully this goes without saying, but this database is incredibly volatile.  It's only as good as the memory it runs it.  Do not ever use the InMemory database provider for production application datastores.

If those caveats aren't a problem for you, you're good to go!

Summary

Entity Framework Core's InMemory provider allows us to create a "database" in memory that our applications can use just like a normal datastore.  In our sample, we use it as the backing store to an MVC application, but in the real-world we'd more likely use it in test cases.

Don't forget to check out the sample project on GitHub if you'd like to see the full working MVC app.  Also, watch out for squirrels.

The demon squirrel is watching you, always watching you...[2]

Happy Coding!

[1] Image from Flickr, used under license
[2] Image from Wikimedia, used under license

Using EF Core's InMemory Provider To Store A "Database" In Memory
Share this