Entity Framework - Cannot create file because it already exists - c#

I'm trying to set up a connection to a service-based database in Visual Studio, for a university project. Currently, I have entities set up to model the specific tables that I need (a credentials table for a login page). I have also set up a DbContext, which implements an SqlConnectionFactory.
However, whenever I call on the DbContext in a LINQ query, I always get this exception:
This is despite the fact that I am not trying to create a new database, but to access an existing one.
DbConfiguration
public class SoftStocksDBConfiguration : DbConfiguration
{
public SoftStocksDBConfiguration()
{
this.SetDefaultConnectionFactory(new SqlConnectionFactory("Data Source=(LocalDB)\\MSSQLLocalDB;AttachDbFilename=C:\\...\\SoftStocksDB.mdf;Integrated Security=True"));
this.SetProviderServices("System.Data.SqlClient", System.Data.Entity.SqlServer.SqlProviderServices.Instance);
}
}
DBContext
[DbConfigurationType(typeof(SoftStocksDBConfiguration))]
public class SoftStocksDBContext : DbContext
{
public DbSet<Credential> Credentials { get; set; }
}
Using the DbContext in the login:
Credential credential;
using (var db = new SoftStocksDBContext())
{
credential = db.Credentials
.FirstOrDefault(c => c.Username == userName &&
c.Password == password);
}
...
I have tried using different values for the connection string to verify that the connection is correct.
I have tried using LocalDbConnectionFactory instead of SqlConnectionFactory
I have tried enabling code-First migrations, and ensuring that my entities were up-to-date and mapped correctly.
As I had an issue in that I could only access records that were programmatically added, I tried loading in the data using db.Credentials.Load()
I have tried restarting Visual Studio.
I have tried closing the database connection from the GUI.
I have tried using different parameters in the connection string, such as Initial Catalog.
I have tried running update-database as specified in this question but I get the same error message as above.

Related

.net Core 2, EF and Multi Tenancy - Dbcontext switch based on user

I have the (almost) worst of multi tenancy. I'm building a asp.net core website that I'm porting a bunch of pokey little intranet sites to. Each subsite will be an asp.net Area. I have an IdentityContext for the Identity stuff. I have multiple copies of vendor databases, each of those with multiple tenants. The ApplicationUserclass has an OrgCode property that I want to use to switch the db context.
I can see myself needing something that maps User.OrgCode and Area to a Connection string
There are many partial examples of this on Stack Overflow. I am very confused after an afternoons reading. The core of it seams to be:
remove DI dbcontext ref from the constructor args.
Instantiate the dbcontext in the controller constructor.
Use dbcontext as before.
Am I on the right track?
Any coherent examples?
Edit 2020/07/09
This has unfortunately become more pressing.
The Identity database is tenant agnostic. Every user in Identity has an OrgCode identifier. (Custom user property).
Each server has multi tenancy built in through the use of 'cost centers'. The server has a collection of databases named the same on every server.
core vendor database
custom database where we store our extensions
logs database for our job output
There are also small application specific databases that already use an Org Code to identify a user
Server A - 1 Org Code
Server B - 4 Org Codes
Server C - 3 Org Codes engaged in project, 50+ not yet (mostly small)
Server D - No Org Codes engaged as of now. 80+ on server. (soon)
It is not possible to consolidate all the organisations onto one server. There are legal and technical ramifications. Each server has hundreds of remote transponders reporting to them that would need updating. The data these supply is what our custom jobs work with.
The dream is to continue to use DI in each page, passing in the contexts as required. The context would then be smart enough to pick the correct underlying connection details based on the OrgCode of the username.
I hesitate to use the word proxy because it seems heavily loaded in this space.
Hell, even using a switch statement would be fine if I knew where to put it
Desired effect User from Org XYZ loads page that requires Vendor database, they get the one from the server that XYZ maps to.
Edit 2020/07/13
To tidy up referenceing, I've switched the OrgCode and Server to Enums. The context inheritance is as follows
DbContext
CustLogsContext
public virtual ServerEnum Server
{
get
{
return ServerEnum.None;
}
}
DbSet (etc)
CustLogsServerAContext
public override ServerEnum Server
{
get
{
return ServerEnum.ServerA;
}
}
CustLogsServerBContext (etc)
CustLogsServerCContext (etc)
CustLogsServerDContext (etc)
VendorContext
VendorServerAContext
VendorServerBContext (etc)
VendorServerCContext (etc)
VendorServerDContext (etc)
I've also created a static class OrgToServerMapping that contains a dictionary mapping OrgCodes to Servers. Currently hardcoded, will change eventually to load from config, and add a reload method.
Currently thinking I need a class that collects the contexts Would have a Dictionary<serverEnum, dbcontext> and be registered as a service. Pretty sure I'd need a version of the object for each inherited dbcontext, unless someone knows ome polymorphic trick I can use
I work on a similar system with thousands of databases, but with LinqToSql instead of EF (I know...). Hopefully the general ideas translate. There are connection pool fragmentation issues that you have to contend with if you end up with many databases, but for just your four databases you won't have to worry about that.
I like these two approaches - they both assume that you can set up the current ApplicationUser to be injected via DI.
Approach #1: In Startup, configure the DI that returns the data context to get the current user, then use that user to build the correct data context. Something like this:
// In Startup.ConfigureServices
services.AddScoped<ApplicationUser>((serviceProvider) =>
{
// something to return the active user however you're normally doing it.
});
services.AddTransient<CustLogsContext>((serviceProvider) =>
{
ApplicationUser currentUser = serviceProvider.GetRequiredService<ApplicationUser>();
// Use your OrgToServerMapping to create a data context
// with the correct connection
return CreateDataContextFromOrganization(currentUser.OrgCode);
});
Approach #2: Rather than injecting the CustLogsContext directly, inject a service that depends on the active user that is responsible for building the data context:
// In Startup.ConfigureServices
services.AddScoped<ApplicationUser>((serviceProvider) =>
{
// something to return the active user however you're normally doing it.
});
services.AddTransient<CustLogsContextWrapper>();
// In its own file somewhere
public class CustLogsContextWrapper
{
private ApplicationUser currentUser;
public CustLogsContextWrapper(ApplicationUser currentUser)
{
this.currentUser = currentUser;
}
public CustLogsContext GetContext()
{
// use your OrgToServerMapping to create a data context with the correct connection;
return CreateDataContextFromOrganization(user.OrgCode);
}
}
Personally I prefer the latter approach, because it avoids a call to a service locator in Startup, and I like encapsulating away the details of how the data context is created. But if I already had a bunch of code that gets the data context directly with DI, the first one would be fine.
I have created a multitenancy implementation as follows (which could scale endlessly in theorie). Create a multitenancy database (say tenantdb). Easy. But the trick is to store connectionstring details for each tenant (your target databases). Along side your user orgCode etc.
I can see myself needing something that maps User.OrgCode and Area to a Connection string
So the way to map it in code is to feed your dbcontext whith your target tenant connectionstring, which you get from your tenantdb. So you would need anohter dbcontext for you tenantdb. So first call your tenantdb get the correct tenant connectionstring by filtering with your user orgcode. And then use it to create a new target dbcontext.
The dream is to continue to use DI in each page, passing in the contexts as required. The context would then be smart enough to pick the correct underlying connection details based on the OrgCode of the username.
I have this working with DI.
I created UI elements for crud operations for this tenantdb, so I can update delete add connection string details and other needed data. The Password is encrypted on save and decrypted on the get just before passing to your target dbcontext.
So I have two connection strings in my config file. One for the tenantdb and one for a default target db. Which can be an empty/dummy one, as you probably encounter application startup errors thrown by your DI code if you don't have one, as it will most likely auto search for a connectionstring.
I also have switch code. This is where a user can switch to anohter tenant. So here the user can choose from all the tenants it has rights to (yes rights are stored in tenantdb). And this would again trigger the code steps described above.
Cheers.
Took this Razor Pages tutorial as my starting point.
This way you can have very lousily coupled target databases. The only overlap could be the User ID. (or even some token from Azure,Google,AWS etc)
Startup.
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddDbContext<TenantContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("TenantContext")));
//your dummy (empty) target context.
services.AddDbContext<TargetContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("TargetContext")));
}
IndexModel (Tenant pages).
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.TenantContext _context;
private ContosoUniversity.Data.TargetContext _targetContext;
public IndexModel(ContosoUniversity.Data.TenantContext context, ContosoUniversity.Data.TargetContext targetContext)
{
_context = context;
//set as default targetcontext -> dummy/empty one.
_targetContext = targetContext;
}
public TenantContext Context => _context;
public TargetContext TargetContext { get => _targetContext; set => _targetContext = value; }
public async Task OnGetAsync()
{
//get data from default target.
var student1 = _targetContext.Students.First();
//or
//switch tenant
//lets say you login and have the users ID as guid.
//then return list of tenants for this user from tenantusers.
var ut = await _context.TenantUser.FindAsync("9245fe4a-d402-451c-b9ed-9c1a04247482");
//now get the tenant(s) for this user.
var SelectedTentant = await _context.Tenants.FindAsync(ut.TenantID);
DbContextOptionsBuilder<TargetContext> Builder = new DbContextOptionsBuilder<TargetContext>();
Builder.UseSqlServer(SelectedTentant.ConnectionString);
_targetContext = new TargetContext(Builder.Options);
//now get data from the switched to database.
var student2 = _targetContext.Students.First();
}
}
Tenant.
public class Tenant
{
public int TenantID { get; set; }
public string Name { get; set; }
//probably could slice up the connenctiing string into props.
public string ConnectionString { get; set; }
public ICollection<TenantUser> TenantUsers { get; set; }
}
TenantUser.
public class TenantUser
{
[Key]
public Guid UserID { get; set; }
public string TenantID { get; set; }
}
Default connstrings.
{ "AllowedHosts": "*",
"ConnectionStrings": {
"TenantContext": "Server=(localdb)\mssqllocaldb;Database=TenantContext;Trusted_Connection=True;MultipleActiveResultSets=true",
"TargetContext": "Server=(localdb)\mssqllocaldb;Database=TargetContext;Trusted_Connection=True;MultipleActiveResultSets=true"
}

Missing ProviderName when debugging AzureFunction as well as deploying azure function

I have an issue getting a DbContext to correctly pull my connection string from my local.settings.json
Context:
This is an Azure function project
The main problem code is in System.Data.Entity.Internal.AppConfig
Although I have a local.settings.json file this is not dotnet core. It's .net 4.6.1
Error message:
'The connection string 'ShipBob_DevEntities' in the application's configuration file does not contain the required providerName attribute."'
Json configuration:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"AzureWebJobsDashboard": ""
},
"ConnectionStrings": {
"ShipBob_DevEntities": {
"ConnectionString": "metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string='data source=***;initial catalog=***;persist security info=True;User Id=***;Password=***;;multipleactiveresultsets=True;application name=EntityFramework'",
"providerName": "System.Data.EntityClient"
}
}
}
Configuration versions tested:
Moving the provider name into the actual ConnectionString token value : same error ocurrs
Setting the provider attribute inside the ConnectionString attribute to EntityClient: this did nothing
Making ShipBob_DevEntities a string value = to the value of ConnectionString : this throws new errors the likes of which are
keyword metadata is not supported
I tried using an ADO connection string which throws a code first exception which seems to occur when your connection string is incorrect in a database first approach.
I've taken the liberty to decompile EntityFramework.dll using dotPeek and have traced the problem down to System.Data.Entity.Internal.LazyInternalConnection.TryInitializeFromAppConfig. Inside this method there is a call to LazyInternalConnection.FindConnectionInConfig which spits out a ConnectionStringSettings object that has it's ProviderName value set to null. Unfortunately I am unable to debug the AppConfig.cs class which it seems to use to generate this value so I am stuck.
So far I have consulted these two articles. One of which states to put the provider name as it's own token; however, this is not working.
https://github.com/Azure/azure-functions-cli/issues/193
https://github.com/Azure/azure-functions-cli/issues/46
Does anyone know the correct format to use in local.settings.json for an Entity Framework connection?
I went through several similar questions and answers here. Many of them are either misleading or assuming everybody is on the same level and understands how the azure functions are working. there is no answer for newbies like me. I would like to summarize here my solution step by step. I dont think that provided answer is the best option because it forces you to change the auto generated edmx files which can be overwritten by mistake or next update of your edmx from database. Also best option here is to use Connection strings instead of App settings in my opinion.
most important thing is that we understand local.settings.json file
IS NOT FOR AZURE. it is to run your app in the local as the name is
clearly saying. So solution is nothing to do with this file.
App.Config or Web.Config doesnt work for Azure function connection strings. If you have Database Layer Library you cant overwrite connection string using any of these as you would do in Asp.Net applications.
In order to work with, you need to define your connection string on the azure portal under the Application Settings in your Azure function. There is
Connection strings. there you should copy your connection string of your DBContext. if it is edmx, it will look like as below. There is Connection type, I use it SQlAzure but I tested with Custom(somebody claimed only works with custom) works with both.
metadata=res:///Models.myDB.csdl|res:///Models.myDB.ssdl|res://*/Models.myDB.msl;provider=System.Data.SqlClient;provider
connection string='data source=[yourdbURL];initial
catalog=myDB;persist security info=True;user
id=xxxx;password=xxx;MultipleActiveResultSets=True;App=EntityFramework
After you set this up, You need to read the url in your application and provide the DBContext. DbContext implements a constructor with connection string parameter. By default constructor is without any parameter but you can extend this. if you are using POCO class, you can amend DbContext class simply. If you use Database generated Edmx classes like me, you dont want to touch the auto generated edmx class instead of you want to create partial class in the same namespace and extend this class as below.
This is auto generated DbContext
namespace myApp.Data.Models
{
public partial class myDBEntities : DbContext
{
public myDBEntities()
: base("name=myDBEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
}
this is the new partial class, you create
namespace myApp.Data.Models
{
[DbConfigurationType(typeof(myDBContextConfig))]
partial class myDBEntities
{
public myDBEntities(string connectionString) : base(connectionString)
{
}
}
public class myDBContextConfig : DbConfiguration
{
public myDBContextConfig()
{
SetProviderServices("System.Data.EntityClient",
SqlProviderServices.Instance);
SetDefaultConnectionFactory(new SqlConnectionFactory());
}
}
}
After all you can get the connection string from azure settings, in your Azure Function project with the code below and provide to your DbContext
myDBEntities is the name you gave in the azure portal for your connection string.
var connString = ConfigurationManager.ConnectionStrings["myDBEntities"].ConnectionString;
using (var dbContext = new myDBEntities(connString))
{
//TODO:
}
So the solution ended up being trivial. The ProviderName attribute specified in local.settings.json MUST be camel case.
From the original git hub discussions :
https://github.com/Azure/azure-functions-cli/issues/46
Shows the provider name as being pascal case
https://github.com/Azure/azure-functions-cli/issues/193
Shows the provider name being camel case in pseudo code
It was very easy to miss but your config section must be exactly as follows
"ConnectionStrings": {
"ShipBob_DevEntities": {
"ConnectionString": "metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string='data source=***;initial catalog=***;persist security info=True;User Id=***;Password=***;;multipleactiveresultsets=True;application name=EntityFramework'",
"ProviderName": "System.Data.EntityClient"
}
}
These points are important:
Make sure your connection string has metadata information
If copying your string from an xml config, make sure you unescape apostrophes
Make sure the ProviderName attribute is camel case
Make sure the provider name is System.Data.EntityClient
Fix for missing providername in deployment
Note, this answer assumes you are trying to use the parameterless constructor of a DbContext. If you are creating new code you can easily follow the second upvoted answer
I figured out a way to circumvent the provider name issue while still retaining the use of the portal config and thus deployment slots. It involves setting the default connection string of db context using static properties
private static string _connectionString = "name=ShipBob_DevEntities";
static ShipBob_DevEntities()
{
if(!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("AzureFunction")))
{
var connectionString = System.Environment.GetEnvironmentVariable("EntityFrameworkConnectionString");
if (!string.IsNullOrEmpty(connectionString))
{
_connectionString = connectionString;
}
}
}
public ShipBob_DevEntities()
: base(_connectionString)
{
this.Configuration.LazyLoadingEnabled = false;
}
This involves the developer to create an app setting in the azure portal as a flag. In my case it is AzureFunction. This makes sure that our code is only run in an azure function and all other clients of this DbContext, whether they be web apps, windows apps, etc, can still continue behaving as expected. This also involves adding your connection string to the azure portal as an AppSetting and not an actual connection string. Please use the full connection string including them metadata information but without the provider name!
EDIT
You will need to edit your auto generated .tt file t4 template to make sure this code does not get overridden if you are using db first.
Here is a link on the T4 syntax: https://learn.microsoft.com/en-us/visualstudio/modeling/writing-a-t4-text-template
And here is an explanation on EF T4 templates: https://msdn.microsoft.com/en-us/library/jj613116(v=vs.113).aspx#1159a805-1bcf-4700-9e99-86d182f143fe
I encountered the similar issue before, I would use the following approach for achieving my purpose, you could refer to it:
local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=brucchstorage;AccountKey=<AccountKey>",
"AzureWebJobsDashboard": "DefaultEndpointsProtocol=https;AccountName=brucchstorage;AccountKey=<AccountKey>",
"sqldb-connectionstring": "Data Source=.\\sqlexpress;Initial Catalog=DefaultConnection;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
},
"ConnectionStrings": {
"Bruce_SQLConnectionString": "Data Source=.\\sqlexpress;Initial Catalog=DefaultConnection;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
}
}
For retrieving the connection string:
var connString = ConfigurationManager.AppSettings["sqldb-connectionstring"];
//or var connString = ConfigurationManager.ConnectionStrings["Bruce_SQLConnectionString"].ConnectionString;
using (var dbContext = new BruceDbContext(connString))
{
//TODO:
}
Or you could init your no-argument constructor for your DbContext as follows:
public class BruceDbContext:DbContext
{
public BruceDbContext()
: base("Bruce_SQLConnectionString")
{
}
public BruceDbContext(string connectionString) : base(connectionString)
{
}
}
Then, you could create the instance for your DbContext as follows:
using (var dbContext = new BruceDbContext(connString))
{
//TODO:
}
Moreover, you could refer to Local settings file for Azure Functions.
Here are two approaches that work for me:
Approach 1
Add the connection string to the App Settings (respectively local.settings.json) in the following format:
metadata=res:///xxx.csdl|res:///xxx.ssdl|res://*/xxx.msl;provider=System.Data.SqlClient;provider connection string='data source=xxx.database.windows.net;initial catalog=xxx;user id=xxx;password=xxx;MultipleActiveResultSets=True;App=EntityFramework'`
Go to the class that extends DbContext ("TestEntities") and extend the constructor to take the connection string as argument
public partial class TestEntities: DbContext
{
public TestEntities(string connectionString)
: base(connectionString)
{
}
If you want then to interact with the database you need to retrieve the connection string from the app settings and then pass it over when initializing DbContext
string connectionString = Environment.GetEnvironmentVariable("connectionStringAppSettings");
using (var dbContext = new TestEntities(connectionString))
{
// Do Something
}
The problem with this approach is that every time you update the database you need to update the class "TestEntities" as it is overwritten
Approach 2
The goal here is to leave the class "TestEntities" as is to avoid the issue from Approach 1
Add the connection string to the App Settings (respectively local.settings.json) like in Approach 1
Leave TestEntities as is
public partial class TestEntities : DbContext
{
public TestEntities ()
: base("name=TestEntities")
{
}
As TestEntities is partial you can extend that class by creating another one that is also partial with the same name in the same namespace. The goal of this class is to provide the constructor that takes the connection string as argument
public partial class TestEntities
{
public TestEntities(string connectionString)
: base(connectionString)
{
}
}
Then you can go on like with Approach 1

Unable to find the auto created Database

I have created simple ASP.NET MVC4 application using EntityFramework Code first approach. The entity class is as below:
public class Album
{
[Key]
public int AblumId { get; set; }
public decimal Price { get; set; }
public string Title { get; set; }
}
public class MusicContext : DbContext
{
public DbSet<Album> Albums { get; set; }
}
And the web.config connection details are as below
<connectionStrings>
<add name="MusicContext" connectionString="Data Source=localhost;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>
The application runs fine and I am able to add, delete, edit the album records but I am unable to find the database. When I checked the SQL server instance using SSMS there is no database created. I have not specified "Initial Catalog" value (DBName) in the web.config and not given DBname during Context creation in the code. I even checked the DB Name at run time, but it returns empty string as shown in the screen shot below. I even checked the SQL Express, no DBs created there and not even on my other instance. So where can I find the auto created DB. I am sure DB is stored somewhere because the records are stored and retrieved without any errors.
If you don't specify a database name then the connection will use the default database for the user, in this case it's integrated security so it's your Windows login. As you likely have full system admin on the server the default database will be master so you will find all your tables in there.
For a little more clarification, master is a system database and as such, to see it in SSMS you will need to navigate to Server -> Databases -> System Databases.
To fix this problem you can either:
Specify the database name in the connection string.
Change the default database for your user.
I would recommend specifying the database name though, unless you have a need to dynamically change the database for the login, then it's always better to be explicit about things like this.
Maybe it's created a local DB inside the solution. Goto View and select Server Explorer then look in the Data Connections Drop down.

Entity Framework Code First without app.config

I hope somebody is able to help me, because it seems I'm totally stuck.
For upcoming projects in our company we'd like to use Entity Framework 5 with an code first approach. I played around a little while and everytime I try to use EF with our existing libraries, I fail because it seems EF heavily relies on an existing app.config.
In our company, we have an inhouse database library that allows us to connect to various data sources and database technologies taking the advantages of MEF (managed extensibility framework) for database providers. I just have to pass some database settings, such as host (or file), catalog, user credentials and a database provider name, the library looks for the appropriate plugin and returns me a custom connection string or IDbConnection.
We'd like to use this library together with EF because it allows us to be flexible about which database we use also change the database at runtime.
So. I saw that a typical DbContext object takes no parameters in the constructor. It automatically looks for the appropriate connection string in app.config. We don't like such things so I changed the default constructor to take a DbConnection object that get's passed to the DbContext base class. No deal.
Problems occur when the code first model changes. EF automatically notices this and looks for migration classes / configuration. But: A typical migration class requires a default parameterless constructor for the context! What a pity!
So we build our own migration class using the IDbContextFactory interface. But again, it seems that also this IDbContextFactory needs a parameterless constructor, otherwise I'm not able to add migrations or update the database.
Further, I made my own data migration configurator where I pass the context, also the target database. Problem is here: It doesn't find any migration classes, no matter what I try.
I'm completely stuck because it seems the only way to use EF is when connection strings are saved in app.config. And this is stupid because we need to change database connections at runtime, and app.config is read-only for default users!
How to solve this?
The answer is provided here
https://stackoverflow.com/a/15919627/941240
The trick is to slightly modify the default MigrateDatabaseToLatestVersion initializer so that:
the database is always initialized ...
... using the connection string from current context
The DbMigrator will still create a new data context but will copy the connection string from yours context according to the initializer. I was even able to shorten the code.
And here it goes:
public class MasterDetailContext : DbContext
{
public DbSet<Detail> Detail { get; set; }
public DbSet<Master> Master { get; set; }
// this one is used by DbMigrator - I am NOT going to use it in my code
public MasterDetailContext()
{
Database.Initialize( true );
}
// rather - I am going to use this, I want dynamic connection strings
public MasterDetailContext( string ConnectionString ) : base( ConnectionString )
{
Database.SetInitializer( new CustomInitializer() );
Database.Initialize( true );
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
public class CustomInitializer : IDatabaseInitializer<MasterDetailContext>
{
#region IDatabaseInitializer<MasterDetailContext> Members
// fix the problem with MigrateDatabaseToLatestVersion
// by copying the connection string FROM the context
public void InitializeDatabase( MasterDetailContext context )
{
Configuration cfg = new Configuration(); // migration configuration class
cfg.TargetDatabase = new DbConnectionInfo( context.Database.Connection.ConnectionString, "System.Data.SqlClient" );
DbMigrator dbMigrator = new DbMigrator( cfg );
// this will call the parameterless constructor of the datacontext
// but the connection string from above will be then set on in
dbMigrator.Update();
}
#endregion
}
Client code:
static void Main( string[] args )
{
using ( MasterDetailContext ctx = new MasterDetailContext( #"Database=ConsoleApplication801;Server=.\SQL2012;Integrated Security=true" ) )
{
}
using ( MasterDetailContext ctx = new MasterDetailContext( #"Database=ConsoleApplication802;Server=.\SQL2012;Integrated Security=true" ) )
{
}
}
Running this will cause the two databases to be created and migrated according to the migration configuration.
It needs a parameterless constructor in order to invoke it. What you could do is provide your default DbConntectionFactory in the empty constructor, something like:
public DbContext()
{
IDbContextFactory defaultFactory; //initialize your default here
DbContext(defaultFactory);
}
public DbContext(IDbContextFactory factory)
{
}

How to let user dynamically specify database provider and connection details in C# EF4.1 Code First?

I am in the process of creating a C# app to replace a VB6 app that uses a MySQL database where multiple copies of the app use the same database. The new app must be able to use the current MySQL database but I would also like it to be database agnostic so future instances of the app can use whatever server the user wants. In a perfect world, I would like the app on first run to present the user with a dialog that lets them choose the database type (MySQL, SQL Server, etc...) and specify the server ip, user, password, and database name. The app would then connect to that server and either use the database if it is already there or create a new database if it isn't.
Using Code First I have gotten to the point where I understand how to use the existing database or create a new one but only by hard coding the connection string in the App.config file.
<add name="GumpIndexDatabase"
connectionString="server=localhost;userid=123;password=123;port=3306;database=gump_new_data;pooling=false;"
providerName="MySql.Data.MySqlClient"
/>
I can change the connection string and provider before launching the app and everything works as expected. I can also change the connection string after launch, but not the provider, and I have to know whether the provider is MySQL or MSSQL in order to get the connection string details correct (ex: user or userid)
class GumpIndexDatabase: DbContext
{
public GumpIndexDatabase(string connectionName)
: base(MakeConnectionString(connectionName))
{
}
private static string MakeConnectionString(string connectionName)
{
if (connectionName=MySQL) {
//return MySQL string
} else {
//return SQL Server string
}
}
Hours of searching have not turned up an example of how to do such a thing, so I'm suspecting it isn't allowed or recommended, even though it seems like such a simple thing. I have seen some articles on connection string builder but did not understand how to get a database specific string from the generic objects.
So the simple question: how to specify the database connection details at run time?
I wouldn't recommend enforcing this feature, unless you 100% positive you cannot live without it. It adds a lot of maintenance tasks, that may not be obvious just now (such as updating to newer versions, bug fixes, spending lots of time to figure out common interfaces, maintaining those interfaces, hell lot of testing, etc). So unless it is a requested business feature - forget about it.
However, given you know what you are doing, this problem is generally solved via interfaces. There may be these common interfaces (it's a task by itslef to figure out them):
IConnection
IDataProvider
IRepository<T>
At the moment you will implement interfaces using MySql database, such as class MySqlConnection : IConnection. If you need MS SQL, class MsSqlConnection : IConnection.
Effectively you must abstract all the functionality into common interfaces. You will have to provide implementations for each database/storage engine you want to support. At runtime, you will use IoC container and DI principle to set up the current implementation. All the child dependencies will use interfaces passed in as parameters to constructor (or properties or methods)
Did you try the Database property?
GumpIndexDatabase.Database.Connection.ConnectionString = "your conn string";
I just tested it very short, so no guarantee it works without problems. But I was successful using it in the contructor of one of my service layer classes:
public class MyService
{
protected DataContext DataContext { get; set; }
public MyService(DataContext dataContext)
{
DataContext = dataContext;
DataContext.Database.Connection.ConnectionString = "conn string";
}
}
Just saw that DbContext has an overload DbContext(string nameOrConnectionString). You should be able to use this too.
Using an existing connection
Or you use an existing connection. Your DbContext should have something like this:
public class DataContext : DbContext
{
public DataContext(DbConnection existingConnection)
: base(existingConnection, true) { }
}
And then initialize it whereever you need to:
public void SomeMethod()
{
var connString = "whatever"; // could also be something like Textbox1.Text
using (var connection = new SqlConnection(connString))
{
var context = new DataContext(connection);
}
}
Of course SqlConnection can be anything that inherits from DbConnection. See DbConnection Class.

Categories

Resources