Change Database on the fly in Entity Framework same scheme - c#

I have a context which I derive my entity I have 5 databases these database are off the same scheme that I need to switch from at run time. The user would be selecting at the launch of the application which database they would be connecting to.
The database that is of the same scheme is SMBASchedulerEntities its just the catalogue name that is different
public class SourceContext : ContextBase
{
public SMBASchedulerEntities _sourceEntities = new SMBASchedulerEntities();
public SystemDa _systemDB = new SystemDa();
public void AddToPatient(Patient newPatient)
{
_sourceEntities.Patients.Add(newPatient);
SaveChanges();
}
public void AddToAppointmentTypes(AppointmentType AppointmentTypes)
{
_sourceEntities.AppointmentTypes.Add(AppointmentTypes);
SaveChanges();
}
}
As you can see there I reference the entities within my context so I would like to have a property that I can call such as changeDatabase and that it would take affect without restarting the application is that at all possible.

You can pass the name of the connection string you want to connect to, when instantiating your DbContext. First, you declare your DbContext like so:
public class SMBASchedulerEntities : DbContext
{
public SMBASchedulerEntities(string connectionString): base(connectionString)
{
}
}
You keep all your connection strings in your Web.config or App.config (depending on the type of project):
<connectionStrings>
<add name="DefaultConnection1" connectionString="server=localhost;user id=MyAppUser;password=SecretPass;database=MyDatabase1" providerName="System.Data.SqlClient" />
<add name="DefaultConnection2" connectionString="server=localhost;user id=MyAppUser;password=SecretPass;database=MyDatabase2" providerName="System.Data.SqlClient" />
<add name="DefaultConnection3" connectionString="server=localhost;user id=MyAppUser;password=SecretPass;database=MyDatabase3" providerName="System.Data.SqlClient" />
<add name="DefaultConnection4" connectionString="server=localhost;user id=MyAppUser;password=SecretPass;database=MyDatabase4" providerName="System.Data.SqlClient" />
<add name="DefaultConnection5" connectionString="server=localhost;user id=MyAppUser;password=SecretPass;database=MyDatabase5" providerName="System.Data.SqlClient" />
</connectionStrings>
Then you use it like this:
using (var db = new SMBASchedulerEntities("DefaultConnection1"))
{
// use MyDatabase1 through connection string "DefaultConnection1"
}
using (var db = new SMBASchedulerEntities("DefaultConnection2"))
{
// use MyDatabase2 through connection string "DefaultConnection2"
}
Dispose your DbContext
It's recommended to dispose the DbContext after using it. If you still want to use your idea with SourceContext, you could implement something like this:
public class SourceContext : ContextBase, IDisposable
{
public SMBASchedulerEntities _sourceEntities;
public SystemDa _systemDB = new SystemDa();
public SourceContext(string connectionString)
{
_sourceEntities = new SMBASchedulerEntities(connectionString);
}
public void AddToPatient(Patient newPatient)
{
_sourceEntities.Patients.Add(newPatient);
SaveChanges();
}
public void ChangeDatabaseTo(string connectionString)
{
if (_sourceEntities != null)
_sourceEntities.Dispose();
_sourceEntities = new SMBASchedulerEntities(connectionString);
}
public void AddToAppointmentTypes(AppointmentType AppointmentTypes)
{
_sourceEntities.AppointmentTypes.Add(AppointmentTypes);
SaveChanges();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_sourceEntities != null)
{
_sourceEntities.Dispose();
}
}
}
}
...then finally use it like this:
using(var context = new SourceContext("DefaultConnection1"))
{
context.AddPatient(patient); // add to Database1
context.ChangeDatabaseTo("DefaultConnection2");
context.AddPatient(patient); // add to Database2
context.ChangeDatabaseTo("DefaultConnection4");
context.AddPatient(patient); // add to Database4
}
By making your SourceContext an IDisposable, you have the opportunity to properly dispose the DbContext instance. Take note I took care of disposing the existing DbContext before changing it.

So, turns out the easiest way to do this is to put all of the DB names and connection strings in the appsettings.json file.
You can make it very complex, of course, but what I did was make a select drop down, let the user select the DB they want to use, then fire the onclick method to pull the connection string out of the app settings.json file, create an instance of the DBCONTEXT with that name’s connection string and use that DB context throughout whatever you need from there.
Public static class AppSettings
{
public static string GetConnectionString(string sDBinstanceName)
{
IConfigurationRoot Configuration;
IConfigurationBuilder builder = new ConfigurationBuilder().SetpathName(<wherever your appsettings.json file is>,”appsettings.json”).ReadJsonFile();
Configuration = builder.Build();
var tmpSettings = Configuration.GeyAppSettings();
string dbConstr = tmpSettings[“ConnectionStrings”][sDBinstanceName];
return dbConstr;
}
}
This is not MY snippet, but a derivative of what I found online. As it is from memory, it is not 100% correct, but I will fix it on Monday. I will update with author when I find it again(Monday).
Once you get the connection string you treat the DBCONTEXT like any other object, create it, use the tables (MEF) and whatever else you need, then destroy it correctly or let the garbage collection clean it up.
I got this working yesterday and please note: There are ZERO DBCONTEXTs registered in the Program.cs file as they are not needed.
This system is simple, lets you allow the user control to which Database they need based on a simple selection mechanism and answers the question.
At our shop we have MULTIPLE databases with exact same schema because we segregate data based on the timeframe for that data, but I can envision situations where you could use different schemas depending on the selection, we just do not do that.
For instance we have a Production and Development DB that are EXACTLY the same schema (different data) so the one DBCONTEXT is used for both in MEF.
With the method above we can let the user see the data in either at their command, it’s just getting there is a whole lot easier.

Related

Correct implementation of DAL with Entity Framework & Repository

I'm having some difficulty and confusion setting up my MVC/WebAPI project with a separate DAL project.
I have a class library project titled "Dashboard.Data", where I generated a code-first to existing database. This class library now contains all my model classes as well as my Repository. I've yet to implement a generic repository but what I have so far should still work.
Repository:
namespace Dashboard.Data.Repository
{
public class ProductRepository : IProductRepository, IDisposable
{
private DatabaseContext _context;
public ProductRepository()
{
_context = new DatabaseContext();
}
public ProductRepository(DatabaseContext context)
{
this._context = context;
}
public async Task<IEnumerable<Department>> GetDepartmentList()
{
return await _context.Departments.ToListAsync();
}
protected void Dispose(bool disposing)
{
if (disposing)
if (_context != null)
{
_context.Dispose();
_context = null;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
IRepository:
namespace Dashboard.Data.Repository
{
public interface IProductRepository: IDisposable
{
Task<IEnumerable<Department>> GetDepartmentList();
}
}
Now i have a separate MVC/WebAPI project, where I try to use the repository.
My APIController:
namespace Dashboard.Controllers
{
public class ProductsAPIController : ApiController
{
protected IProductRepository repository { get; set; }
public ProductsAPIController()
{
this.repository = new ProductRepository();
}
[Route("api/Departments")]
public async Task<IHttpActionResult> GetDepartmentList()
{
var departments = await repository.GetDepartmentList();
return Ok(departments);
}
}
}
However, when I call the GetDepartmentList() in the api, i notice that my repository's context contains no data. It shows a "Enumeration yielded no results" message.
I'm worried that i've just confused myself when setting up my solution.
Could it be an incorrect connection string? Though it does look like it's able to see the database. I feel like I just have improper injection of the context, but not sure where or how to fix this issue.
Also, I am running off a local database file (.mdf). Where should this file be located? I currently have it in the Dashboard.Data folder...should it be in the App_Data folder of the main project?
EDIT 1
I've noticed that the MVC project creates a new and empty .mdf file in the App_Data folder. I have this connection string in both my MVC's web.config as well as the app.config in my DAL
My connection string:
<connectionStrings>
<add name="NriDatabaseContext" connectionString="data source=(LocalDB)\v11.0;attachdbfilename=|DataDirectory|\NRI_Database.mdf;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
</connectionStrings>
is it because of |DataDirectory|?
Not sure if this is the correct way to implement, but I went ahead and moved the .mdf to App_Data in my MVC project, and re-created the connection strings to point to that file.
Migrations seem to work now, as well as data access. Hopefully this is the correct solution.
The DataDirectory part of the connection string will automatically be substituted for the App_Data directory within your web application. It is possible to use a different location for your database file by using a relative path or changing the location within code.
The following links should provide more detailed information:
MSDN
SQL Express Connection string - Relative to application location

Set manual Entity framework connection string

I have a model that is created by EF6 ,by default my connection string is initialized in app.config as you can see here :
<connectionStrings>
<add name="ShirazRailwayEntities" connectionString="metadata=res://*/RailWay.csdl|res://*/RailWay.ssdl|res://*/RailWay.msl;provider=System.Data.SqlClient;provider connection string="data source=****;initial catalog=DB-Metro;user id=sa;password=****;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
</connectionStrings>
I have 3 layers in my application Model ,UI ,Domain class .my connection string is initialized in 'Model' and 'UI' layers,I need to set connection string by user , i mean the connection string should be set by user.
My question :
As i said i have 2 layers that the connection string are initialized inside them ,Is it necessary to initialized both connection by user ? Or just the UI is enough ?Which connection string should be initialized ?
The next question is how can i set the connection string by user?
I have a repository layer between my EF model and UI called repository :
public class StationRepository : GenericRepository<ShirazRailWay.ShirazRailwayEntities, DomainClass.Station>
{
}
My Ui calls this repository .
best regards
how can i set the connection string by user?
The DbContext class that underlies your Entity Framework context class has a constructor that takes in a connectionString parameter. If you expose that in your context, you can pass whatever connection string you want to it at runtime.
using(var ctx = new MyContext(GetCurrentUserConnectionString())
{
...
}
As for your other questions, I didn't really understand what you're asking. If you want a better answer, please clarify.
You can initialize your dbcontext with custom connection string:
var context = new DbContext(connectionString);
update:
In your repository you can add parameter to constructor of repository that will initialize connection string for dbcontext.
Example:
public class StationRepository : GenericRepository<ShirazRailWay.ShirazRailwayEntities, DomainClass.Station>
{
public StationRepository(string connectionstring):base(connectionstring){}
}
public class GenericRepository<T1, T2>
{
protected GenericRepository(string connectionstring)
{
//initialize dbcontext using connection string
}
}

System.data.SQLite entity framework code first programmatic providername specification

I've spent a while on this now and have only found a workaround solution that I'd rather not do...
I have a context as shown below. Is there a programmatic way to specify the database to connect to via the constructor, and get it to use the System.Data.SQLite entity framework provider to connect to a SQLite database? This is working via the app.config (with a connectionstring called "Context"), but not via any programmatic way I can find of supplying it. I have tried using an entity connectionstring builder and that produces the following string:
provider=System.Data.SQLite;provider connection string='data
source="datafile.db"'
When the context is first queried with this string I get a message "Keyword not supported: 'provider'."
public class Context : DbContext
{
public IDbSet<Test> Tests { get; set; }
public Context()
: base("Context")
{
}
}
*Edit.
I may have solved this by implementing my own connectionfactory:
public class ConnectionFactory : IDbConnectionFactory
{
public DbConnection CreateConnection(string nameOrConnectionString)
{
return new SQLiteConnection(nameOrConnectionString);
}
}
Then setting:
Database.DefaultConnectionFactory = new ConnectionFactory();
However, it seems like there should be a built in way to do this, and also one that does not involve overriding the global default connection factory.

Entity Framework - using the same DbContext with different connection strings

I'm currently trying to use the same DbContext (I have two databases, of identical structure) in my application. I'm not quite sure what I'm doing wrong, but here's my current code - hopefully it should be pretty obvious what I'm trying to do. I'm using EF Database First (which the error at the bottom seems not to suggest).
My context factory code:
public class HOLContextFactory
{
public static HOLDbEntities Create()
{
return new HOLDbEntities(); // Works
}
public static HOLDbQuoteEntities CreateQuote()
{
return new HOLDbQuoteEntities(); // Gives error
}
}
public partial class HOLDbQuoteEntities : HOLDbEntities
{
public HOLDbQuoteEntities()
: base("HOLDbQuoteEntities") // This should send "HOLDbQuoteEntities" as the base connection string?!
// Also tried "name=HOLDbQuoteEntities"
{
}
}
Web.config connection strings:
<add name="HOLDbEntities" connectionString="metadata=res://*/HOLDbContext.csdl|res://*/HOLDbContext.ssdl|res://*/HOLDbContext.msl;provider=System.Data.SqlClient;provider connection string=<connstringdetails>" providerName="System.Data.EntityClient" />
<add name="HOLDbQuoteEntities" connectionString="metadata=res://*/HOLDbContext.csdl|res://*/HOLDbContext.ssdl|res://*/HOLDbContext.msl;provider=System.Data.SqlClient;provider connection string=<connstringdetails>" providerName="System.Data.EntityClient" /> // using diff database - same structure
Error I'm getting when using "HOLDbQuoteEntities" :
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 generated 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**
Entity Framework needs to use the actual entities object:
public class HOLContextFactory
{
public static HOLDbEntities Create()
{
// default connection string
return new HOLDbEntities();
}
public static HOLDbEntities CreateQuote()
{
// specified connection string
return new HOLDbEntities ("HOLDbQuoteEntities");
}
}
public partial class HOLDbEntities
{
public HOLDbEntities(string connectionString)
: base(connectionString)
{
}
}
}
I've done the same thing in one of my project. I am creating my entity context using metadata=res://*/
Try this:
<add name="HOLDbEntities" connectionString="metadata=res://*/;provider=System.Data.SqlClient;provider connection string=<connstringdetails>" providerName="System.Data.EntityClient" />
<add name="HOLDbQuoteEntities" connectionString="metadata=res://*/;provider=System.Data.SqlClient;provider connection string=<connstringdetails>" providerName="System.Data.EntityClient" />

Entity Framework in Asp.Net MVC

My ASP.Net MVC application has to connect to multiple databases at run time. I can overload my class to accept the connection string at run time as shown below
class MyClassDBContext:DbContext
{
public MyClassDBContext(string str) : base(str)
{
this.Database.Connection.ConnectionString = str;
}
}
Currently, I am retrieving this connection string from a database table. My workflow is as follows
Website connects to default database using credentials stored in
web.config
Website queries default database to get connection strings for
other databases.
Websites connects to other databases by supplying the connection
string at run time
The problem I facing right now is in keeping my code clean. Every time I need the connection string for database number 2, I have to look it up in the default database. Is there any cleaner way of doing this? I considered storing the connection string in the profile data but I am not sure if this is a good idea. Every user of my website will need to connect to at most 2-3 different databases depending on their credentials.
I would personally put all connection strings in your App.Config file and use a simple IOC implementation.
Actually the ninject package off Nuget might be perfect for your needs.
Here's what I mean though. Hopefully this makes your code clean. I used this exact same pattern for a previous project and it worked out well.
You could take it a step further and make a Service Locator and register services in your global.asax. Let me know if that interests you. Also check out ninject.
public interface IService()
{
string GetConnectionString();
void DoStuff();
}
public class DBServiceOne : DbContext, IService
{
protected string GetConnectionString()
{
return ConfigurationManager.AppSettings["DBServiceOneConnectionString"]
}
public DBServiceOne(string str) : base(str)
{
this.Database.Connection.ConnectionString = GetConnectionString()
}
public void DoStuff() { //logic goes here }
}
public class DBServiceTwo : DbContext, IService
{
public DBServiceTwo(string str) : base(str)
{
this.Database.Connection.ConnectionString = GetConnectionString();
}
protected string GetConnectionString()
{
return ConfigurationManager.AppSettings["DBServiceTwoConnectionString"]
}
public void DoStuff() { //logic goes here }
}
public class DBServiceThree : DbContext, IService
{
public DBServiceThree(string str) : base(str)
{
this.Database.Connection.ConnectionString = GetConnectionString();
}
protected string GetConnectionString()
{
return ConfigurationManager.AppSettings["DBServiceThreeConnectionString"]
}
public void DoStuff() { //logic goes here }
}
Now for the implementation -- Use Constructor Injection on your controllers
//This could be in your home controller
public class HomeController : AsyncController
{
private IService DBOneService;
private IService DBTwoService;
private IService DBThreeService;
public HomeController(IService one, IService two, IService three)
{
DBOneService= one;
DBTwoService = two;
DBThreeService = three;
}
public HomeController() : this(new DBServiceOne(), new DBServiceTwo(), new DBServiceThree()) {}
public ActionResult Index() {
DBOneService.DoStuff(); //here you'd want to return a list of data and serialize down with json or populate your razor template with it. Hope this helps!
}
I had a slightly different problem. The DB I connect to depends on the state of a product import. During the import databases get attached and detached. The currently available db is stored in a "default database".
The main problem I had was that I had to switch off connection pooling, otherwise invalid connection states existed after detaching databases and attaching them again.
This is might not be a problem for you.
Apart from that I store the current Connectionstring in the application state. Only after each 60 seconds I query the "default database" again. You have to watch out for multithreading issues by using locking.

Categories