Migration with dynamic connection string in Entity Framework code-first (the connection string from text file) - entity-framework

I'm using EF6 code-first and at the first, I put the connection string in a text file called 'Settings.txt'
The data in the 'Settings.txt' file is
DataProvider: sqlserver
DataConnectionString: Data Source=.\SQLEXPRESS;Initial Catalog=MyDb;Integrated Security=True;Persist Security Info=False;Enlist=False;
Here what I use for the dbContext class:
public class DbDataContext : BaseDbContext
{
static DbDataContext()
{
Database.SetInitializer(new ContextInitializer());
}
public DbDataContext():base() { }
public DbDataContext(string nameOrConnectionString)
: base(nameOrConnectionString) { }
...
}
[DbConfigurationType(typeof(MyDbConfiguration))]
public abstract partial class BaseDbContext : DbContext, IDbContext
{
public BaseDbContext() : this(GetConnectionString())
{ }
public BaseDbContext(string nameOrConnectionString) : base(nameOrConnectionString)
{ }
public static string GetConnectionString()
{
if (DataSettings.DataSettings.Current.IsValid())
{
return DataSettings.DataSettings.Current.DataConnectionString;
}
throw Error.Application("A connection string could not be resolved for the parameterless constructor of the derived DbContext. Either the database is not installed, or the file 'Settings.txt' does not exist or contains invalid content.");
}
}
public class MyDbConfiguration : DbConfiguration
{
public MyDbConfiguration()
{
IEfDataProvider provider = null;
try
{
provider = (new EfDataProviderFactory(DataSettings.DataSettings.Current).LoadDataProvider()) as IEfDataProvider;
}
catch {
}
if (provider != null)
{
base.SetDefaultConnectionFactory(provider.GetConnectionFactory());
}
}
}
public partial class EfDataProviderFactory : DataProviderFactory
{
public EfDataProviderFactory()
: this(DataSettings.DataSettings.Current){ }
public EfDataProviderFactory(DataSettings.DataSettings settings)
: base(settings) { }
public override IDataProvider LoadDataProvider()
{
var providerName = Settings.DataProvider;
if (providerName.IsEmpty())
{
throw new Exception("Data Settings doesn't contain a providerName");
}
switch (providerName.ToLowerInvariant())
{
case "sqlserver":
return new SqlServerDataProvider();
case "sqlserverce":
return new SqlServerCeDataProvider();
default:
throw new Exception(string.Format("Unsupported dataprovider name: {0}", providerName));
}
}
}
public class SqlServerDataProvider : IEfDataProvider
{
public virtual IDbConnectionFactory GetConnectionFactory()
{
return new SqlConnectionFactory();
}
public bool StoredProceduresSupported
{
get { return false; }
}
public DbParameter GetParameter()
{
return new SqlParameter();
}
public string ProviderInvariantName
{
get { return "System.Data.SqlClient"; }
}
}
I use a static function in 'BaseDbContext' class called 'GetConnectionString()'
This function just for return the connection string from the text file. This behavior is working very well at runtime, but it not working when adding a migration.
This is the problem: how can I add a migration by this way, knowing that when I put the connection string directly in the function like this
public static string GetConnectionString()
{
return (#"Data Source=.\\SQLEXPRESS;Initial Catalog=MyDb;Integrated Security=True;Persist Security Info=False;Enlist=False;");
}
the Add-Migration command is working
How can I solve this problem without forcing the connection string in the code?

I solved this,
the problem occurs in getting the file path (text file) in design-mode or even
unit tests
string filePath = Path.Combine(MapPath("~/App_Data/"), "Settings.txt");
public static string MapPath(string path)
{
path = path.Replace("~/", "").TrimStart('/').Replace('/', '\\');
var testPath = Path.Combine(baseDirectory, path);
var dir = FindSolutionRoot(baseDirectory);
if (dir != null)
{
baseDirectory = Path.Combine(dir.FullName, "MyProjectName.WebAPI");
testPath = Path.Combine(baseDirectory, path);
return testPath;
}
}
private static DirectoryInfo FindSolutionRoot(string currentDir)
{
var dir = Directory.GetParent(currentDir);
while (true)
{
if (dir == null || IsSolutionRoot(dir))
break;
dir = dir.Parent;
}
return dir;
}
private static bool IsSolutionRoot(DirectoryInfo dir)
{
return File.Exists(Path.Combine(dir.FullName, "MySolutionName.sln"));
}
and by this, we can get the file path in runtime-mode

I guess you use the Add-Migration command in the Package Manager Console.
If you are running Add-Migration manually you can just add the connection string as the -ConnectionString parameter:
Add-Migration -ConnectionString "Data Source=.\\SQLEXPRESS;Initial Catalog=MyDb;Integrated Security=True;Persist Security Info=False;Enlist=False;"
You will probably have to add parameter -ConnectionProviderName as well, unless you have a provider defined in your app.config.
I would recommend you to stop using this Settings.txt file and move your connection string to your app.config file, in the section connectionStrings. This is the recommended way to deal with connection strings, much easier than using a custom file like your Settings.txt file.
<connectionStrings>
<add name="MyLocalDatabase" connectionString="Data Source=.\\SQLEXPRESS;Initial Catalog=MyDb;Integrated Security=True;Persist Security Info=False;Enlist=False;" />
</connectionStrings>
If you do that you can use the parameter -ConnectionStringName in the Package Manager Console, using the name you defined in the app.config:
Add-Migration -ConnectionStringName "MyLocalDatabase"
Also, with the connection string in your app.config file you can add a constructor to your context that receives the connection string name as a parameter and can be used when using the Package Manager console:
public DbDataContext():base("MyLocalDatabase") { }
This will allow you to run your commands in Package Manager Console without specifying any connection string parameters at all. Just make sure the right connection strings are included in the app.config file of the start project selected in the console.
And you can get rid of your GetConnectionString code. You are just re-implementing code that you have out-of-the-box when using app.settings connectionString section. That's how DbContext base constructors parameter NameOrConnectionString is meant to be used. You can provide either a full connection string or the name of a connection string defined in the app.settings file.

Related

How to use dynamic connection string in mongodbcontext in C#

I would like to connect to the database specified in the connection string. I have multiple connection sting for different client
public MongoContext(IConfiguration configuration)
{
_configuration = configuration;
// Every command will be stored and it'll be processed at SaveChanges
_commands = new List<Func<Task>>();
}
private void ConfigureMongo()
{
if (MongoClient != null)
{
return;
}
// Configure mongo (You can inject the config, just to simplify)
MongoClient = new MongoClient(_configuration["MongoSettings:Connection"]);
Database = MongoClient.GetDatabase(_configuration["MongoSettings:DatabaseName"]);
}
public IMongoCollection<T> GetCollection<T>(string name)
{
ConfigureMongo();
return Database.GetCollection<T>(name);
}
}
My Repository Class is
public abstract class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected readonly IMongoContext Context;
protected IMongoCollection<TEntity> DbSet;
protected BaseRepository(IMongoContext context, int parentID)
{
Context = context;
DbSet = Context.GetCollection<TEntity>(typeof(TEntity).Name);
}
}
For each request, i want to set connection string dynamically based on UserID
Not sure I've got what you're asking, but if you need connecting to more than one cluster, you should create a separate mongo client for each separate cluster

Entityframework code based config

I'm trying to get rid of the App.config with its EntityFramework (v6) settings and move it into code (Don't want to deploy the config with connection string and password).
Following exception is thrown on opening: System.Data.Entity.Core.EntityException: The underlying provider failed on Open.
I'm using sqlite3 db with encryption and database first entity model.
[DbConfigurationType(typeof(DatabaseConfiguration))]
public partial class TestEntities : DbContext
{
public TestEntities () :
base((DatabaseConfiguration.GetConnectionString("TestEntities", #"\\\\ABT\01_Tools\Test\DB\test.db", "someDummyPw")))
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Employee> Employees { get; set; }
}
public class SQLiteConnectionFactory:IDbConnectionFactory
{
public DbConnection CreateConnection(string nameOrConnectionString)
{
return new SQLiteConnection(nameOrConnectionString);
}
}
public class DatabaseConfiguration:DbConfiguration
{
public DatabaseConfiguration()
{
SetDefaultConnectionFactory(new SQLiteConnectionFactory());
SetProviderFactory("System.Data.SQLite", SQLiteFactory.Instance);
SetProviderFactory("System.Data.SqlClient", SqlClientFactory.Instance);
SetProviderFactory("System.Data.SQLite.EF6", SQLiteProviderFactory.Instance);
//SetProviderFactory("System.Data.EntityClient", EntityProviderFactory.Instance);
SetProviderServices("System.Data.SQLite.EF6", (DbProviderServices)SQLiteProviderFactory.Instance.GetService(typeof(DbProviderServices)));
}
public static string GetConnectionString(string efModelName, string databaseFilePath, string password)
{
// Initialize the connection string builder for the underlying provider.
SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder
{
DataSource = databaseFilePath,
Password = password
};
// Initialize the EntityConnectionStringBuilder.
EntityConnectionStringBuilder entityBuilder = new EntityConnectionStringBuilder();
entityBuilder.Provider = "System.Data.SQLite.EF6";
// Set the provider-specific connection string.
entityBuilder.ProviderConnectionString = sqlBuilder.ToString();
// Set the Metadata location.
entityBuilder.Metadata = string.Format(#"res://*/Model.{0}.csdl|res://*/Model.{0}.ssdl|res://*/Model.{0}.msl", efModelName);
return entityBuilder.ConnectionString;
}
}
Any Ideas?

How to change database schema on runtime in EF7 or EF core

My database have different schema depending on user selections on runtime.
My code is below:
public partial class FashionContext : DbContext
{
private string _schema;
public FashionContext(string schema) : base()
{
_schema = schema;
}
public virtual DbSet<Style> Styles { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer(#"Server=.\sqlexpress;Database=inforfashionplm;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Style>()
.ToTable("Style", schema: _schema);
}
}
Upon testing. I created a context instance with 'schema1'.
So far so good.
But when I create another context instance with different schema 'schema2', the resulting data in which the schema is still on 'schema1'.
Here is the implementation:
using (var db = new FashionContext("schema1"))
{
foreach (var style in db.Styles)
{
Console.WriteLine(style.Name);
}
}
Console.ReadLine();
Console.Clear();
using (var db = new FashionContext("schema2"))
{
foreach (var style in db.Styles)
{
Console.WriteLine(style.Name);
}
}
Console.ReadLine();
Later I noticed that the OnModelCreating is called only one time, so it is never called again when you create a new context instance of the same connection string.
Is it possible to have dynamic schema on runtime? Note: this is possible in EF6
One of possible way was mentioned above, but briefly, so I will try to explain with examples.
You ought to override default ModelCacheKeyFactory and ModelCacheKey.
ModelCachekeyFactory.cs
internal sealed class CustomModelCacheKeyFactory<TContext> : ModelCacheKeyFactory
where TContext : TenantDbContext<TContext>
{
public override object Create(DbContext context)
{
return new CustomModelCacheKey<TContext>(context);
}
public CustomModelCacheKeyFactory([NotNull] ModelCacheKeyFactoryDependencies dependencies) : base(dependencies)
{
}
}
ModelCacheKey.cs, please review Equals and GetHashCode overridden methods, they are not best one and should be improved.
internal sealed class ModelCacheKey<TContext> : ModelCacheKey where TContext : TenantDbContext<TContext>
{
private readonly string _schema;
public ModelCacheKey(DbContext context) : base(context)
{
_schema = (context as TContext)?.Schema;
}
protected override bool Equals(ModelCacheKey other)
{
return base.Equals(other) && (other as ModelCacheKey<TContext>)?._schema == _schema;
}
public override int GetHashCode()
{
var hashCode = base.GetHashCode();
if (_schema != null)
{
hashCode ^= _schema.GetHashCode();
}
return hashCode;
}
}
Register in DI.
builder.UseSqlServer(dbConfiguration.Connection)
.ReplaceService<IModelCacheKeyFactory, CustomModelCacheKeyFactory<CustomContext>>();
Context sample.
public sealed class CustomContext : TenantDbContext<CustomContext>
{
public CustomContext(DbContextOptions<CustomContext> options, string schema) : base(options, schema)
{
}
}
You can build the model externally and pass it into the DbContext using DbContextOptionsBuilder.UseModel()
Another (more advanced) alternative is to replace the IModelCacheKeyFactory to take schema into account.
I found a way to recreate the compiled model on each context creation.
public partial class MyModel : DbContext {
private static DbConnection _connection
{
get
{
//return a new db connection
}
}
private static DbCompiledModel _model
{
get
{
return CreateModel("schema name");
}
}
public MyModel()
: base(_connection, _model, false)
{
}
private static DbCompiledModel CreateModel(string schema)
{
var modelBuilder = new DbModelBuilder();
modelBuilder.HasDefaultSchema(schema);
modelBuilder.Entity<entity1>().ToTable(schema + ".entity1");
var builtModel = modelBuilder.Build(_connection);
return builtModel.Compile();
}
}

Visual Studio Online Unit Test and localDb

I have a solution that is built on Visual Studio Online.
All the unit tests in this solution require a database, I tried to get the build use a test's dedicated localdb (by adding a light mdf file in the test project and use a localdb connection string) but it fail with this error (everything work fine on my desktop) :
System.Data.SqlClient.SqlException: Connection Timeout Expired. The timeout period elapsed while attempting to consume the pre-login handshake acknowledgement. This could be because the pre-login handshake failed or the server was unable to respond back in time. The duration spent while attempting to connect to this server was - [Pre-Login] initialization=29460; handshake=161; ---> System.ComponentModel.Win32Exception: The wait operation timed out.
EDIT
The connection string is
<add name="TestDb" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=AeroBase;Integrated Security=true;AttachDBFilename=|DataDirectory|\App_Data\AeroBase.mdf" providerName="System.Data.SqlClient" />
I use EF6 code first, repository and unit of work patterns to access it.
Here is the DbContext :
public class AeroDataContext : DbContext, IDbContext
{
private Guid DataContextId;
private string _name;
public string Name { get { return _name; } }
public AeroDataContext(string cnxStringName, string cnxString)
: base(cnxString)
{
this.Database.Log = delegate(String name)
{
// Debug.WriteLine(name);
};
_name = cnxStringName;
this.Configuration.LazyLoadingEnabled = false;
DataContextId = Guid.NewGuid();
Debug.WriteLine("AeroDbCreation Id = " + DataContextId.ToString());
}
}
The DbContext is instanciated using a unitOfWorkScope :
public class UnitOfWorkFactory : IUnitOfWorkFactory
{
private string _cnxStringName;
private string _cnxString;
public UnitOfWorkFactory(string cnxStringName, string cnxString)
{
_cnxString = cnxString;
_cnxStringName = cnxStringName;
}
public IUnitOfWorkScope GetUnitOfWorkScope(bool disposeAtEndOfContext = true)
{
return new UnitOfWorkScope(new AeroDataContext(_cnxStringName, _cnxString), disposeAtEndOfContext);
}
}
which allows me to do things like this in the test (and in the app)
[TestMethod]
public void DB_Get_LFFE_Airport_By_ICAOInclude_SubType()
{
//structuremap container built in the TestInitilized method
IUnitOfWorkFactory _uowf = container.GetInstance<IUnitOfWorkFactory>();
using (IUnitOfWorkScope uows = _uowf.GetUnitOfWorkScope(true))
{
IAirportRepository repo = uows.GetRepository<IAirportRepository>();
Airport ar = repo.SearchByICAO("LFFE").FirstOrDefault();
AirportValidator.LFFE(ar);
}
}
Is this scenario even possible? is there any other way to do that?
This very likely has to do with LocalDb not being initialized on the VSO build server that gets spun up to run the build.
According to https://github.com/ritterim/automation-sql, LocalDb may be installed, but not initialized.
LocalDB installed but can't Connect
You may have LocalDB installed, but never initialized the instance on
your machine. Run this command via command prompt.
LocalDB SQL EXPRESS 2014
"C:\Program Files\Microsoft SQL Server\120\Tools\Binn\SqlLocalDB.exe"
create "v12.0" 12.0 -s LocalDB SQL Express 2012
"C:\Program Files\Microsoft SQL Server\110\Tools\Binn\SqlLocalDB.exe"
create "v11.0" 11.0 -s Verify that the command worked by using SQL
Server Management Studio to connect to the instance.
My solution:
Every time VSO launches a build, it creates a brand new virtual machine from a template to run the build. In my case, I wanted to create a small LocalDb database dynamically in order to run unit tests against.
I'm using the RimDev.Automation.Sql nuget package (http://www.nuget.org/packages/RimDev.Automation.Sql/) to allow my tests to programmatically create the database.
I also wanted to integrate with Unity and Entity Framework 6 so that it mimics my database access in production as closely as possible. To do this, I created a based class that all of my tests inherit from. One thing that I wanted is for all of my tests to share the same database, so I made the LocalDb creation static.
In addition, the initialization runs a set of scripts in the Resources folder in order to pre-populate the database with stock data if you so wish. You'll need to make sure that the sql scripts are marked to copy to the output folder (see the properties) so that the files will be in the proper path when the unit test is running.
If you want to create a new database per test class or even per test, this could easily be changed.
using System;
using System.Data.Entity;
using System.IO;
using Microsoft.Practices.Unity;
using Playground.Model;
using RimDev.Automation.Sql;
namespace Playground.UnitTests
{
public abstract class TestBaseClass
{
// For now, these are static. We may want to change them at some point
// to be per class so we create separate databases for each class run, but
// for now, let's not do that for speed sake.
private static readonly IUnityContainer _container = new UnityContainer();
private static bool _isRegistered = false;
private static readonly object _syncRoot = new object();
private static LocalDb LocalDb = new LocalDb(databaseName: "PlaygroundTestDb", databasePrefix: "pg", version: "v12.0");
private static bool _isInitialized = false;
protected TestBaseClass()
{
RegisterComponents(_container);
InitializeData();
_container.BuildUp(GetType(), this);
}
private void InitializeData()
{
lock (_syncRoot)
{
if (!_isInitialized)
{
var dbContext = _container.Resolve<PlaygroundEntities>();
Database.SetInitializer(
new MigrateDatabaseToLatestVersion<PlaygroundEntities, Playground.Model.Migrations.Configuration>());
// Make sure database exists.
dbContext.Database.Initialize(true);
foreach (
var f in Directory.GetFiles(Path.Combine(Environment.CurrentDirectory, "Resources"), "*.sql"))
{
dbContext.Database.ExecuteSqlCommand(File.ReadAllText(f));
}
}
_isInitialized = true;
}
}
private void RegisterComponents(IUnityContainer container)
{
lock (_syncRoot)
{
if (!_isRegistered)
{
// WARNING! Most methods in the unity container are not thread safe. See http://unity.codeplex.com/discussions/27496
// We may need to expose protected methods to register certain types. For now, assume all of the
// tests use the same injected objects. If a test REALLY needs to a different dependency, the test can
// manually create it as well.
container.RegisterType<PlaygroundEntities, PlaygroundEntitiesTest>(new TransientLifetimeManager(),
new InjectionConstructor(new object[] {LocalDb.ConnectionString}));
}
_isRegistered = true;
}
}
}
}
Here is a sample test:
using System.Linq;
using Microsoft.Practices.Unity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Playground.Model;
namespace Playground.UnitTests
{
[TestClass]
public class UnitTest1 : TestBaseClass
{
[Dependency]
public PlaygroundEntities Db { get; set; }
private static bool _initialized = false;
[TestInitialize]
public void TestInitialize()
{
if (!_initialized)
{
Db.Playgrounds.Add(new Playground.Model.Playground() {Name = "Dave's playground", Location = "SomeTown"});
Db.SaveChanges();
_initialized = true;
}
}
[TestMethod]
public void TestMethod1()
{
var p = Db.Playgrounds.FirstOrDefault(pg => pg.Name == "Dave's playground");
Assert.IsNotNull(p);
}
[TestMethod]
public void TestMethod2()
{
var p = Db.Playgrounds.FirstOrDefault(pg => pg.Location == "SomeTown");
Assert.IsNotNull(p);
}
}
}
Lastly, in my test project, I have the test entities object.
using Playground.Model;
namespace Playground.UnitTests
{
public class PlaygroundEntitiesTest : PlaygroundEntities
{
private PlaygroundEntitiesTest()
{
}
public PlaygroundEntitiesTest(string connectionString) : base(connectionString)
{
}
}
}
In my models project, I have my entity and my context.
Playground.cs
using System;
namespace Playground.Model
{
public class Playground
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
}
}
PlaygroundEntities.cs
using System.Data.Entity;
namespace Playground.Model
{
public class PlaygroundEntities : DbContext
{
public PlaygroundEntities() : base("PlaygroundConnectionString")
{
}
public PlaygroundEntities(string connectionString) : base(connectionString)
{
}
public virtual DbSet<Playground> Playgrounds { get; set; }
}
}
Lastly, I set up a post-build step in the unit test project to execute the command to initialize LocalDb as follows:
The full command is
"C:\Program Files\Microsoft SQL Server\120\Tools\Binn\SqlLocalDB.exe"
create "v12.0" 12.0 -s
Then, it was as simple as pushing to Visual Studio Online and launching my build.

Code First Migrations without NuGet

I'm wondering how I can use Entity Framework's Code First Migrations without using NuGet at all (so no commands via the package manager console).
I tried the following:
Database-Context
public sealed class MyContext : DbContext
{
private const string ConnectionStringName = "MyDatabase";
public MyContext()
: base(ConnectionStringName)
{}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
SetupMyModel(modelBuilder);
}
}
Migration-Configuration
public class MyMigrationConfiguration : DbMigrationsConfiguration<MyContext>
{
public MyMigrationConfiguration()
{
AutomaticMigrationsEnabled = false;
}
}
Database-Initializer
System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyMigrationConfiguration>());
InitialDatabaseCreation Migration
public class InitialDatabaseCreation : DbMigration, IMigrationMetadata
{
public override void Up()
{
CreateTable("dbo.MyModel",
c => new
{
Id = c.Guid(false, true),
SomeProperty = c.Int(false)
})
.PrimaryKey(x => x.Id);
}
public override void Down()
{
DropTable("dbo.MyModel");
}
public string Id
{
get { return "0001_InitialDatabaseCreation"; }
}
public string Source
{
get { return null; }
}
public string Target
{
get { return Id; }
}
}
As you can see, I wrote a context, a migration configuration and the migration itself.
For the Migration, I'm not sure, if I implemented the IMigrationMetadata correcly. For the Target I just use the id, because I don't want to have any automatic migrations or the ability to use the package manager console. I think, this should be fine here?
I set a breakpoint at the Up method and debugged it, but it does not stop there, which means it doesn't get executed.
Therefor I want to know how to use the EF Code First migrations when writing everything manually.