skip to Main Content

SQLite Error 1: no such table: __EFMigrationsHistory

It happens after I migrate and update database. I am using a combination of Entity Framework Core and SQLite and have several DbSets in my DbContext file:

using Microsoft.EntityFrameworkCore;
using VinylTap.Models;

namespace VinylTap.ClientApp.Data
{
    public class AlbumContext : DbContext
    {
        public AlbumContext(DbContextOptions options) : base(options)
        {
        }

        public DbSet<Album> Albums { get; set; }
        public DbSet<AlbumOnHand> AlbumsOnHand { get; set; }
        public DbSet<ConfigurationEntity> Configurations { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<AlbumOnHand>()
                .HasOne(b => b.Album)
                .WithMany(a => a.AlbumsOnHand)
                .HasForeignKey(b => b.AlbumId);
        }
    }
}

One to many relationship with album to albums on hand, respectively. DbInitializer file :

using VinylTap.ClientApp.Data;
using VinylTap.Models;

namespace VinylTap.Data
{
    public static class DbInitializer
    {
        public static void Initialize(AlbumContext context)
        {
            if (context.Albums.Any()) return; //db has been seeded
        }
    }
}

One of my principal Controller files:

using Microsoft.AspNetCore.Mvc;
using OAuth;
using Flurl.Http;

namespace VinylTap.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class AlbumsController : Controller
    {
        private static string _apiBaseUrl = "https://api.discogs.com/";
        private readonly IConfiguration _configuration;

        public AlbumsController(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public async Task<IActionResult> Index()
        {
            return View();
        }

        [HttpGet("search")]
        public async Task<IActionResult> GeneralSearch(string query)
        {
            // var response = await _httpClient.GetAsync($"/database/search?query={query}");
            var requestUrl =  _apiBaseUrl + $"database/search?query={query}";

            var client = new OAuthRequest
            {
                Method = "GET",
                Type = OAuthRequestType.ProtectedResource,
                SignatureMethod = OAuthSignatureMethod.PlainText,
                ConsumerKey = _configuration["CONSUMER_KEY"],
                ConsumerSecret = _configuration["CONSUMER_SECRET"],
                Token = _configuration["OAUTH_TOKEN"],
                TokenSecret = _configuration["OAUTH_TOKEN_SECRET"],
                RequestUrl = _apiBaseUrl + $"database/search?query={query}",
            };
            Console.WriteLine(client.GetAuthorizationQuery());
            var url = requestUrl + client.GetAuthorizationQuery();
            var result = await url.GetStringAsync();
            return Ok(result);
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View("Error!");
        }
    }
}

I attempted to integrate OAuth 1.0a (not 2.0) authentication for the external API I’ve planned to use, methods of concealing API tokens (.env), as well as the one-to-many entity relationship.

ConfigurationsController (for grabbing API keys from a separate file):

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using VinylTap.ClientApp.Data;
using VinylTap.Models;

namespace VinylTap.Controllers
{
    [Route("api/configuration")]
    [ApiController]
    public class ConfigurationsController : ControllerBase
    {
        private readonly ILogger<ConfigurationsController> _logger;
        private readonly AlbumContext _context;

        public ConfigurationsController(ILogger<ConfigurationsController> logger, AlbumContext context)
        {
            _logger = logger;
            _context = context;
        }

        [HttpPost]
        public async Task<IActionResult> SaveConfiguration([FromBody] ConfigurationModel configuration)
        {
            if(!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var configEntity = new ConfigurationEntity 
            {
                ConsumerKey = configuration.ConsumerKey,
                ConsumerSecret = configuration.ConsumerSecret,
                OAuthToken = configuration.OAuthToken,
                OAuthTokenSecret = configuration.OAuthTokenSecret
            };

            _context.Configurations.Add(configEntity);
            await _context.SaveChangesAsync();

            return Ok();
        }
    }
}

EnvFileController (future implementation for testing .env file where keys are stored is available, then providing an alternate means to enter one’s own tokens if not):

using Microsoft.AspNetCore.Mvc;

namespace VinylTap.Controllers
{
    [ApiController]
    [Route("api/envfile")]
    public class EnvFileController : ControllerBase
    {
        [HttpGet]
        public IActionResult CheckEnvFile() {
            var envFilePath = Path.Combine(Directory.GetCurrentDirectory(), ".env");
            bool isEnvFileAvailable = System.IO.File.Exists(envFilePath);

            return Ok(new { isEnvFileAvailable });
        }
    }
}

appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=vinyltap.db"
  },
  "AllowedHosts": "*"
}

Extension folder has only ConfigExtensions.cs in it:

using Microsoft.Extensions.Configuration; //rather than "ConfigurationExtensions.cs"

namespace VinylTap.Extensions
{
    public static class ConfigExtensions
    {
        public static IConfigurationBuilder AddDotEnvFile(this IConfigurationBuilder 
        builder) 
        {
            builder.Add(new DotEnvFileSource());
            return builder;
        }
    }

    public class DotEnvFileSource : IConfigurationSource 
    { 
        public IConfigurationProvider Build(IConfigurationBuilder builder) 
        {
            return new DotEnvFileProvider();
        }
    }

    public class DotEnvFileProvider : ConfigurationProvider
    {
        public override void Load()
        {
            var envFilePath = Path.Combine(Directory.GetCurrentDirectory(), ".env");
            if (File.Exists(envFilePath))
            {
                var lines = File.ReadAllLines(envFilePath);
                foreach (var line in lines)
                {
                    if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
                        continue;

                    var parts = line.Split('=', 2);
                    if (parts.Length == 2)
                    {
                        Data[parts[0]] = parts[1];
                    }
                }
            }
            else {
                Console.WriteLine("No .env file found");
            }
        }
    }
}

EDIT: Program.cs (ASP.NET Core 7.0):

using Microsoft.EntityFrameworkCore;
using VinylTap.Extensions;
using System.Reflection;
//using OAuth;
using VinylTap.ClientApp.Data;

var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Development";

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.Sources.Clear();

builder.Configuration
    .AddUserSecrets(Assembly.GetEntryAssembly()!)
    .AddEnvironmentVariables()
    .AddDotEnvFile();

// Add services to the container.

builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<AlbumContext>(options => {
    options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"));
});
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = "OAuth";
    options.DefaultChallengeScheme = "OAuth";
});
builder.Services.AddHttpClient();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();


app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");

app.MapFallbackToFile("index.html");

app.Run();

2

Answers


  1. You need to give permission to access __EFMigrationsHistory table for the user. You can pass the user’s username and password in the connection string. I believe you can do this using database manager as well.

    Login or Signup to reply.
  2. Try using a Configuration Builder class to build the configuration and the use that configuration to get the Connection String and then also remove the line builder.Configuration.Sources.Clear() as it could be clearing the connection string from your configuration file.

    //build your configuration like this
    var cfg = new ConfigurationBuilder()
             .SetBasePath(builder.Environment.ContentRootPath)
    .AddJsonFile("appsettings.json",optional: false,reloadOnChange: true)
    .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json",optional: true)
    .AddEnvironmentVariables()
    .Build();
    //then use the configuration to use sqlite 
    options.UseSqlite(cfg.GetConnectionString("DefaultConnection"));
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search