Entity Framework Code First without app.config - c#

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)
{
}

Related

How to use database sharding with EF Core and C#"

I'm currently in the process of converting my 6 years old C# application to .NET Core v3 and EF Core (and also using Blazor).
Most of it is working except for the Sharding part.
Our application creates a new database for each client. We use more or less this code for it: https://learn.microsoft.com/en-us/azure/sql-database/sql-database-elastic-scale-use-entity-framework-applications-visual-studio
I'm now trying to convert it to EF Core, but get stuck at this part:
// C'tor to deploy schema and migrations to a new shard
protected internal TenantContext(string connectionString)
: base(SetInitializerForConnection(connectionString))
{
}
// Only static methods are allowed in calls into base class c'tors
private static string SetInitializerForConnection(string connnectionString)
{
// We want existence checks so that the schema can get deployed
Database.SetInitializer<TenantContext<T>>(new CreateDatabaseIfNotExists<TenantContext<T>>());
return connnectionString;
}
// C'tor for data dependent routing. This call will open a validated connection routed to the proper
// shard by the shard map manager. Note that the base class c'tor call will fail for an open connection
// if migrations need to be done and SQL credentials are used. This is the reason for the
// separation of c'tors into the DDR case (this c'tor) and the internal c'tor for new shards.
public TenantContext(ShardMap shardMap, T shardingKey, string connectionStr)
: base(CreateDDRConnection(shardMap, shardingKey, connectionStr), true /* contextOwnsConnection */)
{
}
// Only static methods are allowed in calls into base class c'tors
private static DbConnection CreateDDRConnection(ShardMap shardMap, T shardingKey, string connectionStr)
{
// No initialization
Database.SetInitializer<TenantContext<T>>(null);
// Ask shard map to broker a validated connection for the given key
var conn = shardMap.OpenConnectionForKey<T>(shardingKey, connectionStr, ConnectionOptions.Validate);
return conn;
}
The above code doesn't compile because the Database object doesn't exist in this way in EF Core.
I assume I can simplify it using TenantContext.Database.EnsureCreated(); somewhere. But I can't figure out how to modify the methods, which to remove, which to change (and how).
Of course, I've been searching for an example using sharding and EF Core but couldn't find it.
Does anybody here has done this before in EF Core and is willing the share?
I'm specifically looking for what to put in startup.cs and how to create a new sharding/database when I create a new client.
In EF.Core just resolve the shard in OnConfiguring. EG
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var con = GetTenantConnection(this.tenantName);
optionsBuilder.UseSqlServer(con,o => o.UseRelationalNulls());
base.OnConfiguring(optionsBuilder);
}
Note that if you have a service or factory that returns open DbConnections, then you'll need to Close()/Dispose() them in the DbContext.Dispose(). If you get a connection string or a closed connection then DbContext will take care of closing the connection.
ASP.NET Core best-practices probably call for injecting an ITenantConfiguration service or somesuch in your DbContext. But the pattern is the same. Just save the injected service instance to a DbContext field and use it in OnConfiguring.
With the app that I'm working on, the desired shard is not discoverable until request time (for example, knowing what user is making the request, and then routing that user to their database). This meant that the OnConfiguring solution proposed above was not viable.
I worked around this by using IDbContextFactory<TContext>, and defining an extension on top of it, which sets the connection string based on whatever you want. I believe the database connection is created lazily in EF, and you are able to set the connection string up until the EF first needs to actually connect to the database.
In my case, it looked something like this:
var dbContext = _dbContextFactory.CreateDbContext();
var connectionString = $"DataSource={_sqlliteDirectory}/tenant_{tenant.TenantId}.db";
dbContext.Database.SetConnectionString(connectionString);
The downside is that it breaks the database abstraction (this code knows that my database is a local sqllite instance). An abstraction was not necessary in this layer of my app, but it is something very solvable if it's required.

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

Replacing DbContext in Microsoft MVC Individual User Accounts Template

I just created my first MVC Website.
To avoid having to program the controller myself, I am using the Individual User Accounts template from Microsoft.
I know, that this template uses the Entity Framework to create an express database to persist the user/account data.
Since I already have a database, which I want to use, I want to change the
template so it uses the DbContext for said database.
I was able to change the connectionString, so that the tables of the template got created in my database. But I don't want it to create it's own tables but use my already created tables.
Is there any easy way to achieve this?
Or should I just write the whole account/user controller from scratch myself?
// This is an example of DbContext class it implements DbContext
// If you do not use constructor(s) then the expectation by entity framework
// will be that your name of your connectionstring in web.config
// or app.config is name name as your class so e.g. "YourContext",
// otherwise "Name="YourConnectionString"
public class YourContext : DbContext
{
// constructor as you wish /want
public YourContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{ }
// critical mapping
public DbSet<someModel> someModel { get; set; }
// critical overide
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// critical key to NOT let your database get dropped or created etc...
Database.SetInitializer<YourContext>(null);
// This is an example of mapping model to table
// and also showing use of a schema ( dbo or another )
modelBuilder.Entity<someModel>().ToTable("someTable", schemaName: "dbo");
}
}

EF6 and multiple configurations (SQL Server and SQL Server Compact)

Update: Problem solved, see end of this question.
The problem:
We are trying to use Entity Framework 6 and code-based configuration in a scenario were we have use both a SQL Server and SQL Server CE in the same AppDomain.
This quite simple scenario seems not to be supported "by design". From the EF team:
Note: We do not support having multiple configuration classes used in
the same AppDomain. If you use this attribute to set different
configuration classes for two contexts an exception will be thrown.
More information here: Code-based Configuration (Codeplex)
The question:
How do we move forward from here? Any help would be greatly appreciated! Is there a more flexible way to connect a configuration to a context instead of an AppDomain?
(Our context classes are located in different assemblies. We have tried the DbConfigurationType attribute but the problem is EF itself)
Configuration files:
Configuration for normal SQL server
public class EfConfiguration : DbConfiguration
{
public EfConfiguration()
{
SetProviderServices(
SqlProviderServices.ProviderInvariantName,
SqlProviderServices.Instance);
SetDefaultConnectionFactory(new SqlConnectionFactory());
}
}
Configuration for SQL Server Compact Edition
public class EfCeConfiguration : DbConfiguration
{
public EfCeConfiguration()
{
SetProviderServices(
SqlCeProviderServices.ProviderInvariantName,
SqlCeProviderServices.Instance);
SetDefaultConnectionFactory(
new SqlCeConnectionFactory(SqlCeProviderServices.ProviderInvariantName));
}
}
UPDATE:
The error which we get is:
System.TypeInitializationException : The type initializer for
'MyProject.Repositories.Base.DataContext'
threw an exception. ----> System.InvalidOperationException : An
instance of 'EfCeConfiguration' was set but this type was not
discovered in the same assembly as the 'DataContext' context. Either
put the DbConfiguration type in the same assembly as the DbContext
type, use DbConfigurationTypeAttribute on the DbContext type to
specify the DbConfiguration type, or set the DbConfiguration type in
the config file. See http://go.microsoft.com/fwlink/?LinkId=260883 for
more information.
UPDATE 2, the solution
As described above, we can only have one configuration. This is a problem since Sql and SqlCe uses different providers. If we use "SetDefaultConnectionFactory" to fit one type of database, the other will fail.
Instead, supply the connection into the context as described in the post marked as answer below. Once you always initialize the context with a connection as opposed to a connectionstring you are good to go. You can remove the SetDefaultConnectionFactory call from the configuration. We're using only the code below for configuring the SqlCe Context and no configuration for the Sql Context.
public class CommonEfConfiguration : DbConfiguration
{
public CommonEfConfiguration()
{
// EF does not know if the ce provider by default,
// therefore it is required to be informed about it.
// The connection factories are not necessary since the connection
// is always created in the UnitOfWork classes
SetProviderServices(SqlCeProviderServices.ProviderInvariantName, SqlCeProviderServices.Instance);
}
}
EDIT: based On Error details:
Did you already try tell EF where the config class is found?
[DbConfigurationType("MyNamespace.MyDbConfiguration, MyAssemblyFullyQualifiedName")]
public class MyContextContext : DbContext
{
}
If that cant be made work, then see alternative
Use the Context with constructor DbConnection
public class MYDbContext : DbContext {
// MIgration parameterless constructor is managed in MyMigrationsContextFactory
public MyDbContext(string connectionName) : base(connectionName) { } // no this
public MYDbContext(DbConnection dbConnection, bool contextOwnsConnection) // THIS ONE
: base(dbConnection, contextOwnsConnection) { }
you then need a "DBConnection" connection for each provider.
For SQL server
public DbConnection GetSqlConn4DbName(string dataSource, string dbName) {
var sqlConnStringBuilder = new SqlConnectionStringBuilder();
sqlConnStringBuilder.DataSource = String.IsNullOrEmpty(dataSource) ? DefaultDataSource : dataSource;
sqlConnStringBuilder.IntegratedSecurity = true;
sqlConnStringBuilder.MultipleActiveResultSets = true;
var sqlConnFact = new SqlConnectionFactory(sqlConnStringBuilder.ConnectionString);
var sqlConn = sqlConnFact.CreateConnection(dbName);
return sqlConn;
}
repeat for SqlCe factory, it can also generate a DBConnection
SqlCe connection factor create connection
what i did:
public partial class MyDataBaseContext : DbContext
{
public MyDataBaseContext (string ConnectionString)
: base(ConnectionString)
{
}
}
I found the solution in a post on a Microsoft forum post.
Basically, I had two projects, each one with its own context. Entity Framework was loading just (the first) one of the DbConfiguration classes and trying to use this same configuration for both projects. That's the reason for the error message saying something like
"An instance of 'EfCeConfiguration' was set but this type was not discovered in the same assembly as the 'DataContext' context".
So, as someone suggested in that Microsoft forum post, I removed all [DbConfigurationType(typeof(DbConfigurationClass))] anotations from the classes which inherit from DbContext in both projects, and the error didn't happen anymore.

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