I'm migrating to Entity Framework v6 and I'm struggling to build code that will let me define my SQL 2008R2 database connection in the code. I cannot store the connection string information within the app.config file due to this library being a dll that multiple applications will be using. The idea is to maintain all the Database connections within 1 dll without having to reference the entity-libraries in the front-ends nor specify a connection string.
With EF5 I was able to use a partial class and define the connection string in the DBContext, that method does not seem to work with EF6. I'd like an example of an EF6 SQL database connection entirely defined within code. Most of the examples of EF6 out there are for code-first models, I already have the database tables, I just need to build the interface.
-Hiram
(assuming that you are using the EF Designer)
You can't just pass a connection string to the DbContext when using the code generated from the EF6 Designer because the DbContext needs the information created from the EDMX. But you can still create a partial class that has a constructor that accepts a connection string. You will just have to create an ObjectContext and pass that to the DbContext constructor.
Here is an example:
using System.Data.Entity.Core.EntityClient;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Core.Objects;
using System.Data.SqlClient;
namespace Northwind.Model {
public partial class NorthwindEntities {
public NorthwindEntities(string connectionString)
: base(GetObjectContext(connectionString), true) {
}
private static ObjectContext GetObjectContext(string connectionString) {
// You can use the metadata portion of the connection string the the designer added to your config for the paths
var paths = new[] {
"res://*/Northwind.csdl",
"res://*/Northwind.ssdl",
"res://*/Northwind.msl"
};
var workspace = new MetadataWorkspace(paths, new[] { typeof(NorthwindEntities).Assembly });
var connection = new EntityConnection(workspace, new SqlConnection(connectionString));
return new ObjectContext(connection);
}
}
}
You can still define the connection string in the DBContext in EF6.
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext()
: base(#"Your connection string here") { }
// Rest of your DbContext code
}
But hard coding a connection string in there isn't very versatile. Even though your DbContext will be in it's own dll, it can still read the app.config or web.config of your primary project if it is in the same solution (and I'm fairly sure it will work even if you add your DbContext dll as a reference).
Just add a reference to System.Configuration in your DbContext project, and then you can get at the connection string with either ConfigurationManager.ConnectionStrings["MyConnectionStringName"].ConnectionString or ConfigurationManager.AppSettings["MyConnectionStringName"]
And you would store the connection string in your primary applications web.config in the <connectionStrings> section OR in the 'app.config' in the <appSettings> section
Note that if you do it this way (by reading from web.config or app.config), you should change your DbContext code accordingly:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext()
: base("MyConnectionStringName") { }
// Rest of your DbContext code
}
Related
EF code first migrations require me to put a default constructor on my DbContext class, this requires me to know the exact connection string - however, I don't know the connection string yet.
My context and the entities reside in a MyProject.Data assembly with no information about the actual database whatsoever. The connection string is in the MyProject.Executable's app.config file. Still I want to put all the migrations into the MyProject.Data project, because that is where they belong.
Is there any way to do EF migrations without having a default CTOR, etc.?
If the connectionstring is in the app.config/Web.config of your main project (the one you will be building), you can use ConfigurationManager.ConnectionStrings to retrieve them.
If you are wondering how it will work if it's in a different project: This will only be executed at runtime. At runtime, your projects have been compiled into one big application, and everything listens to the same app.config/Web.config file (the one that was in your startup project when you built it).
From the link:
var connectionString =
ConfigurationManager.ConnectionStrings["WingtipToys"].ConnectionString;
using (var connection = new SqlConnection(connectionString))
{
//...
}
The same applies for any constructor where you need to know the connectionstring, like your DBContext.
According to this SO answer, you only need to pass the name of your connectionstring, so that means you don't even need to use ConfigurationManager.
From the linked answer:
After reading the docs, I have to pass the name of the connection string instead:
var db = new NerdDinners("NerdDinnerDb");
You can refer to the name of connection string placed in your default start up project. This is why parameter of DbContext constructor is nameOrConnectionString.
public class MyContext : DbContext
{
public MyContext()
: base("Name=NameOfConnectionString")
{
}
}
I have three database environments, Developer, Testing and Production. To setup database with sample data i created a console app where user selects environment to setup the database. I am using Entity Framework database first but stuck with how to select instance at run time. There is only one database model is it possible to change db connection at run time?
i used following code and it throws exception.
// Truncate all Data
if (env.Key == ConsoleKey.D)
{
db.Database.Connection.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["db_dev"].ToString();
}
Model Entities has no constructor to get Connection String..
Ensure you have a constructor on your DbContext class that takes a connection string and then simply pass it to the base class (Entity Framework will do the rest)
public MyDbContext : DbContext
{
public MyDbContext(string connectionString) : base (connectionString)
{
}
}
Then when you instantiate your Context you simply pass in the connection string that you would like to use... example using your code above would be...
var connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["db_dev"].ToString();
using (var context = new MyDbContext(connectionString))
{
}
When you're creating your instance of database context (db variable) there should be a constructor overload that accepts a string. That's how you can set it up with your custom connection string.
If your class that inherits from the DbContext doesn't have that overload, just create it because the base class does have it.
I wish to pass a dynamic connection string to the entity framework context. I have over 150 schemas which are identical (one per account) and I would like to select the connection as such:
ApplicationDbContext db = new ApplicationDbContext("dbName");
In theory this would be fairly easy, as I can create a connectionString and pass it as the argument for the constructor, for example:
public ApplicationDbContext(string dbName) : base(GetConnectionString(dbName))
{
}
public static string GetConnectionString(string dbName)
{
// The connectionString passed is something like:
// Server=localhost;Database={0};Uid=username;Pwd=password
var connString = ConfigurationManager
.ConnectionStrings["MyDatabase"]
.ConnectionString
.ToString();
return String.Format(connString, dbName);
}
I can connect successfully when I just pass the connection string name, but not when I generate it dynamically as below. I realize now that it's because the connection string in web.config has the providerName="MySql.Data.MySqlClient" attribute in it.
When I pass the actual connection string dynamically to the connection though, it assumes that it needs to connect to SQL Server rather than MySQL and fails due to the connection string being invalid.
The question is, how do I pass the provider name to the connection string if I am creating it dynamically?
Entity Framework 6 offers some handy subtle changes which aid in both getting MySQL working and also creating dynamic database connections.
Getting MySQL working with Entity Framework 6
First, at the date of my answering this question, the only .Net connector drivers compatible with EF6 is the MySQL .Net Connectior 6.8.1 (Beta development version) which can be found at the official MySQL website here.
After installing, reference the following files from your Visual Studio solution:
Mysql.Data.dll
Mysql.Data.Entity.EF6.dll
You will also need to copy these files somewhere where they will be accessible to the project during build time, such as the bin directory.
Next, you need to add some items to your Web.config (or App.config if on desktop based) file.
A connection string:
<connectionStrings>
<add name="mysqlCon"
connectionString="Server=localhost;Database=dbName;Uid=username;Pwd=password"
providerName="MySql.Data.MySqlClient" />
</connectionStrings>
Also add the provider, inside the <entityFramework /> and <providers /> nodes, optionally (this is an absolute must in the second part of my answer, when dealing with dynamically defined databases) you may change the <defaultConnectionFactory /> node:
<entityFramework>
<defaultConnectionFactory type="MySql.Data.Entity.MySqlConnectionFactory, MySql.Data.Entity.EF6" />
<providers>
<provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6" />
</providers>
</entityFramework>
If you change the defaultConnectionFactory from the default sql server connection, don't forget to remove the <parameter> nodes which are nested in the defaultConnectionFactory node. The MysqlConnectionFactory does not take any parameters for its constructor and will fail if the parameters are still there.
At this stage, it's quite easy to connect to MySQL with Entity, you can just refer to the connectionString above by name. Note that if connecting by name, this will work even if the defaultConnectionFactory node still points at SQL Server (which it does by default).
public class ApplicationDbContext: DbContext
{
public ApplicationDbContext() : base("mysqlCon")
{
}
}
The it is just a matter of connecting normally:
ApplicationDbContext db = ApplicationDbContext();
Connecting to a dynamically selected database name
At this point it's easy to connect to a database which we can pass as a parameter, but there's a few things we need to do.
Important Note
If you have not already, you MUST change the defaultConnectionFactory in Web.config if you wish to connect to MySQL
dynamically. Since we will be passing a connection string directly to
the context constructor, it will not know which provider to use and
will turn to its default connection factory unless specified in
web.config. See above on how to do that.
You could pass a connection string manually to the context like this:
public ApplicationDbContext() : base("Server:localhost;...")
{
}
But to make it a little bit easier, we can make a small change to the connection string we made above when setting up mySQL. Just add a placeholder as shown below:
<add name="mysqlCon" connectionString="Server=localhost;Database={0};Uid=username;Pwd=password" providerName="MySql.Data.MySqlClient" />
Now we can build a helper method and change the ApplicationDbContext class as shown below:
public class ApplicationDbContext: DbContext
{
public ApplicationDbContext(string dbName) : base(GetConnectionString(dbName))
{
}
public static string GetConnectionString(string dbName)
{
// Server=localhost;Database={0};Uid=username;Pwd=password
var connString =
ConfigurationManager.ConnectionStrings["mysqlCon"].ConnectionString.ToString();
return String.Format(connString, dbName);
}
}
If you are using database migrations, the following step is important
If you are using migrations, you will find that the ApplicationDbContext will be passed to your Seed method by the framework and it will fail because it will not be passing in the parameter we put in for the database name.
Add the following class to the bottom of your context class (or anywhere really) to solve that problem.
public class MigrationsContextFactory : IDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext Create()
{
return new ApplicationDbContext("developmentdb");
}
}
Your code-first migrations and seed methods will now target the developmentdb schema in your MySQL database.
Hope this helps someone :)
It's now 2019 of course things have changed a bit but Franciso's example really helped me on this. This is the simplest solution I could find and the only one that actually worked. I did change it a bit from what he has shown. Follow this to completion you should end up with a working solution.
I had to change a few things. I am going to be very explicit in what has to be done and I am going to use my actual file names etc so that you don't have to guess about substitutions. Many examples are also short on how to make it work at the end. This example has everything you need to know.
This was built on visual studio 2015 Entityframework 6 using MySql server 8.0.16.0.
Unfortunately the MySql connectors and libraries are a complete mess. The 8.0.xx.0 connector / net and MySql.Data.Entity.EF6 and MySql.Data are completely useless.
I have installed Connector Net 6.10.7.0, MySql.Data.Entity.EF6 6.10.7.0, and MySql.Data 6.10.7.0. That works for me and I will vigorously oppose changing this.
This is for MySql but I really don't know why it could not work for any db.
Scenario
I have a multi tenant situation where I have a common db and multiple tentant databases, one per customer The customer id is kept in the common db for login purposes and authorizaton and the customer id directs which database to use. The client db's are all called myclientdb_x where x is the client number. myclientdb_1, myclientdb_2, myclientdb_35 and so on.
I need to dynamically switch to whatever clientdb_x the code is currently serving. There is a initial database client called myclient_0 which is the template for all of the other myclient_x databases.
Step1
I created a specific connection string in my Web.config for this it looks like this. It allows connections to the clientdb_0
<add name="DefaultClientConnection" providerName="MySql.Data.MySqlClient"
connectionString="server=localhost;user id=xxx;
password=xxxx; persistsecurityinfo=True;database=clientdb_0" />
Step2
I created a new entity called ClientDbUserUpdater using the wizard. The data entity is called
ClientDbUserUpdater.edmx
I told it to use "DefaultClientConnection" as the DB connection
I told it to save this new connection string in the Web.config
This created new entity connection string in the Web.config file and it will look like
<add name="myclient_0Entities" connectionString="metadata=
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;
provider=MySql.Data.MySqlClient;provider connection string="
server=localhost;user id=xxxx;password=yyyyy;
persistsecurityinfo=True;database=myclient_0"" providerName="System.Data.EntityClient" />
You might have to dig a bit because the wizard is not good about putting in \n in appropriate places.
Notice that this connection string is fundamentally the same as the initial connection string except for its name and the fact that it has
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;
The res: strings are needed by the data entity and its why you can't just send a standard connection string into the data entity.
If you try to send in the initial connection string
<add name="DefaultClientConnection" providerName="MySql.Data.MySqlClient"
connectionString="server=localhost;user id=xxx;
password=xxxx; persistsecurityinfo=True;database=clientdb_0" />
you will get an exception from
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
Step3
This new connection string is the one you need to alter. I have not tested it but I am pretty sure if change the data entity model with the wizard you will need to make this change again.
Take string:
<add name="myclient_0Entities" connectionString="metadata=
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;
provider=MySql.Data.MySqlClient;provider connection string="
server=localhost;user id=xxxx;password=yyyyy;
persistsecurityinfo=True;database=myclient_0"" providerName="System.Data.EntityClient" />
and change it to:
<add name="myclient_0Entities" connectionString="metadata=
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;
provider=MySql.Data.MySqlClient;provider connection string="
server=localhost;user id=xxxx;password=yyyyy;
persistsecurityinfo=True;database={0}"" providerName="System.Data.EntityClient" />
Notice that the only part changed is database=myclient_0 to database={0}
Step 4
The data entity created some code behind ClientDbUserUpdater.edmx. The file is called ClientDbUserUpdater.Context.cs.
The code is ...
namespace what.ever.your.namespace.is
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
public partial class client_0Entities : DbContext
{
public client_0Entities()
: base("name=client_0Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<user> users { get; set; }
}
}
Notice that this a partial class. This means you can extend this class and add a new constructor.
Add the following class.
using System;
using System.Configuration ;
using System.Data.Entity ;
namespace what.ever.your.namespace.is
{
public partial class client_0Entities : DbContext
{
public client_0Entities(string dbName) : base(GetConnectionString(dbName))
{
}
public static string GetConnectionString(string dbName)
{
var connString = ConfigurationManager.ConnectionStrings["client_0Entities"].ConnectionString.ToString();
// obviously the next 2 lines could be done as one but creating and
// filling a string is better for debugging. You can see what happened
// by looking a conn
// return String.Format(connString, dbName);
string conn = String.Format(connString, dbName);
return conn ;
}
}
}
The class adds a new constructor which allows you to get the base connection string for the data entity model which from above looks like:
<add name="myclient_0Entities" connectionString="metadata=
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;
provider=MySql.Data.MySqlClient;provider connection string="
server=localhost;user id=xxxx;password=yyyyy;
persistsecurityinfo=True;database={0}"" providerName="System.Data.EntityClient" />
and modfiy it at run time to change the schema.
The String.Format() call in the new partial class swaps out the database schema name in this connection string at run time.
At this point all configuration is done.
Step 5
Now you can make it go. For better understanding of this example it is nice to know what the model looks like for this entity. It is very simple because I was just testing and trying to make it go.
Drilling down through ClientDbUserUpdater.edmx and into into ClientDbUserUpdater.tt you will find your model in modelname.cs . My model is called "user" so my file name is called user.cs
namespace what.ever.your.namespace.is
{
using System;
using System.Collections.Generic;
public partial class user
{
public int UserId { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Nullable<bool> Active { get; set; }
}
}
Now you can generally access your model like this.
client_0Entities _client_0Entities = new client_0Entities("schemaName");
and this code can be anywhere in your solution that can see class client_0Entities.
which in practice is a line similar to any of the 3 below which are connection to databases client_19, client_47 and client_68 respectively.
client_0Entities _client_0Entities = new client_0Entities("client_19");
client_0Entities _client_0Entities = new client_0Entities("client_47");
client_0Entities _client_0Entities = new client_0Entities("client_68");
the following is an actual code example that works on my system. Obviously I am going to not hard code in "client_19" but its better for demo purposes.
here is actual code with real names that works and adds a new row to the user table on database client_19
string _newSchema = "client_19"
using(client_0Entities _client_0Entities = new client_0Entities(_newSchema))
{
user _user = new user();
_user.UserId = 201;
_user.Email = "someone#someplace.com"
_user.FirstName ' "Someone";
_user.LastName = "New";
_user.Active = true;
client_0Entities.users.Add ( _user ) ;
client_0Entities.SaveChangesAsync ( ) ;
}
Hopefully this helps some people. I spent about 20 hrs looking at different solutions which simply did not work or provide enough information to complete them. As I said, finding Franciso's example allowed me to get it working.
Regards,
I am looking at using MVC5 to compliment an existing ASP.Net application (that is not MVC).
The app has been configured using database first.
I need to change the database connection string at runtime depending on who is logged in. (This is easy to do in the current app through data adapters).
There are over 1000 existing databases and more can created at runtime - so Web.config is not an option. Schema is common to all databases.
So far, I have managed to switch the database on the Controller - but this means changing substantially the generated code - there must be an easier way! - Help!
Added comment:
The model is a SaaS accounting application. Each database stores all ledgers for a client company and has approximately 125 tables within it. They are kept separate for security and also portability (some accountants even require to download the SQL data in it's entirety for their clients).
Example code:
The site is built on the Contoso University model:
http://www.asp.net/mvc/tutorials/mvc-5/database-first-development/setting-up-database
It works in its basic form, but I need the connection changed for each clients data:
So looking at the Sales ledger - under the Sales controller we have:
public class SalesController : Controller
{
private Sales db = new Sales();
.....
The Sales definition is:
public partial class Sales : DbContext
{
public Sales()
: base("name=Sales")
{
}
The "name=Sales" links to the WebConfig and gives it the starting database.
Looking at the : base definition takes me to System.Data.Entity.DbContext which is a locked file!
I have trawled through various sites and tried putting the connection string in the Sales class the following being one of the suggestions:
public Sales(string connString) : base (connString)
but this throws:
Code generated using the T4 templates for Database First and Model First development may not work correctly if used in Code First mode. To continue using Database First or Model First ensure that the Entity Framework connection string is specified in the config file of executing application. To use these classes, that were from Database First or Model First, with Code First add any additional configuration using attributes or the DbModelBuilder API and then remove the code that throws this exception
Line 32: protected override void OnModelCreating(DbModelBuilder modelBuilder)
Line 33: {
Line 34: throw new UnintentionalCodeFirstException();
Line 35: }
Line 36:
That's where I am stuck - even if I use the same connection string as in the WebConfig!!
(It mentions Code First mode - which it wasn't?)
You could have your data access layer access an interface providing the connection string it uses? In the same way, if using EF, it might be a context rather than a connection string
public interface class IClient
{
string ConnectionString {get;set;}
}
public class DataAccess
{
private IClient _connectionString;
public DataAccess(IClient client)
{
_connectionString = client.ConnectionString;
}
}
This of course would change slightly based on your design. I would probably have the authentication for all clients on the same db, then based on who authenticates a factory could return an IClient which could then be used by DAL, Repository or whatever you have. IClient would not really just have a connection string or context, you could have other properties or methods on there too, relevant to requirements.
Internally, the Repository (for example) might use that context or connection string in the constructor, so it establishes the correct connection. Methods would not change.
Add a new parameterised Constructor as explained below.
In "Connectionstring" pass your connection like:
string Connectionstring="Data Source=;Initial Catalog=abc;Persist Security Info=True;
User ID=dsds;Password=dsdsd;MultipleActiveResultSets=True";
public Sales()
: base("name=Sales")
{
}
public Sales(string Connectionstring)
: base(Connectionstring)
{
}
I am developing a client system that needs a small local database.
I want to avoid installation of SQL Server Express and have decided to go with SQL Server 4.
I use Entity Framework 5 for data access and have created my custom context.
Everything works fine in development where I can use app.config to either set specific file location or dynamic Data Source=|DataDirectory|\MyDatabase.sdf.
But on deploy I want the database to be located in the users documents folder:
\My Documents\ApplicationName\MyDatabase.sdf
How can I do that?
All I need is actually to be able to set custom connection string in code!
This is what I tried so far:
private MyApplicationDataContext(string connectionString)
: base(connectionString)
{
}
public static MyApplicationDataContext CreateInstance()
{
var directory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var path = Path.Combine(directory, #"ApplicationName\MyDatabase.sdf");
//var connectionString = string.Format("provider=System.Data.SqlServerCe.4.0;provider connection string=\"Data Source={0}\"", path);
var connectionString = string.Format("Data Source={0}", path);
return new MyApplicationDataContext(connectionString);
}
As you can see I tried two kinds of connection strings but both caused exceptions.
Keyword not supported: 'provider'.
and
The provider did not return a ProviderManifestToken string.
Ah, I finally got it right!
I include the adjusted code if someone else has the same problem.
The trick was to set the connection string on the Database.DefaultConnectionFactory
private MyApplicationDataContext()
{ }
public static MyApplicationDataContext CreateInstance()
{
var directory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var path = Path.Combine(directory, #"ApplicationName\MyDatabase.sdf");
// Set connection string
var connectionString = string.Format("Data Source={0}", path);
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0", "", connectionString);
return new MyApplicationDataContext();
}
In EF 6 this can be done with DbConfiguration:
App.config:
<entityFramework codeConfigurationType="MyDbConfiguration, MyAssembly">
</entityFramework>
And inside your assembly create a class like:
public class MyDbConfiguration : DbConfiguration
{
public MyDbConfiguration()
{
SetProviderServices(SqlCeProviderServices.ProviderInvariantName, SqlCeProviderServices.Instance);
var directory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var path = Path.Combine(directory, #"ApplicationName\MyDatabase.sdf");
var connectionString = string.Format(#"Data Source={0}",path);
SetDefaultConnectionFactory(new SqlCeConnectionFactory(SqlCeProviderServices.ProviderInvariantName, "", connectionString));
}
}
The SqlCeConnection instance is used to connect to Sql Ce database file. If I want to connect to MSSQL database, I will use SqlConnectionStringBuilder and SqlConnection instances to build my DbConnection instance.
// The ctor for `DbConnection`.
private MyApplicationDataContext(DbConnection conn) : base(conn, true) {}
public static MyApplicationDataContext CreateInstance()
{
var directory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var path = Path.Combine(directory, #"ApplicationName\MyDatabase.sdf");
// Connection string builder for `Sql ce`
SqlCeConnectionStringBuilder sb = new SqlCeConnectionStringBuilder();
sb.DataSource = path;
// DbConnection for `Sql ce`
SqlCeConnection dbConn = new SqlCeConnection(sb.ToString());
return new MyApplicationDataContext(dbConn);
}
For EF 6
I arrived at this answer thanks to Matthias's post, but included some worthwhile info from Entity Framework Code-Based Configuration (EF6 onwards).
To specify a custom database location you will need to do some configuration. Configuration for an Entity Framework application can be specified in a config file (app.config/web.config) or through code. The latter is known as code-based configuration. Given that my project required the database location to be set dynamically, I went with the code-based configuration, which is described below.
(Note that the config file takes precedence over code-based configuration. In other words, if a configuration option is set in both code and in the config file, then the setting in the config file is used.)
According to EF documentation (above link) there are 4 approaches to implement your custom configuration,which include: Using DbConfiguration, Moving DbConfiguration, Setting DbConfiguration explicitly, and Overriding DbConfiguration.
Using DbConfiguration
Code-based configuration in EF6 and above is achieved by creating a subclass of System.Data.Entity.Config.DbConfiguration. The following guidelines should be followed when subclassing DbConfiguration:
Create only one DbConfiguration class for your application. This class specifies app-domain wide settings.
Place your DbConfiguration class in the same assembly as your DbContext class. (See the Moving DbConfiguration section if you want to change this.)
Give your DbConfiguration class a public parameterless constructor.
Set configuration options by calling protected DbConfiguration methods from within this constructor.
Following these guidelines allows EF to discover and use your configuration automatically by both tooling that needs to access your model and when your application is run.
Example (modified from Matthias's answer):
public class MyDbConfiguration : DbConfiguration
{
public MyDbConfiguration()
{
SetProviderServices(SqlCeProviderServices.ProviderInvariantName, SqlCeProviderServices.Instance);
//var directory Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var directory = #"C:\Users\Evan\Desktop\TestFolder"; // Directory may or may not already exist
Directory.CreateDirectory(directory); // create directory if not exists
var path = Path.Combine(directory, #"ApplicationName\MyDatabase.sdf");
var connectionString = string.Format(#"Data Source={0}",path);
SetDefaultConnectionFactory(new SqlCeConnectionFactory(SqlCeProviderServices.ProviderInvariantName, "", connectionString));
}
}
Note that the config file does not need to be altered unless there are existing configuration settings that override your custom configuration. Also, I changed the directory to illustrate that While EF is capable of creating a new database if it doesn't already exist, it will not create parent directories, which is why I included this line: Directory.CreateDirectory(directory). Given that this approach worked for my project I didn't explore the remaining 3 configuration methods, but you can find info on them in the link provided above and I will include the documentation here as a reference in case the link breaks.
Moving DbConfiguration
There are cases where it is not possible to place your DbConfiguration class in the same assembly as your DbContext class. For example, you may have two DbContext classes each in different assemblies. There are two options for handling this.
The first option is to use the config file to specify the DbConfiguration instance to use. To do this, set the codeConfigurationType attribute of the entityFramework section. For example:
<entityFramework codeConfigurationType="MyNamespace.MyDbConfiguration, MyAssembly">
...Your EF config...
</entityFramework>
The value of codeConfigurationType must be the assembly and namespace qualified name of your DbConfiguration class.
The second option is to place DbConfigurationTypeAttribute on your context class. For example:
[DbConfigurationType(typeof(MyDbConfiguration))]
public class MyContextContext : DbContext
{
}
The value passed to the attribute can either be your DbConfiguration type - as shown above - or the assembly and namespace qualified type name string. For example:
[DbConfigurationType("MyNamespace.MyDbConfiguration, MyAssembly")]
public class MyContextContext : DbContext
{
}
Setting DbConfiguration explicitly
There are some situations where configuration may be needed before any DbContext type has been used. Examples of this include:
Using DbModelBuilder to build a model without a context
Using some other framework/utility code that utilizes a DbContext
where that context is used before your application context is used
In such situations EF is unable to discover the configuration automatically and you must instead do one of the following:
Set the DbConfiguration type in the config file, as described in the
Moving DbConfiguration section above
Call the static DbConfiguration.SetConfiguration method during
application startup
Overriding DbConfiguration
There are some situations where you need to override the configuration set in the DbConfiguration. This is not typically done by application developers but rather by thrid party providers and plug-ins that cannot use a derived DbConfiguration class.
For this, EntityFramework allows an event handler to be registered that can modify existing configuration just before it is locked down. It also provides a sugar method specifically for replacing any service returned by the EF service locator. This is how it is intended to be used:
At app startup (before EF is used) the plug-in or provider should
register the event handler method for this event. (Note that this
must happen before the application uses EF.)
The event handler makes a call to ReplaceService for every service
that needs to be replaced.
For example, to repalce IDbConnectionFactory and DbProviderService you would register a handler something like this:
DbConfiguration.Loaded += (_, a) =>
{
a.ReplaceService<DbProviderServices>((s, k) => new MyProviderServices(s));
a.ReplaceService<IDbConnectionFactory>((s, k) => new MyConnectionFactory(s));
};
In the code above MyProviderServices and MyConnectionFactory represent your implementations of the service.
You can also add additional dependency handlers to get the same effect.
Note that you could also wrap DbProviderFactory in this way, but doing so will only effect EF and not uses of the DbProviderFactory outside of EF. For this reason you’ll probably want to continue to wrap DbProviderFactory as you have before.
You should also keep in mind the services that you run externally to your application - e.g. running migrations from Package Manager console. When you run migrate from the console it will attempt to find your DbConfiguration. However, whether or not it will get the wrapped service depends on where the event handler it registered. If it is registered as part of the construction of your DbConfiguration then the code should execute and the service should get wrapped. Usually this won’t be the case and this means that tooling won’t get the wrapped service.