Entity Framework Core in Blazor – Dev Leader Weekly 30

Welcome to Dev Leader Weekly!

I’m wrapping up my second Dometrain course and about to start on my third this weekend. Dometrain will remain the platform that I launch my paid-for courses on because I believe in the DotNet course ecosystem that Nick Chapsas is building. Check out my first course:

Are you interested in boosting your refactoring skills? Check out my course on Dometrain:
Refactoring for C# Devs: From Zero to HeroCourses - Dometrain - Refactoring for csharp devs - wide

And in other big news… Another dotUltimate 1 year license giveaway! This draw will end on March 1st 2024. You earn the most entries by referring your friends to the draw:

Promo - JetBrains - dotUltimate License

If you enjoy my content, you’ll love premium membership access to my site! This includes:

Let’s dive in!


What’s In This Issue


Entity Framework in Blazor – Next Steps in Our Metrics App!

Why Entity Framework?

I made a conscious decision to use EF Core for this project because I wanted to focus on learning. I am aware of Entity Framework, but I have been writing my own SQL queries by hand now for years. I do it because I am comfortable with it, and once upon a time, EF Core was slow to use. But times have changed, and as EF Core has been improved, I’m now being left in the dust a little bit.

Part of doing this Blazor build series was focusing on learning in public. I wanted to demonstrate to you how I approach building systems and how I approach learning things. While I probably wouldn’t be taking on so many unknowns when building for a customer, this experience is all about learning in general.

Blazor and Entity Framework are two such things I need more experience in — and I hope that you can learn alongside me. And guess what? That means I *will* make mistakes, and we will come back and revisit them later! You can also watch this video alongside this article:

YouTube player

Getting Setup with Entity Framework

I made the decision that, for now, I’d be going with a SQLite implementation of EF Core. And if you’re asking why: Simplicity. I don’t want to have to think about running a SQL server instance currently, and thanks to EF Core, I should easily be able to swap the backend at a later point.

I needed to get these two packages added into our project:

<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />

And from here, it was time to figure out how I wanted to design the schema. We don’t have much going on for now, so let’s see what that looks like:

EF Core Schema for Social Metrics

I had a few things on my mind when it came to tracking what I thought would be important. Firstly, we know that we’re dealing with different social platforms… But I thought it would be important to call out which account on that platform as well. That’s because, for example, I might have a LinkedIn account but also a LinkedIn business page. So I want to track the account ID as well.

I figured the the timestamp would be important when we go to plot this information (or perhaps need to filter it down in our user interface). But the other part that was interesting was which metrics we’re dealing with. I had shared in earlier discussions that we’d have followers and following counts… But is that sufficient? Do I want to limit our entity to those two things? I decided I’d go with a metric name and a metric value to see if we will extend this as we move forward. This flexibility might bite us with complexity (typos in the string, consistency, etc…) but it might also allow us to easily extend to more metrics.

public sealed record SocialMetric(
    string PlatformId,
    string AccountId,
    DateTime TimestampUtc,
    string MetricName,
    int MetricValue)
{
    public int SocialMetricId { get; init; }
}

But one more funny thing here is that the ID for the entity will be auto-incrementing. That means that to construct one of these, I want to use normal record syntax (i.e. set it all in the constructor), but the ID is technically handled by EF Core and not us directly. That’s why I pulled it out separately.

Wiring up our DB Context

This is where things got a bit weird. I know EF Core has some patterns for DB Context Factory and we shouldn’t just keep DB Contexts held open for long periods of time… So I tried to design with this in mind. But truth be told, I can’t stand the use of inheritance with a DB Context. It drives me nuts to have to extend classes like this. And when we slap on the tables that we can access as properties for our different entities, those properties need to have setters! I’m not okay with passing that kind of object around — but there’s probably something I’m missing.

Here’s what I came up with:

// primary constructor with our database path (because this is a SQLite implementation)
// and we have a get/set DbSet property (needs to have the setter for magical EF configuration)
// this implements the base DbContext and our custom interface
public sealed class MetricsSqliteDbContext(
    string _databasePath) : 
    DbContext,
    IMetricsDbContext
{
    public DbSet<SocialMetric> SocialMetrics { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlite($"Data Source={_databasePath}");
}

// this gives us a scoped down API that's *EXACTLY* what I am comfortable passing around
// - dealing with a DbSet of our record (note the property is get-only on the interface)
// - an API to save the changes
// if I need to expose more later, I can do it as needed vs full control to everyone
public interface IMetricsDbContext : IDisposable
{
    DbSet<SocialMetric> SocialMetrics { get; }

    Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}

This class and interface give me something like a “repository” that I am happy to work with across the app. But I needed a factory to create this thing and only expose the interface to work with:

public sealed class MetricsDbContextFactory
{
    private readonly string _databasePath;
    private readonly Lazy<bool> _doOnce;

    public MetricsDbContextFactory(string databasePath)
    {
        _databasePath = databasePath;
        _doOnce = new Lazy<bool>(() =>
        {
            using var dbContext = Create(databasePath);
            dbContext.Database.EnsureCreated();
            return true;
        });
    }

    public IMetricsDbContext CreateDbContext()
    {
        // lazily run our initialization code!
        _ = _doOnce.Value;
        var dbContext = Create(_databasePath);
        return dbContext;
    }

    private MetricsSqliteDbContext Create(
        string databasePath) =>
        new MetricsSqliteDbContext(databasePath);
}

This factory is a bit weird, but it’s probably mostly because I have a lazy method that will ensure we construct the database ONCE just-in-time when we go to create a DB Context. And nothing enforces the “singleton-ness” here except for when I hook it up on our dependency injection:

builder.Services.AddSingleton(x => new MetricsDbContextFactory(
    "socialmetrics.db"));

Write Our Metrics Out to EF Core!

We now had to go make changes to the fetching job class to accept in the DB Context Factory, which meant the weird Quartz job parameter passing (not via the constructor). So the job creation code was also changed to pass it in, but here’s how we consume it:

var dbContextFactory = (MetricsDbContextFactory)context.MergedJobDataMap[nameof(MetricsDbContextFactory)];
Debug.Assert(dbContextFactory != null, nameof(dbContextFactory) + " != null");

// and the job creation part has this kind of code now:
.SetJobData(new JobDataMap
{
    [nameof(ISocialDataFetcher)] = socialJobProvider.DataFetcher,
    [nameof(MetricsDbContextFactory)] = metricsDbContextFactory,
})

And since we aren’t truly fetching data *yet*, we can write out some dummy data for now:

SocialMetric metric = new(
    PlatformId: "The Platform",
    AccountId: "The Account ID",
    TimestampUtc: DateTime.UtcNow,
    MetricName: "Followers",
    MetricValue: 123);

using var dbContext = dbContextFactory.CreateDbContext();
await dbContext.SocialMetrics
    .AddAsync(metric, context.CancellationToken)
    .ConfigureAwait(false);
await dbContext
    .SaveChangesAsync(context.CancellationToken)
    .ConfigureAwait(false);

But this poses some questions:

  • How do we get these values from our social data fetcher and/or the plugin?
  • Do we need to update our SocialStats DTO now to provide this kind of data back?

It seems like we need to change our fetching API to accommodate or we need to provide some additional details from the plugin that we can resolve this stuff.

What’s Next for Our Blazor Build?

I can’t predict exactly what the next step needs to be or should be… But I personally think it would be cool to get this sucker wired up to fetch REAL data and write real data into our database! At this point, we have a Twitter plugin that *should* fetch data if we give it the right credentials and we have a REAL database that we can write to. So if we update the pieces I mentioned in the last section to glue these parts together, we should in theory have our first working plugin end to end!

What do you think? What would you like to see?


Epic Fail or Promising Attempt – Auto Pipeline Config in C#

YouTube player

The pipeline design pattern is an awesome way for us as C# developers to be able to process data. We can configure stages to wire up and pass data from one to the next. However, one of the challenges is setting this design pattern up! Nobody wants to configure it by hand… Will this thought experiment lead to an easier way for us to configure pipelines in dotnet?

NAILED IT! – ASP.NET Core Blazor Plugin API For Quartz

YouTube player

In our ASP.NET Core Blazor build series, we hit a bit of a barrier with our Blazor plugin API not being fully compatible with Quartz. How we’d like to schedule our dotnet jobs means that we need to revisit our plugin API to better fit with Quartz. Join me as we explore building our ASP.NET Core Blazor web app and experiment with integrating Quartz .NET for job scheduling!

LINQ in C# – Being Lazy with Mapping, Reducing, and Filtering

YouTube player

Have you been using LINQ in C#? You’ve probably come across using LINQ but perhaps didn’t understand some of the use cases for it, such as mapping, filtering, and reducing. You may not have even realized it’s… lazy! In this video, I’ll explain LINQ for beginners so that you can use it more effectively. You’ll even be able to write your own LINQ for IEnumerables in CSharp!

Unit of Work Pattern in C# for Clean Architecture: What You Need To Know

Unit of Work Pattern in C# for Clean Architecture: What You Need To Know

Integrate the Unit of Work Pattern in C# with Clean Architecture for efficient programming. Learn benefits and check out C# code examples to avoid mistakes.

C# Clean Architecture with MediatR: How To Build For Flexibility

C# Clean Architecture With MediatR - How To Build For Flexibility

Explore the integration of C# Clean Architecture with MediatR for maintainable, scalable code! Learn how these work together with C# code examples!

CQRS Pattern in C# and Clean Architecture – A Simplified Beginner’s Guide

CQRS Pattern in C# and Clean Architecture - A Simplified Beginner's Guide

Learn about the CQRS pattern in C# and Clean Architecture for better software development. Get code examples and best practices to improve your skills today!

WebApplicationFactory in ASP.NET Core: Practical Tips for C# Developers

WebApplicationFactory in ASP.NET Core - Practical Tips for C# Developers

Learn about WebApplicationFactory in ASP.NET Core and leveraging it for testing. Use an HttpClient and implement the tips in this article for better tests!


As always, thanks so much for your support! I hope you enjoyed this issue, and I’ll see you next week.

​Nick “Dev Leader” Cosentino
[email protected]

Socials:
Blog
Dev Leader YouTube
Follow on LinkedIn
Dev Leader Instagram

P.S. If you enjoyed this newsletter, consider sharing it with your fellow developers. Let’s grow our community together!

author avatar
Nick Cosentino Principal Software Engineering Manager
Principal Software Engineering Manager at Microsoft. Views are my own.