Part 4: Persisting Invoices in a Database with Entity Framework Core

Fábio Salomão
3 min readJun 7, 2024

--

In the previous part, we implemented the billing service that consumes order events from RabbitMQ and generates invoices. Now, we will add persistence for the invoices in a database using Entity Framework Core.

Database Configuration

First, let’s set up Entity Framework Core and the database.

Adding Dependencies

Add the following dependencies to your project:

dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Tools

Configuring the Database Context

Create a folder named Data and add the following BillingDbContext class:

using BillingService.Models;
using Microsoft.EntityFrameworkCore;
namespace BillingService.Data
{
public class BillingDbContext(DbContextOptions<BillingDbContext> options) : DbContext(options)
{
public DbSet<Invoice> Invoices { get; set; }
}
}

Configuring the Database Connection

In the appsettings.json file, add the connection string for the SQLite database:

{
"ConnectionStrings": {
"DefaultConnection": "Data Source=Data/app.db"
}
}

Registering the Context in Startup

In the Program.cs file, register the BillingDbContext and configure the connection string:

using BillingService.Data;
using BillingService.Services;
using Microsoft.EntityFrameworkCore;
using RabbitMQ.Client;

var builder = WebApplication.CreateBuilder(args);

// Configuring the DbContext
builder.Services.AddDbContext<BillingDbContext>(options =>
{
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"));
});
// Configuring RabbitMQ
builder.Services.AddSingleton<IConnection>(sp =>
{
var factory = new ConnectionFactory() { HostName = "localhost" };
return factory.CreateConnection();
});

builder.Services.AddSingleton<IEventSubscriber, RabbitMqEventSubscriber>();

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

// Start the event subscription service
var subscriber = app.Services.GetRequiredService<IEventSubscriber>();
subscriber.Subscribe();

app.Run();

Updating the Billing Service

Let’s update the billing service to save invoices to the database.

Updating the Event Consumer (Subscriber)

Update the RabbitMqEventSubscriber class to use the BillingDbContext:

using BillingService.Data;
using BillingService.Models;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
using System.Text.Json;

namespace BillingService.Services;

public class RabbitMqEventSubscriber(IConnection connection, IServiceScopeFactory scopeFactory) : IEventSubscriber
{
public void Subscribe()
{
using var channel = connection.CreateModel();
channel.QueueDeclare(queue: "orders", durable: false, exclusive: false, autoDelete: false, arguments: null);

var consumer = new EventingBasicConsumer(channel);
consumer.Received += async (model, deliverEventArgs) =>
{
var body = deliverEventArgs.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
var order = JsonSerializer.Deserialize<Order>(message);

if (order == null) return;

// Create a scope to resolve scoped services
using var scope = scopeFactory.CreateScope();
// Create a context instance to interact with the database
var context = scope.ServiceProvider.GetRequiredService<BillingDbContext>();

var invoice = new Invoice
{
Id = Guid.NewGuid(),
OrderId = order.Id,
ProductName = order.ProductName,
Quantity = order.Quantity,
Price = order.Price,
CreatedAt = DateTime.UtcNow
};

context.Invoices.Add(invoice);
await context.SaveChangesAsync();
Console.WriteLine($"Invoice generated: {JsonSerializer.Serialize(invoice)}");
};

channel.BasicConsume(queue: "orders", autoAck: true, consumer: consumer);

Console.WriteLine("Press [enter] to exit.");
Console.ReadLine();
}
}

Updating the Database

Create a migration to update the database with the new invoices table:

  1. Create the Migration:
dotnet ef migrations add InitialCreate

2. Update the Database:

dotnet ef database update

Testing Invoice Persistence

  1. Run the Project: Press F5 to start the project.
  2. Verify Persistence: Use Postman or another tool of your choice to send a POST request to the order service and verify that the corresponding invoice is saved in the database.

--

--

Fábio Salomão

Developer since 2009 with experience in Microsoft .Net and Full Stack skills. React specialist, focused on creating innovative and efficient solutions.