Pass arguments to IDesignTimeDbContextFactory of EF Core - .net-core

How can we pass arguments to dotnet ef database update?
i want to be able to update different database with the use of arguments.
i've tried
dotnet ef database update "Accept"
dotnet ef databse update Accept
but it didn't work..
Or how I can put a switch to get different conenctionString from my configuration?
public ProjectContext CreateDbContext(string[] args)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
// Find a way to get different connection string
var connectionString = configuration.GetConnectionString(args[0]);
var builder = new DbContextOptionsBuilder<ProjectContext >();
builder.UseSqlServer(connectionString);
return new ProjectContext(builder.Options);
}

.NET 5 releases in a couple weeks from the time of this answer. So this is possible to change.
Answer Now
The .NET 5, and the associated EF Core 5+ NuGets support this. In Package Manager you can type:
Add-Migration YourMigrationName -Args "Space Separated Args"
Update-Database -Args "Space Separated Args"
For example, I use this:
Update-Database -Args MyConnName
In my Startup project I have a config file (Or appsettings.json) that has that connection string key, and I pull that in.
Note I said .NET Core 5. This will be have a full release in a few weeks from now. So in a few weeks this answer may be simple. But until then, you may need to install Preview versions (And NuGet PreReleases)
Answer Prior to now
There were lacking options when this question was asked, though there were options, like using dotnet ef commands with AppArgs, as discussed here. But these have changed, and are also now accessible from PM Console as discussed in the above "Now" answer.

You can also set variables like this:
$env:SqlConnectionString="Server=tcp:mySqlServerStuffxxx"
Add-Migration InitialCreate
Update-Database
Source:
https://dev.to/azure/using-entity-framework-with-azure-functions-50aa#adding-an-entity-framework-migration
It won't work for ConnectionStrings.DefaultConnection though. It will give the following error:
The property 'DefaultConnection' cannot be found on this object.
Verify that the property exists and can be set.

from everything I ready here I basically ended up with CreateDbContext(string[] args) the args will get populated with anything after you initial CMD
The -- token directs dotnet ef to treat everything that follows as an
argument and not try to parse them as options. Any extra arguments not
used by dotnet ef are forwarded to the app.
more here
will result with args being populated with 4 args
this works as the -- passes the all else back to dotnet to set the args
As an example, when adding IDesignTimeDbContextFactory<YourContext> interface to your Context adds the following method and this is where I am able to pass in the vars
public DataBaseContext CreateDbContext(string[] args)
{
Debugger.Launch();
Console.WriteLine($#"Args:");
foreach (var arg in args)
{
Console.WriteLine($"Arg: {arg}");
}
}
this will write out the 4 vars passed in -test something -testagain again
I post this answer to help expand on other answers here and see the full context

Hope this answer will help people who are trying to resolve this using .net cli.
Custom arguments can be passed to IDesignTimeDbContextFactory<T> using -- token as mentioned earlier, but I found missing actual example showing that basically there is no rule how you define them, e.g. can be -- -DbUser Admin, -- DbUser=Admin. Just make sure there is a space separator following --. The rest is forwarded to design time context factory automatically.
With all this in mind the sample usage may look like:
dotnet ef database update -c YourDbContext -- -DbUser Admin -Password Secret
dotnet ef database update -c YourDbContext -- DbUser=Admin Password=Secret
Latter seems makes more sense as parameters arrive in single dimension array, so have to map param name with param value manually if you choose the first example. Also second one looks more clear to me as extra dashes make it more confusing.
More information here: learn.microsoft.com

I had a similar issue recently trying to get an Asp.Net Core app to read the connection string. It turns out that you don't need the IDesignTimeDbContextFactory. Instead, just make sure that your context has a paramerless constructor, and use something like this in the startup:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
This should resolve to whichever connection string you have configured. If you did want to use two separate connections at the same time (which I realise you didn't want), you could do this by registering multiple DbContexts at this point; for example:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("OtherConnection")));

Related

Upgrading from MassTransit v5 to v8 and an AutoFac Extension method used by our Multi Tenanted application is no longer available

In a nutshell I'm in the process of upgrading a .NETStandard 2.1 app to .NET 6. Plus upgrading the various libraries accordingly, in particular MassTransit v5 to v8, and AutoFac 4.9.4 to 6.4.0.
This is a multi-tenant application where one instance is shared by multiple tenants, and each tenant has their own database.
The upgrade has gone well apart from one snag. The application uses the, no longer available, AutofacReceivedEndpointExtensions to setup the Tenant details in the Consumer, and I am struggling to find a way to replicate the functionally it provides.
Below is the key bit of code.
config.ReceiveEndpoint(host, azureBusConfig.QueueName, endpoint =>
{
ConfigureConsumer<MyConsumer>(endpoint, componentContext);
});
private static void ConfigureConsumer<TConsumer>(IServiceBusReceiveEndpointConfigurator endpoint, IComponentContext componentContext, Action<IConsumerConfigurator<TConsumer>> configure = null)
where TConsumer : class, IConsumer
{
endpoint.Consumer(componentContext, configure, configureScope: (container, context) =>
{
var tenantName = context.Headers.Get<string>("tenant");
var userId = context.Headers.Get<int>("userId");
container.RegisterInstance(new NamedTenantInfoProvider(tenantName, userId)).As<ITenantInfoProvider>();
});
}
The endpoint.Consumer method as shown is no longer provided.
The ITenantInfoProvider interface is injected into various constructors in the application e.g., to setup the dbContext for a tenant to point to the correct database.
public interface ITenantInfoProvider
{
string GetTenantName();
int? GetUserId();
}
There are two implementations of the ITenantInfoProvider. The NamedTenantInfoProvider which is used to set the Tenant from the received message, above.
There is also a RequestTenantInfoProvider, that gets the Tenant from the HttpRequest. e.g. via api call.
The RequestTenantInfoProvider is registered as follows
builder.RegisterType<RequestTenantInfoProvider>()
.As<ITenantInfoProvider>()
.InstancePerLifetimeScope();
So, what should happen is that the RequestTenantInfoProvider is injected into the constructors by default, but when a message is being consumed the NamedTenantInfoProvider instance is injected instead.
I have tried to register the NamedTenantInfoProvider as per the RequestTenantInfoProvider. Then inject an IEnumerable into the constructors. And set the Tenant in the consumer.ConfigureConsumer on the Named instance. Then use which ever instance has a Tenant set in the code. However, the NamedTenantInfoProvider instance is set after it is required in the other constructors e.g., dbContext.
The only way I can get the application to fully work is to hardcode a Tenant name in the NamedTenentInfoProvider class.
I was hoping that someone has already refactored some similiar code to replace the endpoint.ConfigureConsumer call and can advise a solution.
It may be that I'm missing a bit of knowledge regarding how scoping works with the Microsoft Dependency Injection/MassTransit configuration. Note: I didn't write the original application, and this is my first dabble with Mass Transit as well.
MassTransit v8 (and onward) only support IServiceCollection, which is part of Microsoft.Extensions.DependencyInjection. Third-party containers are no longer directly supported.
There is a Scoped Filter sample that might help you understand how scopes work with MSDI. The token concept is similar to that used by developers injecting "tenant info" into consumers.

How to solve room data integrity error due to identityHash mismatch?

Issue:
Whenever I make changes to the database or the model, I get the following Room data integrity error:
My understanding is that I shouldn't need to increase the version number since I am using .fallbackToDestructiveMigration().
Background:
I using DB Browser for SQLite (v3.12.0) to make changes to the database.
I frequently make changes to my app/database, which is still in development. So, I am using a .fallbackToDestructiveMigration() (see codelab example).
File: RoomDB.java
#Database(entities =
{Note.class, Label.class, Join_ScheduleLabel.class, Schedule.class},
version = 1)
#TypeConverters(DataConverters.class)
public abstract class RoomDB extends androidx.room.RoomDatabase {
public static final String DATABASE_NAME = "vk_prepop.sqlite";
private static RoomDB INSTANCE;
public static RoomDB getInstance(final Context context) {
if (INSTANCE == null) {
synchronized (RoomDB.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
RoomDB.class,
DATABASE_NAME)
// Source: https://developer.android.com/training/data-storage/room/prepopulate
.createFromAsset(DATABASE_NAME)
// Todo: Remove Destructive Migration
// Wipes and rebuilds instead of migrating if no Migration object.
.fallbackToDestructiveMigration()
.build();
}
}
}
return INSTANCE;
}
public abstract RoomDao getRoomDao();
}
Troubleshooting Steps Taken:
Verifying the entities of the RoomDB.java file match the models and database.
Going into the App Info and tapping "Clear data" (see SO answer).
Uninstalling the app.
Making setting android:allowBackup="false" in the manifest (see SO answer).
Possible Solution:
In live-love's answer he says there may be an identityHash mismatch, but I am not sure how to resolve this using DB Browser for SQLite.
My understanding is that I shouldn't need to increase the version number since I am using .fallbackToDestructiveMigration().
The fallBackToDestructiveMigration only runs if a migration is required and there is no Migration covering the migration.
In your situation the issue is that you have included the room_master_table in the pre-packaged database and hence the identity_hash columns is available for comparison (which would be incorrect if changes were made to the schema that affected how room generates the identity_hash from the schema).
By including the room_master_table you are introducing an unnecessary complexity.
If you omit this table from the pre-packaged database, then it will be created and populated, with the appropriate identity_hash when it is created from the asset (i.e. when the database doesn't exist). As such you then only have to make the appropriate changes to the asset (the pre-packaged database), delete the current database (e.g. uninstall the App or clear the Apps data) and then run the App.
live-love's answer states that there may be an identityHash mismatch. Indeed this was the case. Here is how I resolved the issue using DB Browser for SQLite (v3.12.0).
Step 1: In Android Studio's project panel choose the "Project" view:
Step 2: Then double-click on the json file for your schema:
Step 3: Copy the identityHash in this json file:
Step 4: Open your database in DB Browser for SQLite. Click the "Browse Data" tab. Then from the drop-down menu choose "room_master_table".
Step 5: Compare the identifyHash from the json file in Android Studio to the identityHash in DB Browser for SQLite. If the hashes are different, this can be the cause of you Room data integrity error.
Step 6: So, paste the identityHash from the Android Studio's json file into the identityHash cell in the DB Browser for SQLite.
Step 7: Then press Ctrl+Shift+S to save the database.
Step 8: Click "Close Database".
Step 9: In your app on your phone or emulator go to "App Info" -> "Storage" -> "Clear data".
Step 10: Then in Android Studio press "Run app".
Problem solved... at least for me. If these steps did not help, please review these additional troubleshooting steps.

Entity Framework Core Scaffold - Dynamic Schema Name

I'm having an issue at the moment where, we have a database which was already created, so used ef scaffold to create a model of it - the schema the model was created against is called "xxxx-dev".
Now, this has been fine, but in preparation to go live, I created a new DB server and provisioned the database to be called "xxxx-live". Switched the connection string, and attempted a query against it, and got an error.
It seems that scaffolding has hard-coded the schema name into every table in the OnModelCreating call, for example:
modelBuilder.Entity<xxxx>(entity =>
{
...
entity.ToTable("xxxx", "xxxx-dev");
...
});
This is a bit of an issue as going forward, we might have multi-tenant sites based on the same database, and obviously the query overriding the connection string every query isn't a great experience.
Is there anyway to configure the schema name, either in the Scaffold, or at runtime? I've done a bit of searching around and can't seem to see a solution.
Thanks,

Passing a stream or a String to Flyway API instead of locations

I was wondering if there is a way for Flyway to accept an actual SQL migration as a string or a stream instead of searching for it on a classpath?
I'm constructing the SQL migration in Java on the fly and would like to call Flyway API and pass the migration as a paramter.
Please, let me know if this is possible.
Thank you
Not entirely what you are asking for, but looks like Java-based migrations might be a solution.
Basically instead of V1_0__script.sql you write V1_0__script.java class implementing JdbcMigration. Inside that class you have access to JDBC Connection:
class V1_0__script implements JdbcMigration {
public void migrate(Connection connection) throws Exception {
//...
}
}
In migrate() you are free to run your custom SQL queries.
There is no API available for this.
However, if you construct your SQL on the fly, it surely must be possible to construct it one statement at a time. Each statement can then be executed using the Connection parameter you get in a JdbcMigration

Problem with Unit testing of ASP.NET project (NullReferenceException when running the test)

I'm trying to create a bunch of MS visual studio unit tests for my n-tiered web app but for some reason I can't run those tests and I get the following error -
"Object reference not set to an
instance of an object"
What I'm trying to do is testing of my data access layer where I use LINQ data context class to execute a certain function and return a result,however during the debugging process I found out that all the tests fail as soon as they get to the LINQ data context class and it has something to do with the connection string but I cant figure out what is the problem.
The debugging of tests fails here(the second line):
public EICDataClassesDataContext() :
base(global::System.Configuration.ConfigurationManager.ConnectionStrings["EICDatabaseConnectionString"].ConnectionString, mappingSource)
{
OnCreated();
}
And my test is as follows:
TestMethod()]
public void OnGetCustomerIDTest()
{
FrontLineStaffDataAccess target = new FrontLineStaffDataAccess(); // TODO: Initialize to an appropriate value
string regNo = "jonh"; // TODO: Initialize to an appropriate value
int expected = 10; // TODO: Initialize to an appropriate value
int actual;
actual = target.OnGetCustomerID(regNo);
Assert.AreEqual(expected, actual);
}
The method which I call from DAL is:
public int OnGetCustomerID(string regNo)
{
using (LINQDataAccess.EICDataClassesDataContext dataContext = new LINQDataAccess.EICDataClassesDataContext())
{
IEnumerable<LINQDataAccess.GetCustomerIDResult> sProcCustomerIDResult = dataContext.GetCustomerID(regNo);
int customerID = sProcCustomerIDResult.First().CustomerID;
return customerID;
}
}
So basically everything fails after it reaches the 1st line of DA layer method and when it tries to instantiate the LINQ data access class...
I've spent around 10 hours trying to troubleshoot the problem but no result...I would really appreciate any help!
UPDATE:
Finally I've fixed this!!!!:) I dont know why but for some reasons in the app.config file the connection to my database was as follows:
AttachDbFilename=|DataDirectory|\EICDatabase.MDF
So what I did is I just changed the path and instead of |DataDirectory| I put the actual path where my MDF file sits,i.e
C:\Users\1\Documents\Visual Studio 2008\Projects\EICWebSystem\EICWebSystem\App_Data\EICDatabase.mdf
After I had done that it worked out!But still it's a bit not clear what was the problem...probably incorrect path to the database?My web.config of ASP.NET project contains the |DataDirectory|\EICDatabase.MDF path though..
Is LINQDataAccess.EICDataClassesDataContext looking to the web.config or some other outside source of data for its setup?
I can tell you for a fact that you must jump thru hoops to get web.config accessible to your test code.
Update
Ah, yes. I see that you're using ConfigurationManager on the line where your test fails... ConfigurationManager looks to web.config for configuration. This has been a sticking point for me when I write my tests.
You need to either change the code so that the class can be instantiated without web.config, or you need to make it so that your tests can access web.config.
Does your test project have it's own configuration file? This type of behavior usually means the app can't find the connection string. Test projects require their own file since they are not running in the context of the client app.
UPDATE The error you describe after adding an app.config is common when testing web applications built on SQLExpress and attaching an .mdf. SQLExpress cannot be run in more than one process at a time. So if you have previously run your web application it may still be active and will conflict with the attempt to attach the database.
You can use SQL Management Studio to attach the database permanently and then use a more traditional connection string like:
Server=myServer;Database=EICDatabase;Trusted_Connection=True;
For me it seems like your problem is the connection string, which is not set.
I assume your unit test is in a different project than the DAL.
You call the 'new' command on the datacontext constructor without a connection string. So it should usually use its default, when set. But since this setting normally is stored in the web.config of the other project there is no connection string set and you get the error.
If its right, what i assume, you have to get the settings from the DAL project into the unit-testing project. Simplest solution should be to copy web.config connection string to app.config of unit test project.
There are other possibilities for sure, but i think its not easy to get the web.config configuration into your unit-testing project.

Resources