I’m alive! There are no great things I’ve learned or need to remember for a while, but I decided that collecting the small things is also worthwhile. So, here are several things I learned about the last month.
.ToLookup()
Sometimes, you have a big list of
Things. A
Thing, for example, has the properties
Id,
CountryCode,
Currency,
Population and
Name. Now, we have a list of
Thangs ,which we want to match up with one of the
Things. The naive way of doing this, is searching the list of
Things with a simple
.Where() clause. For example:
var matchedCountry = Things.Where(t => t.CountryCode == thang.CountryCode). Simple, clear, but not very performant. Especially if both lists get very big. So naturally, I would create a
Dictionary<string, Thing> to create a lookup of sorts. This works if the lookup value is unique, but otherwise you would have to create a
Dictionary<string, IEnumerable<Thing>> or something and populating that would be a bit ugly. Fortunatly, I found
a post on StackOverflow by the great John Skeet, which provided the
.ToLookup() functionality. This works great because:
- No ugly loops and checking for existing keys when populating
lookup[key] will return an empty IEnumerable when the key is not present instead of an exception that the key is not present- Natively support multiple
Things per key - Lookup is O(1)
with
I have not been working with
records a lot, but I have been trying to use it more. Especially in situations where a
class is a bit much, but tuples are not enough. Usually, a
record is immutable, but they have a nice copy function where you can (re)set some or all of the properties:
with. It works as follows:
record Contract(string Person, int Salary, string Location, DateTime Start);
var contractTemplate = new Contract
{
Location = "Home",
Start = DateTimeOffset.Now,
}
foreach (var employee in newEmployees)
{
var newContract = contractTemplate with {
Person = employee.Name,
Salary = employee.Salary
};
Console.WriteLine(newContract);
}
This will create a new instance of
Contract, so now there are two instances. Try it!
Console.WriteLine(newContract == contractTemplate); // Output: "False"
This is great when you want to create a large number of records, but with a lot of the same property values.
DbContext in Blazor
I’m still new to Blazor and its lifecycle, so when I had to use a DbContext in a recent Server side Blazor project, I used what I’ve always known:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddDbContext<ApplicationDbContext>(
options => options.UseSqlServer("..."));
}
And in the repository I injected the context as always:
public class MyRepository: IMyRepository
{
private readonly ApplicationDbContext _context;
public MyRepository(ApplicationDbContext context)
{
_context = context;
}
public Task SaveThing(Thing thing)
{
_context.Things.Add(thing);
return _context.SaveChangesAsync();
}
}
This worked for a while, until I had to do two separate database operations on the same page. Suddenly, exception message:
InvalidOperationException: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913my buggy app
My mind was really confused. All dependency injection was correct, I used a pre defined DbContext insertion method so it should be a new one on every operation, right? Well, it turned out I had the MVC pattern still burned in my head. With Blazor, the object lifetime is a bit different. Here, the objects are not created per request (as I expected), but per session. So, once I started browsing the application, the same DbContext was used everywhere. This worked out until I had to do two database actions on the same page. To fix this I had to fix both the injection in the repository as the service registration:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddDbContextFactory<ApplicationDbContext>(
options => options.UseSqlServer("..."));
}
public class MyRepository: IMyRepository
{
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
public MyRepository(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
public async Task SaveThing(Thing thing)
{
using var context = _contextFactory.CreateContext();
context.Things.Add(thing);
await context.SaveChangesAsync();
}
}
And all was well again…