I am trying to create a Microservices architecture. I have a stateless service combined with Entity Framework deployed on the Azure Service Cluster Fabric.
However my problem is when i have a Initializer with DropCreateDatabaseAlways the database is removed but not recreated.
I have the following Initializer:
class CompanyInitializer : DropCreateDatabaseAlways<CompanyContext>
{
protected override void Seed(CompanyContext context)
{
var companies = new List<Company>
{
new Company { Name = "AAA", City = "Eindhoven", Streetname="Street 12" },
new Company { Name = "BBB", City = "Rotterdam", Streetname = "Street 12" },
new Company { Name = "CCC", City = "Eindhoven", Streetname = "Street 12" }
};
companies.ForEach(s => context.Companies.Add(s));
context.SaveChanges();
base.Seed(context);
}
}
With the following context:
public class CompanyContext : DbContext
{
public CompanyContext(string connectionString) : base(connectionString)
{
this.Database.Connection.ConnectionString = connectionString;
Database.SetInitializer<CompanyContext>(new CompanyInitializer());
}
public DbSet<Company> Companies { get; set; }
}
And i am connecting these through the constructor of the stateless service:
public StatelessServiceCompany(StatelessServiceContext context)
: base(context)
{
_databaseConnectionstring = WebConfigurationManager.AppSettings["Entity.Framework.ConnectionString"];
_context = new CompanyContext(_databaseConnectionstring);
new CompanyInitializer().InitializeDatabase(_context);
}
And the connectionstring is as followed:
Data Source=*****.*****.****.***;Initial Catalog=******;Integrated Security=False;User ID=********;Password=********;Connect Timeout=30;Encrypt=True;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False
The problem is that the database is dropped but never recreated. I believe there are rights missing to create a database through code on the Azure platform.
Secondly Service cluster fabric is not letting me enable migrations for the project. When enabling migrations via the Package manager console i get the following error:
"System.BadImageFormatException: Could not load file or assembly
'StatelessServiceCompany' or one of its dependencies. An attempt was
made to load a program with an incorrect format."
How can i solve this. I don't want to make a replicate of Entity Framework via Ado.net.
Edit
I solved my problem by deleting the Initializer and enabling migrations. The solution for enabling migration was in the Platform Target under Properties > Build. Switching the platform target between x64 and x86 seems to do the trick. The seed function in the configuration file is also a kind of initializer.
You questions consists of two issues:
Database initialisation and
Schema migrations
of which I will only address the first one as that is your primary concern.
These two issues might be connected as well as might not.
The initializer DropCreateDatabaseAlways, unsuprisingly, drops the database when you run the application but it does not recreate a new one immediately. Instead, it waits for the first context usage in particular application domain and only then recreates it.
https://msdn.microsoft.com/en-us/library/gg679506(v=vs.113).aspx
Related
I am trying to use an existing library which is a .net library which uses EF 6.0 to connect to a database. Since Azure Functions does not have an app.config file, I am trying to set the connection string using C# code. But I am getting the following exception while connecting to the DB using my DB context:
System.ArgumentException: The ADO.NET provider with invariant name 'System.Data.SqlClient' is either not registered in the machine or application config file, or could not be loaded. See the inner exception for details.
System.ArgumentException: The specified invariant name 'System.Data.SqlClient' wasn't found in the list of registered .NET Data Providers
MyDBContext.partial.cs:
[DbConfigurationType(typeof(MyDbConfiguration))]
public partial class MyDBContext : DbContext
{
public MyDBContext (string ConnectionString)
: base(ConnectionString)
{
}
}
public class MyDbConfiguration : DbConfiguration
{
public MyDbConfiguration()
{
SetProviderServices("System.Data.SqlClient", SqlProviderServices.Instance);
SetDefaultConnectionFactory(new SqlConnectionFactory());
}
}
I have a method as following to get the DBContext. This method will be used by the library methods to get the DB context instance.
public MyDBContext GetDB( string metadata, string connectionString )
{
EntityConnectionStringBuilder b = new EntityConnectionStringBuilder();
b.Metadata = metadata;
b.ProviderConnectionString = connectionString;
b.Provider = "System.Data.SqlClient";
return new MyDBContext (b.ConnectionString);
}
When I execute a library method to load data from db from an Azure function v2, which internally calls the above method to get DB Context and then connects to actual DB. Here MyDBContext object is getting created, but when it connects to db the following exception occurs.
System.ArgumentException: The ADO.NET provider with invariant name 'System.Data.SqlClient' is either not registered in the machine or application config file, or could not be loaded. See the inner exception for details.
System.ArgumentException: The specified invariant name 'System.Data.SqlClient' wasn't found in the list of registered .NET Data Providers
I just worked on this issue but for Azure function V1.
When using EF with Azure function, you can specify connection string in 'local.settings.json' file like this:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"AzureWebJobsDashboard": ""
},
"ConnectionStrings": {
"YourEntities": {
"ConnectionString": "metadata=res://*/EF.yourModel.csdl|res://*/EF.yourModel.ssdl|res://*/EF.yourModel.msl;provider=System.Data.SqlClient;provider connection string='data source=yourServer;initial catalog=yourDB;persist security info=True;user id=yourUserID;password=yourPwd;MultipleActiveResultSets=True;App=EntityFramework'",
"ProviderName": "System.Data.EntityClient"
}
}
}
Please pay attention to 'ProviderName' attribute. Case should be exact as shown above and provider should be 'EntityClient'
Plus 'Provider Connection String' attribute of actual connection string should be in single quote (I am not sure why Microsoft did this but this is how it is supposed to be).
This will help you run your function app locally with EF without any more changes
Now for deployment in Azure.
local.settings.json does not get deployed to cloud. As its name suggests it acts as configuration file for local run.
So you need to set connection string in 'Configuration' of Azure function app on portal.
There you can specify following parameters:
Name - 'YourEntities'
value - Just Connection string part from above json file
Type - 'Custom'
Slot Settings - according to your requirement
Now if you notice there is no way to specify ProviderName here. If you try to run function now you will get error for 'missing provider name'
Here your extended DBConfiguration class comes in handy.
Create your DB configuration class as below and specify provider as EntityType
public class YourDBContextConfig : DbConfiguration
{
public YourDBContextConfig()
{
SetProviderServices("System.Data.EntityClient",
SqlProviderServices.Instance);
SetDefaultConnectionFactory(new SqlConnectionFactory());
}
}
You can create this class in same file where you have created partial class for your DBContext
Add following attribute to your Context class:
[DbConfigurationType(typeof(YourDBContextConfig))]
Also make sure your partial context class has constructor that takes connection string as parameter and supply it while initializing context:
string connString =
ConfigurationManager.ConnectionStrings["YourEntities"].ConnectionString;
using (YourEntities db = new YourEntities(connString))
{
}
This will work for deployment.
I've reached a bit of a brick-wall with my current project.
I have three normalised databases, one of which I want to dynamically connect to; these are:
Accounts: For secure account information, spanning clients
Configuration: For managing our clients
Client: Which will be atomic for each of our clients & hold all of their information
I need to use data stored in the "Configuration" database to modify the ConnectionString that will be used to connect to the "Client" database, but this is the bit I'm getting stuck on.
So far I've generated the entities from the databases into a project by hooking up EntityFrameWorkCore Tools and using the "Scaffold-DbContext" command & can do simple look-ups to make sure that the databases are being connected to okay.
Now I'm trying to register the databases by adding them to the ServiceCollection, I have them added in the StartUp class as follows:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
});
services.AddDbContext<Accounts>( options =>
options.UseSqlServer(Configuration.GetConnectionString("Accounts"))
);
services.AddDbContext<Support>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Configuration"))
);
// Erm?
SelectClientDatabase(services);
}
Obviously the next stage is to dip into the "Configuration" database, so I've been trying to keep that contained in "SelectClientDatabase()", which just takes the IServiceCollection as a parameter and is for all intents and purposes empty for now. Over the last few days I've found some excellent write-ups on EFC and I'm currently exploring a CustomConfigurationProvider as a possible route, but I must admit I'm a little lost on starting out in ASP.Net Core.
Is it possible to hook into the freshly added DbContext within the ConfigureServices method? Or can/must I add this database to the service collection at a later point?
Thanks!
Edit 1:
I just found this post, which mentions that a DbContext cannot be used within OnConfiguring as it's still being configured; which makes a lot of sense. I'm now wondering if I can push all three DbContexts into a custom middleware to encapsulate, configure and make the connections available; something new to research.
Edit 2:
I've found another post, describing how to "Inject DbContext when database name is only know when the controller action is called" which looks like a promising starting point; however this is for an older version of ASP.Net Core, according to https://learn.microsoft.com "DbContextFactory" has been renamed so I'm now working to update the example given into a possible solution.
So, I've finally worked it all out. I gave up on the factory idea as I'm not comfortable enough with asp.net-core-2.0 to spend time working it out & I'm rushing head-long into a deadline so the faster options are now the better ones and I can always find time to refactor the code later (lol).
My appsettings.json file currently just contains the following (the relevant bit of appsettings.Developments.json is identical):
{
"ConnectionStrings" : {
"Accounts": "Server=testserver;Database=Accounts;Trusted_Connection=True;",
"Client": "Server=testserver;Database={CLIENT_DB};Trusted_Connection=True;",
"Configuration": "Server=testserver;Database=Configuration;Trusted_Connection=True;"
},
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
}
}
I've opted to configure the two static databases in the ConfigureServices method of StartUp, these should be configured and ready to use by the time the application gets around to having to do anything. The code there is nice & clean.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.Configure<MvcOptions>(options =>
{
//options.Filters.Add(new RequireHttpsAttribute());
});
services.AddDbContext<AccountsContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Accounts"))
);
services.AddDbContext<ConfigContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Configuration"))
);
services.AddSingleton(
Configuration.GetSection("ConnectionStrings").Get<ConnectionStrings>()
);
}
It turns out that one can be spoilt for choice in how to go about accessing configuration options set in the appsettings.json, I'm currently trying to work out how I've managed to get it to switch to the release version instead of the development one. I can't think what I've done to toggle that...
To get the placeholder config setting I'm using a singleton to hold the string value. This is just dipping into the "ConnectionStrings" group and stuffing that Json into the "ClientConnection" object (detailed below).
services.AddSingleton(
Configuration.GetSection("ConnectionStrings").Get<ClientConnection>()
);
Which populates the following structure (that I've just bunged off in its own file):
[DataContract(Name = "ConnectionStrings")]
public class ClientConnection
{
[DataMember]
public string Client { get; set; }
}
I only want this holding the connection string for the dynamically assigned database, so it's not too jazzy. The "Client" DataMember is what is selecting the correct key in the Json, if I wanted a different named node in the Json I'd rename it to "Accounts", for instance.
Another couple of options I tested, before settling on the Singleton option, are:
services.Configure<ConnectionStrings>(Configuration.GetSection("ConnectionStrings"));
and
var derp = Configuration.GetSection("ConnectionStrings:Client");
Which I discounted, but it's worth knowing other options (they'll probably be useful for loading other configuration options later).
I'm not keen on the way the Controller dependencies work in ASP.Net Core 2, I was hoping I'd be able to hide them in a BaseController so they wouldn't have to be specified in every single Controller I knock out, but I've not found a way to do this yes. The dependencies needed in the Controllers are passed in the constructor, these weirded me out for a while because they're auto-magically injected.
My BaseController is set up as follows:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.EntityFrameworkCore.Internal;
using ServiceLayer.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
namespace ServiceLayer.Controllers
{
public class BaseController : Controller
{
private readonly ClientConnection connectionStrings;
private readonly AccountsContext accountsContext;
private readonly ConfigurationContext configContext;
public ClientTemplateContext clientContext;
private DbContextServices DbContextServices { get; set; }
public BaseController(AccountsContext accounts, ConfigContext config, ClientConnection connection) : base()
{
accountsContext = accounts;
configContext = config;
connectionStrings = connection;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
}
}
}
The code for selecting the database then goes in the "OnActionExecuting()" method; this proved to be a bit of a pain as well, trying to ensure that the dbcontext was set up properly, in the end I settled on:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ServiceLayer.Controllers
{
public class BaseController : Controller
{
private readonly ClientConnection connectionStrings;
private readonly AccountsContext accountsContext;
private readonly ConfigurationContext configContext;
public ClientTemplateContext clientContext;
private DbContextServices DbContextServices { get; set; }
public BaseController(AccountsContext accounts, ConfigurationContext config, ClientConnection connection) : base()
{
accountsContext = accounts;
configContext= config;
connectionStrings = connection;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
// Temporary selection identifier for the company
Guid cack = Guid.Parse("827F79C5-821B-4819-ABB8-819CBD76372F");
var dataSource = (from c in configContext.Clients
where c.Cack == cack
join ds in configContext.DataStorage on c.CompanyId equals ds.CompanyId
select ds.Name).FirstOrDefault();
// Proto-connection string
var cs = connectionStrings.Client;
if (!string.IsNullOrEmpty(cs) && !string.IsNullOrEmpty(dataSource))
{
// Populated ConnectionString
cs = cs.Replace("{CLIENT_DB}", dataSource);
clientContext = new ClientTemplateContext().Initialise(cs);
}
base.OnActionExecuting(context);
}
}
}
new ClientTemplateContext().Initialise() is a bit messy but I'll clean it up when I refactor everything else. "ClientTemplateContext" is the entity-framework-core generated class that ties together all the entities it generated, I've added the following code to that class (I did try putting it in a separate file but couldn't get that working, so it's staying in there for the moment)...
public ClientTemplateContext() {}
private ClientTemplateContext(DbContextOptions options) : base(options) {}
public ClientTemplateContext Initialise(string connectionString)
{
return new ClientTemplateContext().CreateDbContext(new[] { connectionString });
}
public ClientTemplateContext CreateDbContext(string[] args)
{
if (args == null && !args.Any())
{
//Log error.
return null;
}
var optionsBuilder = new DbContextOptionsBuilder<ClientTemplateContext>();
optionsBuilder.UseSqlServer(args[0]);
return new ClientTemplateContext(optionsBuilder.Options);
}
I also included using Microsoft.EntityFrameworkCore.Design; and added the IDesignTimeDbContextFactory<ClientTemplateContext> interface to the class. So it looks like this:
public partial class ClientTemplateContext : DbContext, IDesignTimeDbContextFactory<ClientTemplateContext>
This is where the CreateDbContext(string[] args) comes from & it allows us to create a new instance of a derived context at design-time.
Finally, the code for my test controller is as follows:
using Microsoft.AspNetCore.Mvc;
using ServiceLayer.Entities;
using System.Collections.Generic;
using System.Linq;
namespace ServiceLayer.Controllers
{
[Route("api/[controller]")]
public class ValuesController : BaseController
{
public ValuesController(
AccountsContext accounts,
ConfigurationContext config,
ClientConnection connection
) : base(accounts, config, connection) {}
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
var herp = (from c in clientContext.Usage
select c).FirstOrDefault();
return new string[] {
herp.TimeStamp.ToString(),
herp.Request,
herp.Payload
};
}
}
}
This successfully yields data from the database dynamically selected from the DataSource table within the Configuration database!
["01/01/2017 00:00:00","derp","derp"]
If anyone can suggest improvements to my solution I'd love to see them, my solution is mashed together as it stands & I want to refactor it as soon as I feel I'm competent enough to do so.
We are using EF 6 Code First Migrations in a relatively new project (i.e. there isn't a lot of clutter to contend with). Also, as this is an "enterprise-y" application, we have some specific deployment rules for our target database:
all application level data access must be done through a specific DB user (app-user)
this app-user does not have permissions to create new databases
Therefore, in order to correctly provision a new target database for this application, we need to:
CREATE DATABASE [database_name] CONTAINMENT = PARTIAL
CREATE USER [app-user] WITH PASSWORD=N'p#ssw0rd'
(plus assign specific DB roles to this new user)
I was hoping to carry this out by writing a custom IDatabaseInitializer<TContext>, but it seems that I cannot hook into the database initialization at the correct point.
Conceptually, I want to do this:
have one connection string that is used for read/write access to the DB, using the "controller" app-user user
have a separate connection string that is solely used for provisioning the DB, using more privileged credentials
The code I have tried to use looks a bit like this:
internal class ProvisionThenMigrateInitializer<TContext, TConfiguration>
: MigrateDatabaseToLatestVersion<TContext, TConfiguration>, IDatabaseInitializer<TContext>
where TContext : DbContext
where TConfiguration : DbMigrationsConfiguration<TContext>, new()
{
private readonly DbMigrationsConfiguration _readWriteConfiguration;
private readonly string _provisioningConnectionName;
public ProvisionThenMigrateInitializer(string readWriteConnectionName, string provisioningConnectionName)
{
_provisioningConnectionName = provisioningConnectionName;
_readWriteConfiguration = new TConfiguration
{
TargetDatabase = new DbConnectionInfo(readWriteConnectionName)
};
}
void IDatabaseInitializer<TContext>.InitializeDatabase(TContext context)
{
if (context.Database.Exists())
{
if (!context.Database.CompatibleWithModel(false))
{
DbMigrator migrator = new DbMigrator(_readWriteConfiguration);
migrator.Update();
}
}
else
{
// TODO - Create the DB and user here...
string[] sqlStatements =
{
"CREATE DATABASE [database_name] CONTAINMENT = PARTIAL ",
"USE [database_name]",
"CREATE USER [app_user] WITH PASSWORD=N'p#ssw0rd'",
"USE [database_name]",
"ALTER ROLE [db_datareader] ADD MEMBER [app_user]",
"ALTER ROLE [db_datawriter] ADD MEMBER [app_user]",
};
string connectionString = ConfigurationManager.ConnectionStrings[_provisioningConnectionName].ConnectionString;
SqlConnection sqlConnection = new SqlConnection(connectionString);
foreach (SqlCommand command in sqlStatements.Select(sqlStatement => new SqlCommand(sqlStatement, sqlConnection)))
{
command.ExecuteNonQuery();
}
context.Database.Create();
Seed(context);
context.SaveChanges();
}
}
I set the initializer to use in the static constructor of my DbContext derived class:
Database.SetInitializer(new ProvisionThenMigrateInitializer<Context, Configuration>(
DOMAIN_MODEL_CONNECTION_STRING_NAME,
DOMAIN_MODEL_PROVISIONING_CONNECTION_STRING_NAME));
However, when I attempt to use my fancy new custom database initializer, in the following manner, it just plain doesn't work:
using (Context c = new Context())
{
try
{
c.Database.Initialize(true);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
I think that by the time I attempt to call c.Database.Initialize(true) EF has already attempted to connect to the database (using the app_user credentials, not the "provisioning credentials), the connection attempt fails, and we bomb out.
Is it actually possible to use EF 6, Code First and Migrations in a way that will allow this provisioning of my database? If so, what am I doing wrong?
Many thanks.
Here's how I did it:
I have an 'admin' SQL login that is a member of the 'dbcreator' and 'securityadmin' fixed server roles.
I have two connection strings: one specifying the 'admin' sql login, and the other specifying the sql login name I have reserved for use by tenant connections to the db. The 'tenant' login gets created via the initial migration and is granted only reader and writer access to the domain model database.
I have a domain model.
I have my DbContext class.
I have a parameterless constructor on my DbContext class which specifies the ADMIN connection string and is intended to be used to run migrations; and I have another constructor which specifies the TENANT connection string and is the constructor used through the code for all db access made in the context of a logged on tenant user.
public Context()
: base(ADMIN_CONNECTION_STRING_NAME)
{
//etc
and
public Context(int tenantOrgId)
: base(TENANT_CONNECTION_STRING_NAME)
{
Prior to enabling migrations, I used the DbContext in a unit test, which resulted in EF Code First creating the DB catalog.
I have enabled migrations which has produced an inital DbMigration.
I then edited the initial DbMigration "Up" method to provision the tenant sql login and grant it membership to the reader and writer roles:
public override void Up()
{
SqlConnectionStringBuilder domainModelConnectionStringBuilder = new SqlConnectionStringBuilder(ConfigurationManager.ConnectionStrings[Context.TENANT_CONNECTION_STRING_NAME].ConnectionString);
string domainModelDatabaseName = domainModelConnectionStringBuilder.InitialCatalog;
Sql(string.Format("IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = 'gsp_domainmodel_tenant') CREATE LOGIN [gsp_domainmodel_tenant] WITH PASSWORD=N'ge0sp#tia!', DEFAULT_DATABASE=[{0}], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF", domainModelDatabaseName));
Sql(string.Format("USE [{0}]", domainModelDatabaseName));
Sql(string.Format("IF NOT EXISTS (SELECT * FROM [{0}].sys.database_principals WHERE name = 'gsp_domainmodel_tenant') CREATE USER [gsp_domainmodel_tenant] FOR LOGIN [gsp_domainmodel_tenant] WITH DEFAULT_SCHEMA=[gsp]", domainModelDatabaseName));
Sql(string.Format("USE [{0}]", domainModelDatabaseName));
Sql(string.Format("ALTER ROLE [db_datareader] ADD MEMBER [gsp_domainmodel_tenant]", domainModelDatabaseName));
Sql(string.Format("USE [{0}]", domainModelDatabaseName));
Sql(string.Format("ALTER ROLE [db_datawriter] ADD MEMBER [gsp_domainmodel_tenant]", domainModelDatabaseName));
CreateTable( //etc
That is all you need to do if you are happy with the team using Update-Database to apply migrations to their local DBs, and you are happy with exec'ing Migrate.exe on the command line to deploy the db on your build machine, and you are happy with deploying the db changes to production using your own wits.
You can go one step further and specify the MigrateDatabaseToLatestVersion initializer to automate deploying the migrations, both on local dev workstations and on environments that you deploy to.
The trick is that you need to make the MigrateDatabaseToLatestVersion initializer run using the parameterless DbContext constructor so that the migrations are applied in the context of the ADMIN sql login (not the TENANT). That is achieved thusly:
static Context()
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion());
// Make the initializer run now, with the parameterless constructor, such that the migrations are run using the admin connection string.
using(var initializerCtx = new Context())
{
initializerCtx.Database.Initialize(true);
}
}
You should be able to do what you want. Key to the matter is making sure context is accessed / renewed with right connection details.
Call the Migrate Method when it suits you in your code.
Change MigrateDatabaseToLatestVersion to match you migration strategy.
EDIT: I will try and summarise the idea and show a snippet sample.
Essentially I use a LUW class that defaults to DONT TOUCH DB.
The Luw needs the DBServer and DBName in constructor
I have a tool that gets the DBConnection for SQL Server
From an Admin ui I have a button. Migrate.
I can then trigger automatic migration when it suits.
I currently use Automatic. But this concept woudl apply fine to managed migrations.
public class Luw{
public Luw(string dataSource, string dbName ){ // constructor
Context = GetContext(dataSource, dbName );
}
public override void MigrateDb() {
// i put this method in my UoW class, I trigger Migrations when I want them to start.
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MYDbContext, MYSECIALMigrationConfiguration>());
// Context = GetDefaultContext(); //HERE GET THE CONTEXT WITH CORRECT CONNECTION INFO
Context.Database.Initialize(true);
}
public static MyDbContext GetContext(string dataSource, string dbName)
{
Database.SetInitializer(new ContextInitializerNone<MyDbContext>());
return new MyDbContext((MYTOOLS.GetSQLConn4DBName(dataSource,dbName )),true);
}
public class MYSPECIALMigrationConfiguration : MYBaseMigrationConfiguration<MYDbContext>{ }
public abstract class MYBaseMigrationConfiguration<TContext> : DbMigrationsConfiguration<TContext>
where TContext : DbContext{
protected MYBaseMigrationConfiguration() {
AutomaticMigrationsEnabled = true; // you can still chnage this later if you do so before triggering Update
AutomaticMigrationDataLossAllowed = true; // you can still chnage this later if you do so before triggering Update
}
public clas SQLTOOLS{
// ..... for SQL server....
public DbConnection GetSqlConn4DbName(string dataSource, string dbName) {
var sqlConnStringBuilder = new SqlConnectionStringBuilder();
sqlConnStringBuilder.DataSource = String.IsNullOrEmpty(dataSource) ? DefaultDataSource : dataSource;
sqlConnStringBuilder.IntegratedSecurity = true;
sqlConnStringBuilder.MultipleActiveResultSets = true;
var sqlConnFact = new SqlConnectionFactory(sqlConnStringBuilder.ConnectionString);
var sqlConn = sqlConnFact.CreateConnection(dbName);
return sqlConn;
}
I am attempting to use Entity Framework code based migrations with my web site. I currently have a solution with multiple projects in it. There is a Web API project which I want to initialize the database and another project called the DataLayer project. I have enabled migrations in the DataLayer project and created an initial migration that I am hoping will be used to create the database if it does not exist.
Here is the configuration I got when I enabled migrations
public sealed class Configuration : DbMigrationsConfiguration<Harris.ResidentPortal.DataLayer.ResidentPortalContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(Harris.ResidentPortal.DataLayer.ResidentPortalContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
The only change I made to this after it was created was to change it from internal to public so the WebAPI could see it and use it in it's databaseinitializer. Below is the code in the code in the Application_Start that I am using to try to initialize the database
Database.SetInitializer(new MigrateDatabaseToLatestVersion<ResidentPortalContext, Configuration>());
new ResidentPortalUnitOfWork().Context.Users.ToList();
If I run this whether or not a database exists I get the following error
Directory lookup for the file "C:\Users\Dave\Documents\Visual Studio 2012\Projects\ResidentPortal\Harris.ResidentPortal.WebApi\App_Data\Harris.ResidentPortal.DataLayer.ResidentPortalContext.mdf" failed with the operating system error 2(The system cannot find the file specified.).
CREATE DATABASE failed. Some file names listed could not be created. Check related errors.
It seems like it is looking in the totally wrong place for the database. It seems to have something to do with this particular way I am initializing the database because if I change the code to the following.
Database.SetInitializer(new DropCreateDatabaseAlways<ResidentPortalContext>());
new ResidentPortalUnitOfWork().Context.Users.ToList();
The database will get correctly created where it needs to go.
I am at a loss for what is causing it. Could it be that I need to add something else to the configuration class or does it have to do with the fact that all my migration information is in the DataLayer project but I am calling this from the WebAPI project?
I have figured out how to create a dynamic connection string for this process. You need to first add this line into your EntityFramework entry on Web or App.Config instead of the line that gets put there by default.
<defaultConnectionFactory type="<Namespace>.<ConnectionStringFacotry>, <Assembly>"/>
This tells the program you have your own factory that will return a DbConnection. Below is the code I used to make my own factory. Part of this is a hack to get by the fact that a bunch of programmers work on the same set of code but some of us use SQL Express while others use full blown SQL Server. But this will give you an example to go by for what you need.
public sealed class ResidentPortalConnectionStringFactory: IDbConnectionFactory
{
public DbConnection CreateConnection(string nameOrConnectionString)
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(ConfigurationManager.ConnectionStrings["PortalDatabase"].ConnectionString);
//save off the original catalog
string originalCatalog = builder.InitialCatalog;
//we're going to connect to the master db in case the database doesn't exist yet
builder.InitialCatalog = "master";
string masterConnectionString = builder.ToString();
//attempt to connect to the master db on the source specified in the config file
using (SqlConnection conn = new SqlConnection(masterConnectionString))
{
try
{
conn.Open();
}
catch
{
//if we can't connect, then append on \SQLEXPRESS to the data source
builder.DataSource = builder.DataSource + "\\SQLEXPRESS";
}
finally
{
conn.Close();
}
}
//set the connection string back to the original database instead of the master db
builder.InitialCatalog = originalCatalog;
DbConnection temp = SqlClientFactory.Instance.CreateConnection();
temp.ConnectionString = builder.ToString();
return temp;
}
}
Once I did that I coudl run this code in my Global.asax with no issues
Database.SetInitializer(new MigrateDatabaseToLatestVersion<ResidentPortalContext, Configuration>());
using (ResidentPortalUnitOfWork temp = new ResidentPortalUnitOfWork())
{
temp.Context.Database.Initialize(true);
}
Can SimpleMembership be used with EF model-first? When I try it, I get "Unable to find the requested .NET Framework Data Provider" when I call WebSecurity.InitializeDatabaseConnection.
To put it another way: I can't get the call to WebSecurity.InitializeDatabaseConnection to work when the connection string employs the System.Data.EntityClient provider (as it does when using the model-first paradigm).
To repro the issue, create an MVC 4 app, and replace the code-first UserProfile entity class (which you get for free with the MVC 4 template) with a model-first User class that you have created in the Entity Designer:
Create an MVC 4 app in VS 2012 and add a new, blank Entity Data
Model.
Add a new Entity named User to the model, with fields for Id,
UserName, and FullName. So, at this point, the User data entity is
mapped to a Users table and is accessed via a funky connection
string that employs the System.Data.EntityClient provider.
Verify that the EF can access the User entity. One easy way to do
that is to scaffold out a Users controller based on the User table
and its associated DbContext.
Edit the AccountModels.cs file to remove the UserProfile class and
its associated UsersContext class. Replace the references to the
(now missing) UserProfile and UsersContext classes with references
to your new User class and its associated DbContext class.
Move the call to InitializeDatabaseConnection from the
InitializeSimpleMembershipAttribute filter class to the
Application_Start method in Global.asax.cs. While you're at it,
modify the arguments to use your new User entity's connection
string, table name, and UserId column name.
Delete the (no longer used) InitializeSimpleMembershipAttribute
class and the references to it.
When you run the repro, it will get an Exception at the call to InitializeDatabaseConnection.
Bob
SimpleMembership can work with model first. Here is the solution.
1.InitializeSimpleMembershipAttribute.cs from MVC 4 Internet Application templete should look like this
namespace WebAndAPILayer.Filters
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Ensure ASP.NET Simple Membership is initialized only once per app start
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
try
{
WebSecurity.InitializeDatabaseConnection("ConnStringForWebSecurity", "UserProfile", "Id", "UserName", autoCreateTables: true);
}
catch (Exception ex)
{
throw new InvalidOperationException("Something is wrong", ex);
}
}
}
}
}
2.Delete CodeFirst Classes from AcountModel.cs
3.Fix AccountCotroler.cs to work with your Model-first DbContext (ExternalLoginConfirmation(RegisterExternalLoginModel model, string returnUrl) method)
4.Define your "ConnStringForWebSecurity" connection string which is not same as that funky conn string for model-first db access, notice that we use provider System.Data.SqlClient not System.Data.EntityClient
<connectionStrings>
<add name="ModelFirstEntityFramework" connectionString="metadata=res://*/Context.csdl|res://*/Context.ssdl|res://*/Context.msl;provider=System.Data.SqlClient;provider
connection string="data source=.\SQLEXPRESS;Initial
Catalog=aspnet-MVC4;Integrated
Security=SSPI;multipleactiveresultsets=True;App=EntityFramework""
providerName="System.Data.EntityClient" />
<add name="ConnStringForWebSecurity" connectionString="data source=.\SQLEXPRESS;Initial Catalog=aspnet-MVC4;Integrated
Security=SSPI" providerName="System.Data.SqlClient" />
</connectionStrings>
That's a bug in MVC 4. There's a workaround in this blog post.
As an action filter, InitializeSimpleMembershipAttribute hooks into OnActionExecuting to perform the lazy initialization work, but this can be too late in the life cycle. The Authorize attribute will need the providers to be ready earlier if it needs to perform role based access checks (during OnAuthorization). In other words, if the first request to a site hits a controller action like the following:
[Authorize(Roles="Sales")]
.. then you’ll have an exception as the filter checks the user’s role but the providers aren’t initialized.
My recommendation is to remove ISMA from the project, and initialize WebSecurity during the application start event.
1 - You need to enable migrations, prefereably with EntityFramework 5
2 - Move your
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "EmailAddress", autoCreateTables: true);
to your Seed method in your YourMvcApp/Migrations/Configuration.cs class
protected override void Seed(UsersContext context)
{
WebSecurity.InitializeDatabaseConnection(
"DefaultConnection",
"UserProfile",
"UserId",
"UserName", autoCreateTables: true);
if (!Roles.RoleExists("Administrator"))
Roles.CreateRole("Administrator");
if (!WebSecurity.UserExists("lelong37"))
WebSecurity.CreateUserAndAccount(
"lelong37",
"password",
new {Mobile = "+19725000000", IsSmsVerified = false});
if (!Roles.GetRolesForUser("lelong37").Contains("Administrator"))
Roles.AddUsersToRoles(new[] {"lelong37"}, new[] {"Administrator"});
}
Now EF5 will be in charge of creating your UserProfile table, after doing so you will call the WebSecurity.InitializeDatabaseConnection to only register SimpleMembershipProvider with the already created UserProfile table (In your case, you can replace the "UserProfile" parameter value with your custom table name), also tellling SimpleMembershipProvider which column is the UserId and UserName. I am also showing an example of how you can add Users, Roles and associating the two in your Seed method with custom UserProfile properties/fields e.g. a user's Mobile (number).
3 - Now when you run update-database from Package Manager Console, EF5 will provision your table with all your custom properties
For additional references please refer to this article with sourcecode:
http://blog.longle.net/2012/09/25/seeding-users-and-roles-with-mvc4-simplemembershipprovider-simpleroleprovider-ef5-codefirst-and-custom-user-properties/
this problem caused by WebSecurity.InitializeDatabaseConnection can't use connection string with System.Data.EntityClient provider name.
providing dual connection string isn't sound good, so you can generate the connection string for EF model first in the constructor in the partial class.
the code is look like bellow
public partial class MyDataContext
{
private static string GenerateConnectionString(string connectionString)
{
var cs = System.Configuration.ConfigurationManager
.ConnectionStrings[connectionString];
SqlConnectionStringBuilder sb =
new SqlConnectionStringBuilder(cs.ConnectionString);
EntityConnectionStringBuilder builder =
new EntityConnectionStringBuilder();
builder.Provider = cs.ProviderName;
builder.ProviderConnectionString = sb.ConnectionString;
builder.Metadata = "res://*/MyDataContext.csdl|" +
"res://*/MyDataContext.ssdl|res://*/MyDataContext.msl";
return builder.ToString();
}
public MyDataContext(string connectionName) :
base(GenerateConnectionString(connectionName)) { }
}
with this trick you can use single connection string on your web config, but one problem you can't use default constructor on your datacontext, instead you should seed connection string name everywhere when you instantiate the datacontext. but it is not a big problem when you use dependency injection pattern.
I´m not able to work with EF and WebMatrix webSecurity class so to avoid this problem and go ahead:
Change my Ef model first to code first.
Change the connection string to use providerName="System.Data.SqlClient"(removing all the metadata information) or use the EF connection
In my case the model, data and web are different proyects so for me is not an issue to remove this information from the web.config on the web.project.
Nowadays websecuroty.initializedatabase dosen't run with EF connection string.
I wish this helps