Allows to use StrEnum string enums with Entity Framework Core.
Supports EF Core 3.1–7.0
You can install StrEnum.EntityFrameworkCore using the .NET CLI:
dotnet add package StrEnum.EntityFrameworkCore
Define a string enum and an entity that uses it:
public class Sport: StringEnum<Sport>
{
public static readonly Sport RoadCycling = Define("ROAD_CYCLING");
public static readonly Sport MountainBiking = Define("MTB");
public static readonly Sport TrailRunning = Define("TRAIL_RUNNING");
}
public class Race
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public Sport Sport { get; private set; }
private Race()
{
}
public Race(string name, Sport sport)
{
Id = Guid.NewGuid();
Name = name;
Sport = sport;
}
}
And call the UseStringEnums()
method when configuring your DB context:
public class RaceContext: DbContext
{
public DbSet<Race> Races { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=.;Database=BestRaces;user id=*;pwd=*;")
.UseStringEnums();
}
}
That's it! EF Core is now able to deal with string enums.
EF Core will store string enums in non-nullable string columns (NVARCHAR(MAX)
in SQL Server, TEXT
in Postgres).
Running dotnet ef migrations add Init
will produce the following migration:
migrationBuilder.CreateTable(
name: "Races",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
Sport = table.Column<string>(type: "nvarchar(max)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Races", x => x.Id);
});
In order to store a nullable string enum, mark the property is non-required when configuring your entity:
var race = builder.Entity<Race>();
race.Property(p => p.Sport).IsRequired(false);
EF Core will translate LINQ operations on string enums into SQL.
Let's add some races first:
var context = new RaceContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
var races = new[]
{
new Race("Chornohora Sky Marathon", Sport.TrailRunning),
new Race("Cape Town Cycle Tour", Sport.RoadCycling),
new Race("Cape Epic", Sport.MountainBiking)
};
await context.Races.AddRangeAsync(races);
await context.SaveChangesAsync();
And filter by a single Sport:
var trailRuns = await context.Races.Where(o => o.Sport == Sport.TrailRunning).ToArrayAsync();
That will produce the following SQL:
SELECT [r].[Id], [r].[Name], [r].[Sport]
FROM [Races] AS [r]
WHERE [r].[Sport] = N'TRAIL_RUNNING'
You can also query by multiple Sport values:
var cyclingSport = new[] { Sport.MountainBiking, Sport.RoadCycling };
var racesThatRequireABicycle = await context.Races.Where(o => cyclingSport.Contains(o.Sport)).ToArrayAsync();
Which will translate to the following SQL:
SELECT [r].[Id], [r].[Name], [r].[Sport]
FROM [Races] AS [r]
WHERE [r].[Sport] IN (N'MTB', N'ROAD_CYCLING')
Thanks to Andrew Lock for his research on using custom ValueConverterSelector
.
Copyright © 2022 Dmitry Khmara.
StrEnum is licensed under the MIT license.