I have a running NET6 app that uses EF6 to connect with Postgres.
I don't want to have DB access in the tests just logic, so im trying to replace the actual Postgres db with an in-memory db.
public class TestApp : WebApplicationFactory<Program>
{
protected override IHost CreateHost(IHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services.RemoveAll(typeof(IAuthService));
services.AddScoped<IAuthService, StubAuthService>();
var myDatabaseName = "test_"+DateTime.Now.ToFileTimeUtc();
var options = new DbContextOptionsBuilder<PlayersContext>()
.UseInMemoryDatabase(databaseName: myDatabaseName )
.Options;
services.AddDbContextFactory<PlayersContext>(f => new DbContext(options));
});
return base.CreateHost(builder);
}
}
This is wrong and not working, im likely trying doing it wrong. My questions here would be:
How to stub the database in tests so the app uses an in memory db instead of the actual database ?
How can I read that stubbed DB context inside a test to validate something was inserted for instance ?
Thanks !
Related
I've configured my Database First EF6 DbContext to log the queries it generates but I'm noticing that the queries it provides are parameterized. For debugging purposes I wanted to output the values of each of the parameters. I wrote an interceptor class and configured it to output the parameters like in the below code snippet, but it still doesn't output the parameter value. What am I doing wrong? What's the correct way to output the parameter values for the queries that Entity Framework generates. I know that in EF Core there's a setting on the OptionsBuilder to enable logging of sensitive data but I can't find any similar setting in EF6.
public class LoggingInterceptor : DatabaseLogFormatter {
public LoggingInterceptor(DbContext context, Action<string> writeAction) : base(context,writeAction) {
}
public override void LogCommand<TResult>(DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
{
var sql = new StringBuilder();
sql.Append(command.CommandText);
sql.Append("\n");
foreach (var param in command.Parameters) {
sql.Append(param.ToString());
sql.Append("\n");
}
Write($"Entity Framework SQL : {sql}");
}
public override void LogResult<TResult>(DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext){}
}
You don't need to do anything special (like your inerceptor), if you add this code before your query to the database you will get the query and the parameters on the output window of Visual studio:
context.Database.Log = x => System.Diagnostics.Debug.WriteLine(x);
I have a .NET Core 2.1 web app where users can select the database provider of their choice. It's a choice between SQL Server, SQLite and MySQL (for now, more providers could be added later). I am saving user's choices to a json file along with the connection strings for each database provider:
"ConnectionStrings": {
"MSSQL": "Server=(localdb)\\MSSQLLocalDB;Database=ABC_db;Trusted_Connection=True;MultipleActiveResultSets=true",
"SQLite": "Data Source=ABC.db"
},
"UserSettings": {
"DatabaseProvider": "MSSQL", //this changes as per user's selection
"GenerateDb": false //this will be false for the first time, after that it will be true
}
And in my ConfigureServices method in Startup.cs I have placed some checks to register/inject the database context and identity:
GenerateDb = Configuration.GetValue<bool>("GenerateDb");
DatabaseProvider = Configuration.GetValue<string>("SystemSettings:SystemProfile:DatabaseProvider");
if(GenerateDb)
{
if (DatabaseProvider == "MSSQL")
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString(DatabaseProvider)));
else if (DatabaseProvider == "SQLite")
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlite(Configuration.GetConnectionString(DatabaseProvider)));
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
}
and this code works as expected, it sets the database context with whatever provider the user has selected. The only problem is that to activate the database context, I have to stop and start the app again so next when it reads the json file, GenerateDb is true. I am looking for something that can help me restart the app without manually doing it. Is this functionality available? I couldn't find anything in the docs.
An option would be to register 2 different implementations of ApplicationDbContext.
First, create the new classes (they can be empty implementations, it doesn't matter)
public class SQliteApplicationDbContext : ApplicationDbContext {}
public class SqlServerApplicationDbContext : ApplicationDbContext {}
Then register them as such:
services.AddDbContext<SqlServerApplicationDbContext >(options =>
options.UseSqlServer(Configuration.GetConnectionString(DatabaseProvider)));
services.AddDbContext<SQliteApplicationDbContext>(options =>
options.UseSqlite(Configuration.GetConnectionString(DatabaseProvider)));
services.AddScoped<ApplicationDbContext>((ctx) =>
{
// fyi: would be better to implement the options pattern here
DatabaseProvider = Configuration.GetValue<string>("SystemSettings:SystemProfile:DatabaseProvider");
if (DatabaseProvider == "MSSQL")
ctx.GetService<SqlServerApplicationDbContext >();
else if (DatabaseProvider == "SQLite")
ctx.GetService<SQliteApplicationDbContext>();
else
throw new Exception("Bad configuration");
});
Note that this makes the assumptions that asp.net core is configured to watch the changes in the json file.
We're using DBUP to handle db migrations. Each release, we would like to run the dbup console app with a command line switch so that during dev we can re-run our scripts while we're working on them, however we don't want it to re-run all the previous releases scripts which already appear in the database. How can this be achieved?
We added a '-debug' command line switch to our DbUp console application. If this is present we switch which Journal class is used when talking to the database.
The Journal class (https://dbup.readthedocs.io/en/latest/more-info/journaling/) in DbUp is the class that interacts with the database to check and record which scripts have already been run (stored by default in the Schema Versions table). For Dev, we force this to use a read-only version of this, which can check which scripts are already present (to prevent you re-running everything each time) but prevents new records being recorded, so that next time it will attempt to re-run your new scripts again.
The read only journal looks like this;
public class ReadOnlyJournal : IJournal
{
private readonly IJournal _innerJournal;
public ReadOnlyJournal(IJournal innerJournal)
{
_innerJournal = innerJournal;
}
public void EnsureTableExistsAndIsLatestVersion(Func<IDbCommand> dbCommandFactory)
{
_innerJournal.EnsureTableExistsAndIsLatestVersion(dbCommandFactory);
}
public string[] GetExecutedScripts()
{
return _innerJournal.GetExecutedScripts().ToArray();
}
public void StoreExecutedScript(SqlScript script, Func<IDbCommand> dbCommandFactory)
{
// don't store anything
}
}
Then an extension method to allow the use of this new journal to be easier specified;
public static class DbUpHelper
{
public static UpgradeEngineBuilder WithReadOnlyJournal(this UpgradeEngineBuilder builder, string schema, string table)
{
builder.Configure(c => c.Journal = new ReadOnlyJournal(new SqlTableJournal(() => c.ConnectionManager, () => c.Log, schema, table)));
return builder;
}
}
And then finally the change to your DbUp console app;
var upgrader = debug
? DeployChanges.To
.SqlDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
.WithReadOnlyJournal("dbo", "SchemaVersions")
.LogToConsole()
.Build()
: DeployChanges.To
.SqlDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
.LogToConsole()
.Build();
var result = upgrader.PerformUpgrade();
if (!result.Successful)
....
I have a test project, i need to get the connection string value in a test class
public class EmplyeesScenarios
{
private readonly TestServer _testServer;
private readonly AppDbContext _testService;
public EmplyeesScenarios()
{
_testServer = TestServerFactory.CreateServer<TestsStartup>();
var dbOption = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlServer("//here i need to put the same connection string of _testServer")
.Options;
_testService = new AppDbContext(dbOption);
}
}
You can use the service collection on your test host. For example:
var config = _testServer.Host.Services.GetRequiredService<IConfigurationRoot>();
var connectionString = config.GetConnectionString("Foo");
However, you should actually be using this for all your services, so instead of trying to new up your context at all, just do:
var context = _testServer.Host.Services.GetRequiredService<AppDbContext>();
No connection string needed.
Assuming that your test server is set up properly, you should just resolve your database context from the server instance directly. Like this:
_testServer = TestServerFactory.CreateServer<TestsStartup>();
// create service scope and retrieve database context
using (var scope = _testServer.Host.Services.CreateScope())
{
var db = scope.ServiceProvider.GetService<AppDbContext>();
// ensure that the db is created for example
await db.Database.EnsureCreatedAsync();
// add test fixtures or whatever
}
Technically, you could also resolve the configuration from the test host and the read the connection string out of it but since you are doing an integration test, you should actually test the full integration and not deviate from the existing setup by creating your database context manually.
Per the FAQ (1), I can add additional databases to my existing connection in a number of ways. I have tried them all, and none work for SQL Azure.
In fact, SQL Azure as a provider, doesn't even include the option to "Include additional databases."
Can someone please tell me a workaround for LinqPad to connect two databases? I am trying to create a migration linqpad script to sync data from one database to another.
http://www.linqpad.net/FAQ.aspx#cross-database
This fails because SQL Azure does not let you create linked servers. See
Can linked server providers be installed on a SQL Azure Database instance?
If you simply want to copy data from one database to another, and the schemas are the same, a workaround is to create a separate connection using the same TypedDataContext class:
void Main()
{
CopyFrom<Customer>("<source connection string>");
}
void CopyFrom<TTable> (string sourceCxString) where TTable : class
{
// Create another typed data context for the source. Note that it must have compatible schema:
using (var sourceContext = new TypedDataContext (sourceCxString) { ObjectTrackingEnabled = false })
{
// Delete the rows currently in our table:
ExecuteCommand ("delete " + Mapping.GetTable (typeof (TTable)).TableName);
// Insert the rows from the source table into the target table and submit changes:
GetTable<TTable>().InsertAllOnSubmit (sourceContext.GetTable<TTable>());
SubmitChanges();
}
}
Simple Select Example:
void Main()
{
SimpleSelect("<your conn string>");
}
void SimpleSelect (string sourceCxString)
{
// Create another typed data context for the source. Note that it must have compatible schema:
using (var sourceContext = new TypedDataContext (sourceCxString) { ObjectTrackingEnabled = false })
{
sourceContext.Assignee.OrderByDescending(a => a.CreateTimeStamp).Take(10).Dump();
Assignee.OrderByDescending(a => a.CreateTimeStamp).Take(10).Dump();
}
}