Imagine the following situation…

That you are new in the world of programming minding your own business and suddenly decide to develop a .NET Core application with a pretty simple database access using code first approach.

You did some research and conclude the use of Entity Framework as your ORM of choice.

First few lines of code

Since .NET Core bootstraps a weather application out of the box you decide to just to extend it. After all this is only an exercise.

You start with ASP.NET Core Web API application call it … Weather.

Obviously, you are a research-oriented person and all the reading points that layering a project is usually considered a best practice. So you decide to create a DAL (Data Access Layer) which will hold all the logic that is tied with the database.

Here is how your project looks like for the moment:

SolutionExplorer

You also have a pretty good idea how your database should look like. It consists of only one table which is called Degrees. This table will hold the info of outside temperature per hour. At this time where the data will come is not relevant. The only relevant thing is a working code.

Here is the Degree entity:

public class Degree
{
    public int Id { get; set; }

    public double Temperature { get; set; }

    public double FeelsLike { get; set; }

    public string City { get; set; }

    public DateTime CreateDate { get; set; }
}

Nothing special it is just Plain Old CLR Object. Everything in the class is self-explanatory enough so let’s move on.

It comes to your mind that you probably should enforce some database rules. That’s how DegreeConfigFile.cs is born:

public class DegreeConfigFile : IEntityTypeConfiguration<Degree>
{
    public void Configure(EntityTypeBuilder<Degree> builder)
    {
        // Specify that Id of your class is Primary Key in the database.
        builder.HasKey(x => x.Id);

        // City cannot be more than 200 characters long and is required to supply.
        builder.Property(x => x.City).HasMaxLength(200).IsRequired();

        // The following properties are required when inserting a record.
        builder.Property(x => x.Temperature).IsRequired();
        builder.Property(x => x.FeelsLike).IsRequired();
        builder.Property(x => x.CreateDate).IsRequired();
    }
}

This is the place where you realize that in order to compile the project you should install Entity Framework to your project. These are the packages that you settle:

Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Relational

After including your namespaces your project can be built once again …YAY.

The last piece of this exercise is to add a context file and generate your database. You do not need anything fancy so you will settle down with a bare-bones context which suspiciously looks like this one.

public class WeatherContext : DbContext
{
    public WeatherContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<Degree> Degrees { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new DegreeConfigFile());
    }
}

This context has only one table and enforces the rules that we created a little earlier.

After completing the context file, you start searching how to generate a database out of this code. Naturally you come across EF Core CLI tools. Lucky for you Microsoft has a very clear documentation.

The only thing you must do is to execute the following piece of code inside cmd or powershell:

dotnet tool install --global dotnet-ef

And you get EF Core tools on your PC. It is a general knowledge that in order to get a database, first you should make a migration. The documentation for adding a migration is clear too. Kudos to Microsoft for that. The only thing you should do is to execute this in the folder where your db context is located:

dotnet-ef migrations add InitialMigration

After this step you get a pretty good error message which tells you:

Your startup project ‘DAL’ doesn’t reference Microsoft.EntityFrameworkCore.Design. This package is required for the Entity Framework Core Tools to work. Ensure your startup project is correct, install the package, and try again.

No problem. You are ready to install a tiny package in order to generate a database. You head over to NuGet and install the latest version. Once you are ready with all this you try the previous command again. Only to discover that this time you triggered another error:

Unable to create an object of type ‘WeatherContext’. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728

Hmm… you head over to check what patterns you can use. First one “From application services” requires your context to be in the Web API project. You have no intention to do that. You want your database logic separated so you move on to number two “Using a constructor with no parameters”. This one is appealing, so you go with this one.

You add another constructor to your WeatherContext class which now looks like this:

public class WeatherContext : DbContext
{
    public WeatherContext()
    {
    }

    public WeatherContext(DbContextOptions<WeatherContext> options) : base(options)
    {
    }

    public DbSet<DegreesForCity> DegreesForCity { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new DegreesForCityConfig());
    }
}

You try to execute the migration code again:

dotnet-ef migrations add InitialMigration

Only to discover that nothing has changed. The error is the same. What is going on? You move to number three which clearly says that if you have this one the other rules are bypassed:

You can also tell the tools how to create your DbContext by implementing the IDesignTimeDbContextFactory interface: If a class implementing this interface is found in either the same project as the derived DbContext or in the application’s startup project, the tools bypass the other ways of creating the DbContext and use the design-time factory instead.

Since you have enough classes you make your DbContext inherits this interface and everything should work. Microsoft has a nice example in their documentation, so you go with it. Your class now looks like this:

public class WeatherContext : DbContext, IDesignTimeDbContextFactory<WeatherContext>
{

    public WeatherContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<Degree> Degrees { get; set; }

    public WeatherContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder<WeatherContext>();
        optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=Weather;Integrated Security=True");

        return new WeatherContext(optionsBuilder.Options);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new DegreeConfigFile());
    }
}

Here you should realize that you lack yet another package because you do not have UseSqlServer method. Let me help you that package is:

Microsoft.EntityFrameworkCore.SqlServer

You head over to NuGet and install the latest version.

If by this point you do not remember what you were doing let me remind you. You were trying to add a migration…Focus. So, you once again try the add migration command:

dotnet-ef migrations add InitialMigration

Only to discover another error which says:

No parameterless constructor defined for type ‘DAL.WeatherContext’.

Let’s create one in that case:

public class WeatherContext : DbContext, IDesignTimeDbContextFactory<WeatherContext>
{
    public WeatherContext()
    {
    }

    public WeatherContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<Degree> Degrees { get; set; }

    public WeatherContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder<WeatherContext>();
        optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=Weather;Integrated Security=True");

        return new WeatherContext(optionsBuilder.Options);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new DegreeConfigFile());
    }
}

Yet again you run:

dotnet-ef migrations add InitialMigration

Hurray… we have a migration. Let’s put this migration to use and create our database. Since you are a perfectionist and you do not want your connection string laying around in the code you want to pass it as an argument. You slightly modify CreateDbContext like that:

public WeatherContext CreateDbContext(string[] args)
{
    var optionsBuilder = new DbContextOptionsBuilder<WeatherContext>();
    optionsBuilder.UseSqlServer(args[0]);

    return new WeatherContext(optionsBuilder.Options);
}

Let’s call the update database method with connection string parameter. Based on the documentation it should be easy. Just copy your InitialMigration’s auto-generated class name which should be in the Migration folder of the DAL project.

dotnet ef database update 20201127212754_InitialMigration --connection "Data Source=.;Initial Catalog=Weather;Integrated Security=True"

You should get the following error:

System.IndexOutOfRangeException: Index was outside the bounds of the array.

This one is on you because you assumed that –connection parameter will fill your args array in CreateDbContextMethod. Well… you assumed wrong - it doesn’t.

If you dig deep enough in the documentation you should land on this section in the EF Core documentation which shows you how to pass arguments. It shows that in order to pass arguments you should separate them from EF Core’s arguments by putting in front of them. Basically this:

dotnet ef database update -- "Data Source=.;Initial Catalog=Weather;Integrated Security=True"

You do not have any more errors and have a shiny database instead.

Conclusion

See how easy that was. You just had to:

  1. Create the required classes:
    • Degree.cs
    • DegreeConfigFile.cs
    • WeatherContext.cs.
  2. Install the required packages:
    • Microsoft.EntityFrameworkCore,
    • Microsoft.EntityFrameworkCore.Relational
    • Microsoft.EntityFrameworkCore.SqlServer
  3. Try to add migration with bare-bones context.
  4. After the above attempt fails try to install Microsoft.EntityFrameworkCore.Design and try again.
  5. After the above attempt fails try the second design approach since the first one requires your Web API to know about your database context.
  6. After the above attempt fails try to inherit IDesignTimeDbContextFactory.
  7. After the above attempt fails Create a parameterless constructor.
  8. After adding the migration try to update database with connection string as argument with –connection parameter.
  9. After the above attempt fails to update the database try with – connection_string.
  10. And you get a working auto-generated database. …

Become a Subscriber

Subscribe to my blog and get the latest posts straight to your inbox.