C# .NET Entity Framework multi-tenant best practice - c#

Consider the following code segment:
public class DatabaseContext : DbContext
{
public DatabaseContext(String connectionString) : base(connectionString)
{
}
}
public class ContextNameDatabaseContext : DatabaseContext
{
public ContextNameDatabaseContext(String connectionString) : base(connectionString)
{
}
}
Would one say it is best practice when building the back-end for a multi-tenant solution where each client has its own database and maintain the data state until a user logs out / off?
Developer using these classes in this instance will need to be aware and careful as to when and how the classes are being used where the 'DatabaseContext' class acts as a base to the 'ContextNameDatabaseContext' class.
Please advise on any thoughts or suggestions.

One approach is to keep all the database connection strings as parameters in the database. However you have to assure that its encrypted.
Then at your DB layer you can pass the connection as parameter in plain text after decrypting and constructing your connection string accordingly:
public class MyDatabase: DbContext
{
public MyDatabase(string connString)
{
this.Database.Connection.ConnectionString = connString;
}
public DbSet<Order> Orders{ get; set; }
}
You can also use IOptions if you are using .NET Core to inject the connection string as a dependency.

Related

How to create a DbContext instance of the specified database type

I know that the default return here is the SQL Server database type, but I need the MySQL database type, the problem is to create a DbContext instance of the specified database type?
Of course, I know that it can also be created by specifying the connectionName, but the database connection string is encrypted and the string needs to be decrypted
The pseudo code is as follows:
public partial class MyDbContext : DbContext
{
static string mysqlConn = ReadConnectionString(); //Get the encrypted MySQL connection string
public MyDbContext() : base(mysqlConn)
{
// Even if I want to modify the connection string in this way, nor will it throw a connection string malformed exception
Database.Connection.ConnectionString = DecryptConnectionString(mysqlConn);
}
}
Ok, it's about what I expected, few of my questions can be answered.
Although I am not too familiar with EntityFramework, by looking at the source code of DbContext, I can almost certainly determine that this is a design flaw, it lacks a constructor like this:
Public DbContext(string connectionString, string providerName)
I had same problem and found .Net framework 4.5 needs additional attribute on DbContext class to use MySql as below
[DbConfigurationType(typeof(MySql.Data.Entity.MySqlEFConfiguration))]
public class MySqlContext : DbContext
{
...
}

Set EF ConnectionString at runtime from subdomain for multi-tenancy setup

Currently, my DbContext class has this code:
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
What I'd like to do, is inspect the subdomain and use that for the database name, so something like http://abc.mysite.com/ would use a connection string with database name abc.
But how do I manipulate the value of "DefaultConnection" in the constructor?
DBContext will take a name or a connection string in its constructor. That constructor is not usually exposed if you have a generated model.
You can use a partial class to expose that constructor:
public partial class DataEntities
{
public DataEntities(string connectionString) : base(connectionString)
{
}
}
I have done that before. My project was set up for DI with Castle Windsor and one of my IWindsorInstallers was DataAccessInstaller responsible for registering, among other classes like repositories, my database context and here is the relevant code:
container.Register(Component
.For<MyDatabaseContext>().Forward<DbContext>()
.ImplementedBy<MyDatabaseContext>()
.LifestylePerWebRequest()
.UsingFactoryMethod(context =>
{
return MyDatabaseContextFactory.Create(HttpContext.Current.Request.Url);
}));
You can have several connection strings set up in your web.config matching your domain.
My context factory implementation:
public static class MyDatabaseContextFactory
{
public static MyDatabaseContext Create(Uri uri)
{
return new MyDatabaseContext(uri.GetTopDomain());
}
}
If you just have a simple project and don't even have DI, you can still make use of a factory that finds out what the website use and instantiates a database context with the appropriate connection string.
Needless to say, your current database context constructor doesn't have to change.

ASP.NET vNext EF7 dbContext issues

I am starting a vNext project, and I'm having some issues kicking it off the ground. I have added a table to the ApplicationDbContext class, and it successfully created the table in the db (which in my case is in Azure). However, I can't seem to correctly instantiate a dbContext to use in my Controllers.
In my experience with previous ASP.NET EF projects, I could instantiate the ApplicationDbContext class without passing it any parameters, but in the case of vNext however, it seems to expect a number of things (IServiceProvider, and IOptionsAccessor<DbContextOptions>). I have tried creating a parameter-less constructor, but the App breaks due to not knowing what connection strings to use. My code is below -- as you see in the OnConfiguring(DbContextOptions options) override, I force the connection string in via the DbContextOptions, but that's obviously not ideal, and I feel like I'm just not understanding where those two IServiceProvider, and IOptionsAccessor parameters need to come from.
Thanks for any help!
namespace Project.Models
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
public string CompanyName { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
private static bool _created = false;
public DbSet<Business> Businesses { get; set; }
public ApplicationDbContext()
: base()
{
if (!_created)
{
Database.EnsureCreated();
_created = true;
}
}
protected override void OnConfiguring(DbContextOptions options)
{
var configuration = new Configuration();
configuration.AddJsonFile("config.json");
configuration.AddEnvironmentVariables();
options.UseSqlServer(configuration.Get("Data:DefaultConnection:ConnectionString"));
}
public ApplicationDbContext(IServiceProvider serviceProvider, IOptionsAccessor<DbContextOptions> optionsAccessor)
: base(serviceProvider, optionsAccessor.Options)
{
// Create the database and schema if it doesn't exist
// This is a temporary workaround to create database until Entity Framework database migrations
// are supported in ASP.NET vNext
if (!_created)
{
Database.EnsureCreated();
_created = true;
}
}
}
}
IServiveProvider and IOptionAccessor are injected by the Dependency Injection
the ASP.Net Core DI has limitation, you cannot have more than one constructor.
Read this: http://blogs.msdn.com/b/webdev/archive/2014/06/17/dependency-injection-in-asp-net-vnext.aspx

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 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