I. Introduction
Have you been in a situation where you need to test a new feature with your web application without affecting the production database? Or maybe a fast prototype without setting up a whole new database.
And in this situation, you are thinking about an in-memory database, and you are right.
Didn’t you know that Entity Framework Core can help us with this situation without directly interacting with the underlying database provider?
This is where the EF Core In-Memory Database Provider comes into the picture, and where an in-memory database comes into play.
II. Chances That You Need to Use an In-Memory Database
Here are some scenarios where you need to use EF Core In-Memory Database.
Integration Testing
If you are into integration testing, you can use EF Core In-Memory Database.
Instead of mocking your repositories, you can use this EF Core In-Memory Database for a lightweight integration test.
Quick Demo
For a quick demo, validating an idea, or creating a fast spike solution, we can use in-memory to move fast without worrying about the underlying database setup.
Temporary Data Storage
If you don’t need persistence, it resets data on restart; thus, in-memory is enough for temporary data storage.
III. What is EF Core In-Memory Database Provider?
As we all know, Entity Framework Core helps developers store and retrieve data, but it also includes an in-memory database.
A database that resides in volatile memory instead of a physical disk.
To use this, we need to install Microsoft.EntityFrameworkCore.In-Memory NuGet package.
Moreover, this in-memory database is a quick and easy way to test your ASP.NET Core web applications, without the overhead of actual database operations.
IV. How to Use an In-Memory Provider?
Let’s try to do this step by step.
But before we start, please create a new Web API project, then follow the steps below.
First thing is to install Microsoft.EntityFrameworkCore.InMemory.
Or you can take a look at our project file (.csproj) for the package reference.
<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.25" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
Just a note here: we also use AutoMapper for entity-to-model mapping later.
Second, let’s create our entities.
namespace EFCoreInMemoryDbSamp.Entities;
public class Author
{
public Guid Id { get; set; }
public string FirstName { get; set; } = null!;
public string LastName { get; set; } = null!;
public DateTime BirthDate { get; set; }
public ICollection<Book> Books { get; set; } = new List<Book>();
}
namespace EFCoreInMemoryDbSamp.Entities;
public class Book
{
public Book(string title)
{
this.Title = title;
}
public Guid Id { get; set; }
public string Title { get; set; } = string.Empty;
public string? Description { get; set; }
public Guid AuthorId { get; set; }
public Author? Author { get; set; }
}
Third, now that we have our Entities, let's try to create our DbContext.
using EFCoreInMemoryDbSamp.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreInMemoryDbSamp.Context;
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
public DbSet<Author> Authors { get; set; }
public DbSet<Book> Books { get; set; }
override protected void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>()
.HasOne(b => b.Author)
.WithMany(a => a.Books)
.HasForeignKey(b => b.AuthorId);
base.OnModelCreating(modelBuilder);
}
}
Fourth, we need a repository to obtain the authors’ information.
using EFCoreInMemoryDbSamp.Entities;
namespace EFCoreInMemoryDbSamp.Services;
public interface IAuthorRepository
{
Task<IEnumerable<Author>> GetAllAuthorsAsync();
Task<Author?> GetAuthorByIdAsync(Guid id);
Task AddAuthorAsync(Author author);
Task DeleteAuthorAsync(Guid id);
}
Now, let’s look at the implementation of the repository interface.
using EFCoreInMemoryDbSamp.Context;
using EFCoreInMemoryDbSamp.Entities;
using Microsoft.EntityFrameworkCore;
namespace EFCoreInMemoryDbSamp.Services;
public class AuthorRepository : IAuthorRepository
{
private MyDbContext _context;
public AuthorRepository(MyDbContext context)
{
this._context = context;
}
public async Task AddAuthorAsync(Author author)
{
await this._context.Authors.AddAsync(author);
await this._context.SaveChangesAsync();
}
public async Task DeleteAuthorAsync(Guid id)
{
var author = await this._context.Authors.SingleOrDefaultAsync(a => a.Id == id);
if (author != null)
{
this._context.Authors.Remove(author);
await this._context.SaveChangesAsync();
}
}
public async Task<IEnumerable<Author>> GetAllAuthorsAsync()
{
return await this._context.Authors.Include(item => item.Books).ToListAsync();
}
public async Task<Author?> GetAuthorByIdAsync(Guid id)
{
Author author = new Author();
var result = await this._context.Authors..Include(item => item.Books).SingleOrDefaultAsync(a => a.Id == id);
if (result != null)
{
author = result;
}
return author;
}
}
Fifth, let’s create a model and an AutoMapperprofile to use in our controller.
namespace EFCoreInMemoryDbSamp.Model;
public class Author
{
public Guid Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public List<Book> Books { get; set; }
}
namespace EFCoreInMemoryDbSamp.Model;
public class Book
{
public Guid Id { get; set; }
public string Title { get; set; } = string.Empty;
public string? Description { get; set; }
}
using AutoMapper;
namespace EFCoreInMemoryDbSamp.Model;
public class AuthorProfile : Profile
{
public AuthorProfile()
{
CreateMap<Entities.Author, Author>();
}
}
using AutoMapper;
namespace EFCoreInMemoryDbSamp.Model
{
public class BookProfile : Profile
{
public BookProfile()
{
CreateMap<Entities.Book, Model.Book>();
}
}
}
Sixth, we need a controller; in this, we created an AuthorController.
using AutoMapper;
using EFCoreInMemoryDbSamp.Model;
using EFCoreInMemoryDbSamp.Services;
using Microsoft.AspNetCore.Mvc;
namespace EFCoreInMemoryDbSamp.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthorController : ControllerBase
{
private readonly IAuthorRepository _authorsRepository;
private readonly IMapper _mapper;
public AuthorController(IAuthorRepository authorRepository, IMapper mapper)
{
this._authorsRepository = authorRepository;
this._mapper = mapper;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<Author>>> GetAuthors()
{
var authorsFromRepo = await _authorsRepository.GetAllAuthorsAsync();
return Ok(_mapper.Map<IEnumerable<Author>>(authorsFromRepo));
}
[HttpGet("{authorId}")]
public async Task<ActionResult<Author>> GetAuthor(
Guid authorId)
{
var authorFromRepo = await _authorsRepository.GetAuthorByIdAsync(authorId);
if (authorFromRepo == null)
{
return NotFound();
}
return Ok(_mapper.Map<Author>(authorFromRepo));
}
}
}
Seventh, we need to form data, so we create a database initializer.
using EFCoreInMemoryDbSamp.Entities;
namespace EFCoreInMemoryDbSamp.Context
{
public static class DbInitializer
{
public static void Seed(MyDbContext context)
{
var authors = new List<Author>
{
new Author()
{
Id = Guid.Parse("d28888e9-2ba9-473a-a40f-e38cb54f9b35"),
FirstName = "George",
LastName = "RR Martin",
Books = new List<Book>()
{
new Book("A Dance with Dragons")
{
Id = Guid.Parse("5b1c2b4d-48c7-402a-80c3-cc796ad49c6b"),
AuthorId = Guid.Parse("d28888e9-2ba9-473a-a40f-e38cb54f9b35"),
Description = "A Dance with Dragons is the fifth of seven planned novels in the epic fantasy series A Song of Ice and Fire."
},
new Book("A Game of Thrones")
{
Id = Guid.Parse("d8663e5e-7494-4f81-8739-6e0de1bea7ee"),
AuthorId = Guid.Parse("d28888e9-2ba9-473a-a40f-e38cb54f9b35"),
Description = "A Game of Thrones is the first novel in A Song of Ice and Fire, a series of fantasy novels by American author George R. R. ... In the novel, recounting events from various points of view, Martin introduces the plot-lines of the noble houses of Westeros, the Wall, and the Targaryens."
}
}
},
new Author()
{
Id = Guid.Parse("da2fd609-d754-4feb-8acd-c4f9ff13ba96"),
FirstName = "Stephen",
LastName = "Fry",
Books = new List<Book>()
{
new Book("Mythos")
{
Id = Guid.Parse("d173e20d-159e-4127-9ce9-b0ac2564ad97"),
AuthorId = Guid.Parse("da2fd609-d754-4feb-8acd-c4f9ff13ba96"),
Description = "The Greek myths are amongst the best stories ever told, passed down through millennia and inspiring writers and artists as varied as Shakespeare, Michelangelo, James Joyce and Walt Disney. They are embedded deeply in the traditions, tales and cultural DNA of the West.You'll fall in love with Zeus, marvel at the birth of Athena, wince at Cronus and Gaia's revenge on Ouranos, weep with King Midas and hunt with the beautiful and ferocious Artemis. Spellbinding, informative and moving, Stephen Fry's Mythos perfectly captures these stories for the modern age - in all their rich and deeply human relevance."
}
}
},
new Author()
{
Id = Guid.Parse("24810dfc-2d94-4cc7-aab5-cdf98b83f0c9"),
FirstName = "James",
LastName = "Elroy",
Books = new List<Book>()
{
new Book("American Tabloid")
{
Id = Guid.Parse("493c3228-3444-4a49-9cc0-e8532edc59b2"),
AuthorId = Guid.Parse("24810dfc-2d94-4cc7-aab5-cdf98b83f0c9"),
Description = "American Tabloid is a 1995 novel by James Ellroy that chronicles the events surrounding three rogue American law enforcement officers from November 22, 1958 through November 22, 1963. Each becomes entangled in a web of interconnecting associations between the FBI, the CIA, and the mafia, which eventually leads to their collective involvement in the John F. Kennedy assassination."
}
}
},
new Author()
{
Id = Guid.Parse("2902b665-1190-4c70-9915-b9c2d7680450"),
FirstName = "Douglas",
LastName = "Adams",
Books = new List<Book>()
{
new Book("The Hitchhiker's Guide to the Galaxy")
{
Id = Guid.Parse("40ff5488-fdab-45b5-bc3a-14302d59869a"),
AuthorId = Guid.Parse("2902b665-1190-4c70-9915-b9c2d7680450"),
Description = "In The Hitchhiker's Guide to the Galaxy, the characters visit the legendary planet Magrathea, home to the now-collapsed planet-building industry, and meet Slartibartfast, a planetary coastline designer who was responsible for the fjords of Norway. Through archival recordings, he relates the story of a race of hyper-intelligent pan-dimensional beings who built a computer named Deep Thought to calculate the Answer to the Ultimate Question of Life, the Universe, and Everything."
}
}
}
};
if (!context.Authors.Any())
{
context.Authors.AddRange(authors);
context.SaveChanges();
}
}
public static void StartSeed(this WebApplication app)
{
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
DbInitializer.Seed(context);
}
}
}
}
Now, for our last step, go to Program.cs file and check the differences between your Program.cs file and add the related methods from our previous code samples.
using EFCoreInMemoryDbSamp.Context;
using EFCoreInMemoryDbSamp.Model;
using EFCoreInMemoryDbSamp.Services;
using Microsoft.EntityFrameworkCore;
using System.Text.Json.Serialization;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//we need to add authorrepository
builder.Services.AddScoped<IAuthorRepository, AuthorRepository>();
//we need to set the EF to use In Memory Database
builder.Services.AddDbContextFactory<MyDbContext>(options =>
{
options.UseInMemoryDatabase("MyInMemory");
});
//we need to add automapper profiles
builder.Services.AddAutoMapper(typeof(AuthorProfile), typeof(BookProfile));
var app = builder.Build();
//we need some data when the application has started.
app.StartSeed();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
V. Limitations
Wow! We have seen how to use the In-Memory in EF Core, but please be aware that it has several limitations.
- It will allow you to save data, but remember it is not a relational database, so no foreign key constraints, no joins enforced like SQL, so no relational behaviors.
- It will allow executing queries in memory using LINQ-to-Objects, not SQL.
- Transactions are either ignored or not truly enforced.
The simple rule of thumb is that EF In Memory is a fake database, like a mock, while SQL Server is the production truth.
VI. Summary
In this post, we have seen that setting up EF Core with an in-memory database is straightforward.
As we discussed, this can significantly enhance your testing capabilities by providing a quick, easy way to simulate database operations without the overhead of a full database setup.
This setup is ideal for unit tests or integration tests, quick demos, and temporary data storage, allowing you to test your data access code in isolation.
From there, a code sample was shown on how to get started with this kind of setup, and I hope you enjoyed it.
Let me know if you have any questions about the code sample in the comment section below.
If you want the full codebase, you can find it here on GitHub.
Stay tuned for more (keep visiting our blog and share this with your friends, colleagues, and network).
Until next time, happy programming!
Please don’t forget to bookmark, like, and comment. Cheers! And Thank you!
VII. References
• https://entityframeworkcore.com/providers-inmemory
• https://rmauro.dev/set-up-entity-framework-core-in-memory-store/
• https://learn.microsoft.com/en-us/ef/core/providers/in-memory/?tabs=dotnet-core-cli
• https://www.infoworld.com/article/2336587/how-to-use-ef-core-as-an-in-memory-database-in-asp-net-core-6.html

Top comments (0)