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 DbSet
s 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
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.
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.