post-thumb

How to configure EntityFramework with migrations - tutorial

When I start a new project in .NET and I need to persist data, EntityFramework is my first choice. Why? It is the most common approach in .NET world and it allows to start application easily. In this article I will guide you through configuring EntityFramework with migrations including some tricks and good practices.

DataModel and its configuration

The first thing you need to do is creating a data model to store in the database. I have prepared Article class to store it:

public class Article
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public string Url { get; set; }
}

Data model is not enough. You need to add also a bit of metadata to it. You can use attributes DataAttributes or FluentConfiguration approach. I am the of the second option, because it is much cleaner and there are more capabilities. It can be like:

public class ArticleConfiguration : IEntityTypeConfiguration<Article>
{
    public void Configure(EntityTypeBuilder<Article> builder)
    {
        builder.HasKey(x => x.Id);
        builder.Property(x => x.Title).HasMaxLength(500);
        builder.Property(x=> x.Content).HasMaxLength(-1);
        builder.Property(x => x.Url).HasMaxLength(200);
        builder.HasIndex(x => x.Url).IsUnique();
    }
}

With FluentConfiguration you have to implement IEntityTypeConfiguration<T> and write Configure(EntityTypeBuilder<T> builder) method. What you can see here:

  • Setting Id as primary key
  • Setting columns lengths
  • Adding unique index on Url column

DbContext

Next thing you need to do is creating your own DbContext. In our case it is:

using Microsoft.EntityFrameworkCore;

namespace CodePruner.TestContainerExamples.EF;

public class CodePrunerDbContext : DbContext
{
    public CodePrunerDbContext(DbContextOptions<CodePrunerDbContext> options) : base(options)
    { }
    public DbSet<Article> Articles { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
    }
    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.ApplyConfiguration(new ArticleConfiguration());
        base.OnModelCreating(builder);
    }
}

There are some facts:

  1. You need to inherit from DbContext
  2. Constructor should accept DbContextOptions<CodePrunerDbContext> options. It is required to:
    • Pass ConnectionString later
    • Setting database type, like SqlServer, Postgres, SQLite
  3. Add DbSet<Article> Articles. Sometimes it can be omitted, but I suggest to add it by default, because it makes working with EF nicer
  4. In OnModelCreating I have applied the configuration.
    • You can do something like: builder.ApplyConfigurationsFromAssembly(typeof(CodePrunerDbContext).Assembly) to avoid adding configuration in the future. They will be loaded automatically.

Adding migration

When you have all of the previous steps done you are almost ready to add the migration. Before you do it I recommend to add context factory to simplify adding migrations, because dotnet will know how to construct the DbContext.

using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore;

namespace CodePruner.TestContainerExamples.EF;

public class CodePrunerDbContextFactory : IDesignTimeDbContextFactory<CodePrunerDbContext>
{
    public CodePrunerDbContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder<CodePrunerDbContext>();
        optionsBuilder.UseSqlServer();
        return new CodePrunerDbContext(optionsBuilder.Options);
    }
}

Ok. Now you are ready to add the first migration. To do it, go to the directory with your EF project and add migration:

cd .\CodePruner.TestContainerExamples.EF
dotnet ef migrations add AddArticle

After it migration will be created. If you have any problem with that, let me know in the comments sections below. The migration will create couple of files:

  • CodePrunerDbContextModelSnapshot.cs - It is metadata information for EntityFramework to know how to create next migrations. eg. which table exists and should be deleted or altered.
  • 20240903064641_AddArticle - It is a single migration. These numbers at the beginning represent date when migration was created. It is important to keep them in order. It should look like:
    using System;
    using Microsoft.EntityFrameworkCore.Migrations;
    
    #nullable disable
    
    namespace CodePruner.TestContainerExamples.EF.Migrations
    {
        /// <inheritdoc />
        public partial class AddArticle : Migration
        {
            /// <inheritdoc />
            protected override void Up(MigrationBuilder migrationBuilder)
            {
                migrationBuilder.CreateTable(
                    name: "Articles",
                    columns: table => new
                    {
                        Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
                        Title = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
                        Content = table.Column<string>(type: "nvarchar(max)", maxLength: -1, nullable: false),
                        Url = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false)
                    },
                    constraints: table =>
                    {
                        table.PrimaryKey("PK_Articles", x => x.Id);
                    });
    
                migrationBuilder.CreateIndex(
                    name: "IX_Articles_Url",
                    table: "Articles",
                    column: "Url",
                    unique: true);
            }
    
            /// <inheritdoc />
            protected override void Down(MigrationBuilder migrationBuilder)
            {
                migrationBuilder.DropTable(
                    name: "Articles");
            }
        }
    }
    

Creating database and executing migrations

At this moment you can create/update the database with command: dotnet ef database update --connection "ConnectionString"

or when you create DbContext you can run migration:

 private async Task RunMigration()
 {
     await using var dbContext = CreateDbContext();
     await dbContext.Database.MigrateAsync();
     // or await dbContext.Database.EnsureCreatedAsync();
 }

The difference between EnsureCreatedAsync and MigrateAsync

Both of them will create database as we want, but when we go a bit deeper.

  • EnsureCreateDatabaseAsync - Will just create database, but it wont care about any migrations. So it is a better choice for test cases when we just need a created database. Documentation describes the behavior:
    • If the database exists and has any tables, then no action is taken. Nothing is done to ensure the database schema is compatible with the Entity Framework model.
    • If the database exists but does not have any tables, then the Entity Framework model is used to create the database schema.
    • If the database does not exist, then the database is created and the Entity Framework model is used to create the database schema.
  • MigrateAsync - It also create database if it doesn’t exist, but if it does it will process migration steps to update it to the newest version.

If you would like to see a speed comparison, let me know in the comments, then I will prepare it for you.

Summary

It is everything for today. Is it useful for you? Would you like to add or ask anything? Let me know in the comment below. See you next time.

comments powered by Disqus

Are you still here? Subscribe for more content!