Search code examples
c#asp.netentity-frameworkmysql-connector

DotNet Minimal API Refuses to connect to MySql instance


I have set up the following Program.cs:

using Api.Models;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddDbContext<MarketDb>(options =>
{
    var connectionString = builder.Configuration.GetConnectionString("MySqlLocal");
    var serverVersion = new MySqlServerVersion(new Version(8, 3, 0));
    options.UseMySql(connectionString, serverVersion).EnableSensitiveDataLogging().EnableDetailedErrors();
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// build app
var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapGet("/hello", () => "Hello World!");

app.MapGet("/regions", async (MarketDb dbContext) =>
{
    return await dbContext.RegionId.ToListAsync();
}).WithName("GetRegions").WithOpenApi();

app.Run();

The connection string is defined in appsettings.Development.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "MySqlLocal": "server=127.0.0.1;port=3306;database=dbservice_test;user=root;password=password"
  }
}

Further, the db context is defined in MarketDb.cs:

using Microsoft.EntityFrameworkCore;

namespace Api.Models
{
  public class MarketDb : DbContext
  {
    public MarketDb(DbContextOptions<MarketDb> options) : base(options) { }

    public DbSet<CompletedDates> CompletedDates { get; set; }
    public DbSet<RegionId> RegionId { get; set; }
    public DbSet<TypeId> TypeId { get; set; }
    public DbSet<MarketData> MarketData { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
      modelBuilder.Entity<MarketData>()
        .HasOne(m => m.Date)
        .WithMany(d => d.MarketData)
        .HasForeignKey(m => m.DateID);

      modelBuilder.Entity<MarketData>()
        .HasOne(m => m.Region)
        .WithMany(r => r.MarketData)
        .HasForeignKey(m => m.RegionID);

      modelBuilder.Entity<MarketData>()
        .HasOne(m => m.Type)
        .WithMany(t => t.MarketData)
        .HasForeignKey(m => m.TypeID);
    }
  }
}

When I attempt to reach the /hello endpoint, I receive "Hello World!" as expected.

When I attempt to reach the /regions endpoint, I receive the following error:

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      System.InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.

As far as I can tell, I have followed that error message's suggestions to the letter, and yet it still absolutely will not connect to my MySql instance, running in a Docker container. I can reach the database through the adminer container, and I can reach the database with the mysql browser. I do not see what the issue is here?

Below are my model files, in case those are of use:

using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

namespace Api.Models
{
  [Table("type_id")]
  public class TypeId : DbContext
  {
    public int Id { get; set; }
    public string Value { get; set; }
    public ICollection<MarketData> MarketData { get; set; } = new List<MarketData>();

    public TypeId(int id, string value)
    {
      Id = id;
      Value = value ?? throw new ArgumentNullException(nameof(value));
    }
  }
}

using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

namespace Api.Models
{
  [Table("region_id")]
  public class RegionId : DbContext
  {
    public int Id { get; set; }
    public string Value { get; set; }
    public ICollection<MarketData> MarketData { get; set; } = new List<MarketData>();

    public RegionId(int id, string value)
    {
      Id = id;
      Value = value ?? throw new ArgumentNullException(nameof(value));
    }
  }
}

using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;


namespace Api.Models
{
  [Table("market_data")]
  [PrimaryKey(nameof(DateID), nameof(RegionID), nameof(TypeID))]
  public class MarketData : DbContext
  {
    public int DateID { get; set; }
    public CompletedDates Date { get; set; }

    public int RegionID { get; set; }
    public RegionId Region { get; set; }
    public int TypeID { get; set; }
    public TypeId Type { get; set; }
    public double Average { get; set; }
    public double Highest { get; set; }
    public double Lowest { get; set; }
    public long Volume { get; set; }
    public long OrderCount { get; set; }

    public MarketData(int dateID, CompletedDates completedDate, int regionID, RegionId region, int typeID, TypeId type, double average, double highest, double lowest, long volume, long orderCount)
    {
      DateID = dateID;
      Date = completedDate ?? throw new ArgumentNullException(nameof(completedDate));
      RegionID = regionID;
      Region = region ?? throw new ArgumentNullException(nameof(region));
      TypeID = typeID;
      Type = type ?? throw new ArgumentNullException(nameof(type));
      Average = average;
      Highest = highest;
      Lowest = lowest;
      Volume = volume;
      OrderCount = orderCount;
    }
  }

}

using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;

namespace Api.Models
{
  [Table("completed_dates")]
  public class CompletedDates : DbContext
  {
    public int Id { get; set; }
    public DateOnly Date { get; set; }
    public ICollection<MarketData> MarketData { get; set; } = new List<MarketData>();

  }
}

Solution

  • The issue was twofold:

    First: I should not have inherited from DbContext for my data model classes:

    using System.ComponentModel.DataAnnotations.Schema;
    using Microsoft.EntityFrameworkCore;
    
    namespace Api.Models
    {
      [Table("type_id")]
    ---public class TypeId : DbContext
    +++public class TypeId
      {
        public int Id { get; set; }
        public string Value { get; set; }
        public ICollection<MarketData> MarketData { get; set; } = new List<MarketData>();
    
        public TypeId(int id, string value)
        {
          Id = id;
          Value = value ?? throw new ArgumentNullException(nameof(value));
        }
      }
    }
    
    ...etc.
    

    This exposed a warning, which I was trying to be rid of, that navigation properties (TypeId, RegionId, and CompletedDates) in my MarketData model could not be provided to the constructor.

    Instead, they are to be made required properties, which enforces in Entity Framework the non-null relationship in the underlying MySql database. This also obviates the need for an explicit constructor:

    using System.ComponentModel.DataAnnotations.Schema;
    using Microsoft.EntityFrameworkCore;
    
    
    namespace Api.Models
    {
      [Table("market_data")]
      [PrimaryKey(nameof(DateID), nameof(RegionID), nameof(TypeID))]
      public class MarketData
      {
        public int DateID { get; set; }
        public required CompletedDates Date { get; set; }
        public int RegionID { get; set; }
        public required RegionId Region { get; set; }
        public int TypeID { get; set; }
        public required TypeId Type { get; set; }
        public double Average { get; set; }
        public double Highest { get; set; }
        public double Lowest { get; set; }
        public long Volume { get; set; }
        public long OrderCount { get; set; }
      }
    }
    

    So, in summary, it was just a boneheaded mistake that I kept reading past over and over.