AspNet Core Add Migration - ConnectionString is from database - asp.net

I am working with AspNet Core 2.0, I have a situation where I have a context but the connection string is dynamic as per selected by the user at runtime.
Add-Migration doesn't work as it wants to have the connection string to match migration history.
var datastore = _MainDBContext.datastores.FirstOrDefault(x=>x.db_type=="MS");
string connectionString = #"Server=" + datastore.db_instance_ip + ";Port=3306;Database=" + datastore.db_name + ";Uid=" + datastore.db_user + ";Password=" + datastore.db_pass + ";";
optionsBuilder.UseMySQL(connectionString);
_MSDBContext= new MSDBContext(optionsBuilder.Options);
_MSDBContext.Database.Migrate();
Error
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 object in its constructor and passes it to the base constructor for DbContext.
I want the migrate to create the database along with tables dynamically. Any help is appreciated.
Thanks

The error message says your DbContext needs a constructor which accepts a DbContextOptions.
public MSDBContext(DbContextOptions options) : base(options)
{
}
Or you could try the following approach (inside of your context class):
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var connectionString = configuration.GetConnectionString("NAME_OF_CONNECTION_STRING");
optionsBuilder.UseSqlServer(connectionString); //or any other DB provider
}
}
Of course, you can use different logic to get the connection string. You don't need to use appsettings.json file.

Related

.NET Core DI Child Scope with Db Context

(I am writing a processor that handles requests in a queue (console app).
I would like to use the .NET Core DI.
So far my code looks like this:
...
var connectionString = exportConfiguration.ConnectionString;
using (var scope = _serviceProvider.CreateScope())
{
var provider = scope.ServiceProvider;
var service = provider.GetRequiredService<MyContext>();
service.SqlConnectionString = sqlConnectionString; // I don't think property injection on a dbcontext will work, it takes its connection string in via the constructor
}
I have read how to assign parameters to the object as shown above, but how do I create a new context based on the connection string that is used in all the objects that the service uses (using constructor injection because thats why dbcontexts take - connection string in constructor)?
(I am not storing my connection string in the queue by the way, a code comes down the queue and my app then chooses the connection string to use).
I have managed to work this out. The key was that when you use CreateScope(), then GetRequiredService(), the DI system will provide new objects. So I just had to provide the correct information. This is now what my code looks like:
// Prior code gets information from a queue, which could be different every time.
// This needs passing as a constructor to the DbContext and possibly other information from the queue to other methods constructors
// (constructor injection not property injection)
var connectionString = queueItem.ConnectionString;
// save the connection string so the DI system (startup.cs) can pick it up
Startup.ConnectionString = connectionString;
using (var scope = _serviceProvider.CreateScope())
{
var provider = scope.ServiceProvider;
var service = provider.GetRequiredService<IMyService>();
// go off and get data from the correct dbcontext / connection string
var data = service.GetData();
// more processing
}
/// The Service has the DbContext in its constructor:
public class MyService : IMyService {
private DbContext _dbContext;
public MyService(DbContext dbContext) {
_dbContext = dbContext;
}
// more stuff that uses dbcontext
}
/// In startup.cs:
public static string ConnectionString {get;set;}
...
builder.Services.AddScoped<IMyService, MyService>();
builder.Services.AddScoped<DbContext>(options => options.UseSqlServer(Startup.ConnectionString));
// Also the following code will work if needed:
// Parameter1 is something that comes from the queue and could be different for each
// CreateScope()
build.Services.AddScoped<IMyOtherService>((_) =>
new MyOtherService(Startup.Parameter1));
I hope this helps somebody, because when I was googling around I couldn't find out how to do this.

Creating a proper db context .NET Core 3

I'm trying to explicitly create a db context in .NET Core 3 startup
I know I can do this in startup.cs ConfigureServices to inject a dbcontext into the controller (which works fine):
String dbconn = Configuration["ConnectionStrings:VerseDBConnectionStringMSSQL"];
services.AddDbContext<VerseDBContext>(options => options.UseSqlServer(dbconn));
but I am trying to generalize the storage provider (and keep the controller code the same for all storage readers), so it takes an IVerseStorageReader interface, instead of a DB context (as I may want to read from memory, or xmlfile, etc) and use the same code in the controller, just switch it based on config in appsettings. One of the VerseStorageReaders takes a db context in constructor:
public class DBVerseReader : IVerseStorageReader
{
private VerseDBContext _dbContext;
public DBVerseReader(VerseDBContext dbContext)
{
_dbContext = dbContext;
}
...
}
My problem is: I can't quite figure out the syntax right for creating the db context explicitly. I'm very close (I think) but this doesn't work:
String dbconn = Configuration["ConnectionStrings:VerseDBConnectionStringMySQL"];
var optionsBuilder = new DbContextOptionsBuilder<VerseDBContext>();
optionsBuilder.UseMySql(dbconn);
VerseDBContext x = optionsBuilder.UseMySql<VerseDBContext>(dbconn); <-- compile error
services.AddSingleton<IVerseStorageReader>(new DBVerseReader(x));
Can someone clue me on what I'm doing wrong? What I'm trying to inject is an instance of IVerseStorageReader, not a DBContext. There are overloads of VerseStorageReader that take a db context as input, and others which take other inputs (e.g. xmlfilename, etc)...so I want startup to add an instance of one of the IVerseStorageReaders and that gets injected (not a dbcontext injection).
You have to get the options from the builder after configuring it
String dbconn = Configuration["ConnectionStrings:VerseDBConnectionStringMySQL"];
var optionsBuilder = new DbContextOptionsBuilder<VerseDBContext>();
optionsBuilder.UseMySql(dbconn);
DbContextOptions<VerseDBContext> options = optionsBuilder.Options;
VerseDBContext x = new VerseDBContext(options);
services.AddSingleton<IVerseStorageReader>(new DBVerseReader(x));
But since DbContext derived classes are usually registered as scoped, I would suggest you move the context into the factory delegate and register the service abstraction as scoped also.
String dbconn = Configuration["ConnectionStrings:VerseDBConnectionStringMySQL"];
var optionsBuilder = new DbContextOptionsBuilder<VerseDBContext>();
optionsBuilder.UseMySql(dbconn);
DbContextOptions<VerseDBContext> options = optionsBuilder.Options;
services.AddScoped<IVerseStorageReader>( sp => {
VerseDBContext x = new VerseDBContext(options);
return new DBVerseReader(x);
});

UnitTest in ASP.NET with Postgres

I write some tests of created system which worked with PostgreSQL. I create in solution new project with type Class Library (.NET Core). Then, i create class, which testing class DocumentRepository. But in constructor of DocumentRepository is used IConfiguration (for connecting with database), and this IConfiguration i can't call in test class. How i can to imitate connecting with database in UnitTest?
Here class, which i want testing
public class DocumentsRepository : IRepository<Documents>
{
private string connectionString;
public DocumentsRepository(IConfiguration configuration, string login, string password)
{
connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");
connectionString = connectionString.Replace("username", login);
connectionString = connectionString.Replace("userpassword", password);
}
internal IDbConnection Connection
{
get
{
return new NpgsqlConnection(connectionString);
}
}
public void Add(Documents item)
{
using (IDbConnection dbConnection = Connection)
{
dbConnection.Open();
dbConnection.Execute("SELECT addrecuserdocuments(#DocumentName,#Contents,#DocumentIntroNumber)", item);
}
}
Here's test, which i try use
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WebApplication4.Controllers;
using WebApplication4.Entites;
using WebApplication4.ViewModels;
using Xunit;
namespace TestsApp
{
public class UserControllerTest
{
private IConfiguration configuration;
private string connectionString;
[Fact]
public async Task IndexUsers()
{
connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");
var aCon = new AccountController(configuration);
var uCon = new UserController(configuration);
LoginModel model = new LoginModel
{
Login = "postgres",
Password = "111"
};
aCon.Authorization(model);
var result = uCon.Index();
var okResult = result.Should().BeOfType<OkObjectResult>().Subject;
var persons = okResult.Value.Should().BeAssignableTo<IEnumerable<Documents>>().Subject;
persons.Count().Should().Be(7);
}
}
}
Test show my error on
var result = uCon.Index();
And get me NullReferenceException.
How i can resolve this problem?
First and foremost, you're not unit testing, you're integration testing. As soon as you've got something like a database connection in the mix, unit testing is well out the window. If your goal is to write unit tests for your repository class, you should be mocking the data store.
Second, you should not inject IConfiguration, if you need some data from your configuration, such as a connection string, you should bind it to a strongly-typed class, and inject that instead:
services.Configure<MyConnectionStringsClass>(Configuration.GetSection("ConnectionStrings"));
Then, inject IOptionsSnapshot<MyConnectionStringsClass> instead.
Third, you really shouldn't be handling it this way, anyways. If you repository has a dependency on IDbConnection, then you should be injecting that into your repository. In Startup.cs:
services.AddScoped(p => new NpgsqlConnection(Configuration.GetConnectionString("Foo"));
Then, accept NpgsqlConnection in your repo constructor and set it to a private field.
Fourth, if you insist on continuing the way you currently are, you should absolutely not have a custom getter on your Connection property that news up NpgsqlConnection. That means you'll get a new instance every single time you access this property. Instead, you should define it as simple { get; private set; }, and set it in your repo's constructor.
Fifth, you should not be using using with a property defined in either way, as it will be disposed after the first time you do it, making all subsequent queries fail with an ObjectDisposedException. If you're going to new it up in your class, then your class needs to implement IDisposable and you should dispose of your connection in the Dispose method. FWIW, if you inject all dependencies (including your connection) into your class, you don't need to implement IDisposable as there's nothing the class will own that it needs to dispose of - another great reason to use dependency injection all the way down.
Finally, to answer you main question, you should use TestServer. When creating a TestServer you pass it your Startup class as a type param, so you end up with a true replica of your actual app, with all the appropriate services and such. Then, you can issue HTTP requests, like you would with HttpClient to test your controller actions and such. However, again, this is for integration testing only, which is the only time you should actually have a PostreSQL database in-play anyways.

SQLite encryption in Universal Windows apps

I am using SQLite database in my universal app. I want to make password protection for the DB file. I am able to set the password for the db file. But when I am trying to read it it shows error like "Sqlite26: file is encrypted or not a database file".
I referred to this URL. I am using Entity Framework Core in the .NET Standard library. Is it possible to read the value from the encrypted DB in .NET Standard library?
The author of the article you linked actually has an answer here on SO that deals with the EF Core scenario as well. You can open the database with EF Core in the OnConfiguring method override:
class AppDbContext : DbContext
{
private SqliteConnection _connection;
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
_connection = new SqliteConnection(_connectionString);
_connection.Open();
var command = _connection.CreateCommand();
command.CommandText = "PRAGMA key = 'password';";
command.ExecuteNonQuery();
options.UseSqlite(_connection);
}
protected override void Dispose()
{
_connection?.Dispose();
}
}
The kez is to provide the password as in a PRAGMA command before the first query on the connection.

EF7 + ASP.NET5 beta8 - Database initializers

I'm working on multitenant application (ASP.NET 5 + EF7). Each tenant will have separate database. I will have one separate database for tenant account data. I have registered service for EF in startup class for this separate database. I have problem with migrations. I cant create EF migration, until tenantDbContext is registered as service with specific connection string. But this conection string must be dynamic for each tenant... Any idea please? What is the best option to manage DbContexts for tenants?
Future edit - protected override void OnConfiguring was the key how to do: Is this good solution please?
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<TenantDbContext>();
public class TenantDbContext : IdentityDbContext<ApplicationUser>
{
public TenantDbContext() //development database with no connectionString in constructor
{
this._connectionString = "Connection String";
}
public TenantDbContext(string ConnectionString)
{
this._connectionString = ConnectionString;
}
private string _connectionString { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
...etc
As I mentioned in comments I have not tried multi-tenant/multi-db myself but try the following:
You can use DbContext CreateIfNotExists() method. https://msdn.microsoft.com/en-us/library/system.data.entity.database.createifnotexists(v=vs.113).aspx
If you have a Migrations/Configuration.cs you can set AutomaticMigrationsEnabled property to false
Setting the initializer off is probably needed as well: Database.SetInitializer<DatabaseContext>(null);
Sorry without knowing more details like workflow of creating a new tenant (automatic from DB or is a screen filled out with the connection string and name etc.) I can't make more detailed suggestions. I would suggest that your data layer be quite abstracted from the context. It seems like a bad idea for developers to have to select the correct context. Hence the use of a factory.
An option is always requiring a tenant id to be passed into all service or repository methods. I'm guessing this would be in some kind of user claim available in the controller.

Resources