At work we currently have a very large web application with a connection to a massive database. We have been using Entity Framework for a while now and to make things easier we divided the database into many Entity models. This works well for us but we ran into an issue. Each EF model needs its own connection string due to the metadata part of the connection string. Managing so many connection string is a pain.
Now I have a solution that I think will work. I am going to create a class that will have the metadata info saved as a property also concatenated to the standard connection string in the web.config. So when we use the connection string "Database.EntityConnectionString" it will give me the Entity Connection string but we only have to manage a single connection string in the web.config. We will still have to manage the class with the metadata but Models don't change very much and we don't create them often so maintenance should be fine. My question, is there a better way of dealing with this issue or how would you do it?
Thanks!
This is how I have implemented my solution to this problem:
namespace DBLibrary
{
public enum Models
{
Model1,
Model2
}
public static class Database
{
public static string EntitiesConnectionString(Models model)
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(ConfigurationManager.ConnectionStrings["Default"].ConnectionString);
builder["MultipleActiveResultSets"] = true;
builder["Connect Timeout"] = 30;
EntityConnectionStringBuilder entityBuilder = new EntityConnectionStringBuilder();
entityBuilder.Provider = "System.Data.SqlClient";
entityBuilder.ProviderConnectionString = builder.ConnectionString;
switch (model)
{
case Models.Model1:
entityBuilder.Metadata = "res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl";
return entityBuilder.ToString();
case Models.Model2:
entityBuilder.Metadata = "res://*/Model2.csdl|res://*/Model2.ssdl|res://*/Model2.msl";
return entityBuilder.ToString();
default:
throw new Exception("Invalid model, no connection string defined");
}
}
}
}
I still need to clean up the code and all but I think this give you a good idea on how this can be implemented. I would still be very interested if there are different and better ways of doing this.
Thanks!
Add Default Construction in your Class
public class ItemContext : DbContext
{
public DbSet<Item>Items get; set; }
public DbSet<ItemDetail> ItemDetails { get; set; }
public ItemContext ()
{
this.Database.Connection.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
}
}
I had the same problem. I have solved it by following way:
I have created two edmx file, but while creating second edmx file, i ignored the connection string to be save in config file. This way my config file will hold only one Connection string.
Then i modified following lines in my connection string:
<add name="MyDbContext" connectionString="metadata=res://*/;provider=System.Data.SqlClient;provider connection string="data source=abc;initial catalog=mydb;persist security info=True;user id=myuser;password=password;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
Just replace "res://model1.csdl" with "res://*/" and it works like a charm.
You can specify this connection name in constructor of your dbcontext class like:
public MyDbContext() : base("name=NameOfYourConnectionString") // Name of your connection string
{ }
Note: I am using Entity Framework 5.0.
Related
I am currently using Mehdi El Gueddari's DbContextScope project, I think by the book, and it's awesome. But I came across a problem I'm unsure how to solve today. I have a query that I need to execute using a different database login/user because it requires additional permissions. I can create another connection string in my web.config, but I'm not sure how to specify that for this query, I want to use this new connection string. Here is my usage:
In my logic layer:
private static IDbContextScopeFactory _dbContextFactory = new DbContextScopeFactory();
public static Guid GetFacilityID(string altID)
{
...
using (_dbContextFactory.CreateReadOnly())
{
entity = entities.GetFacilityID(altID)
}
}
That calls into my data layer which would look something like this:
private AmbientDbContextLocator _dbcLocator = new AmbientDbContextLocator();
protected CRMEntities DBContext
{
get
{
var dbContext = _dbcLocator.Get<CRMEntities>();
if (dbContext == null)
throw new InvalidOperationException("No ambient DbContext....");
return dbContext;
}
}
public virtual Guid GetFaciltyID(string altID)
{
return DBContext.Set<Facility>().Where(f => f.altID = altID).Select(f => f.ID).FirstOrDefault();
}
Currently my connection string is set in the default way:
public partial class CRMEntities : DbContext
{
public CRMEntities()
: base("name=CRMEntities")
{}
}
Is it possible for this specific query to use a different connection string and how?
I ended up modifying the source code in a way that feels slightly hacky, but is getting the job done for now. I created a new IAmbientDbContextLocator with a Get<TDbContext> method override that accepts a connection string:
public TDbContext Get<TDbContext>(string nameOrConnectionString) where TDbContext : DbContext
{
var ambientDbContextScope = DbContextScope.GetAmbientScope();
return ambientDbContextScope == null ? null : ambientDbContextScope.DbContexts.Get<TDbContext>(nameOrConnectionString);
}
Then I updated the DbContextCollection to pass this parameter to the DbContext's existing constructor overload. Last, I updated the DbContextCollection maintain a Dictionary<KeyValuePair<Type, string>, DbContext> instead of a Dictionary<Type, DbContext> as its cached _initializedDbContexts where the added string is the nameOrConnectionString param. So in other words, I updated it to cache unique DbContext type/connection string pairs.
Then I can get at the DbContext with the connection I need like this:
var dbContext = new CustomAmbientDbContextLocator().Get<CRMEntities>("name=CRMEntitiesAdmin");
Of course you'd have to be careful your code doesn't end up going through two different contexts/connection strings when it should be going through the same one. In my case I have them separated into two different data access class implementations.
When you derive from DbContext and use the parameter-less constructor it will load a connection string from web.config. You also have the option of explicitly specifying the connectionString using one of the other DbContext constructors.
My particular situation dictates that the connection string CANNOT be specified in the web.config, as the location of the server/username and password are determined at runtime. Easy fix right? Just use the above mentioned constructor to specify the connection string? Wrong.
The problem is that when you specify the connection string using said constructor, it still attempts to use the default provider, so if you're using one or more non standard providers, as I am, it will not work.
I'm sure I can change the default provider in the web.config, but I want to use multiple providers so this will not do.
The only possible way around this that I can see is to use ObjectContext instead of DbContext, which seems to allow you to specify the provider along with the database connection string.
Is there any other way to do it? Is my workaround fairly reasonable?
I believe I can also create a DbContext from an ObjectContext instance.
Create your DbConnection manually and pass it to the DbContext constructor as follows:
var conn = DbProviderFactories.GetFactory("MY_CONN_PROVIDER").CreateConnection();
conn.ConnectionString = "MY_CONN_STR";
new DbContext(conn, true);
Notice the second parameter bool contextOwnsConnection is true. Since you don't re-use the connection elsewhere, it lets the context manage the connection and Dispose() it when needed.
You can get to the ObjectContext through IObjectContextAdapter:
((IObjectContextAdapter)context).ObjectContext
DbContext ("context" above) still wraps ObjectContext, so don't worry that you will have a new instance.
You can instantiate DbContext using this overload
public DbContext(ObjectContext objectContext, bool dbContextOwnsObjectContext) {}
for example:
public class YourDbContext : DbContext
{
public YourDbContext() : this(new YourObjectEntities(), dbContextOwnsObjectContext: true)
{}
}
Then you can set your connection string inside of YourObjectEntities:
public partial class YourObjectEntities : ObjectContext
{
public const string ConnectionString = "name=YourEntities"; // Get it from somewhere
public YourObjectEntities() : base(ConnectionString, "YourEntities")
{
// Some initialization, e.g. ContextOptions.LazyLoadingEnabled = false;
}
}
How you specify the provider there is your exercise.
Try like this ,
public DBDataContext _dataContex;
public DBDataContext DBContext
{
get
{
if (_dataContex== null)
{
_v= new DBDataContext(ConfigurationManager.ConnectionStrings["yourConnectinString"].ConnectionString);
}
return _dataContex;
}
}
I am trying to initialize an Entity object (ADO.NET EF Object), but it does not allow me to choose what connection string I want to use. I need to change connection string in order to give different access levels to users.
There are no overrides in the Entities Object, just a parameterless constructor.
If anyone can give me any pointers, it is appreciated.
If you have used the designer to generate an .edmx file for you, you will have something like below:
public MyEntities() : base("name=MyEntities", "MyEntities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
This will by default, get the connection string from your configuration file.
What you could do in this case is set the connection string
public partial class MyEntities
{
partial void OnContextCreated()
{
//Dynamically Building a Connection String
this.Connection.ConnectionString = "myconnectionstring";
}
}
Bear in mind though that this will first use the base constructor to pull the connection string from config, then set it with your custom version, basically overriding the connection string. This is typically good when you always want a default connection string.
Another option if you want a bit more control, is pass the connection string in via the constructor as shown below:
public partial class MyEntities
{
public MyEntities(string connectionString) :
base(connectionString,"MyEntities")
{
this.OnContextCreated();
}
}
Now you are passing in the connection string down to the base class and this is the only one it will use. This does mean however that you will most often need to supply this each time.
Hope this helps...
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
Ok, I want to recreate a project that I created using EF 4.1 to EF 5.0, simple enough or at least I thought. One of the things in my old project is that I was able to change the database connection string at runtime in EF 4.1:
using (var myContext = new MyEntities(ConnectionString))
{
}
Easy-peasy but in EF 5.0 you have to do this differently:
string connectionString = "data source=LocalHost;initial catalog=MyDatabase;user id=MyUserName;password=MyPassword;multipleactiveresultsets=True;App=EntityFramework";
using (var myContext = new MyEntities())
{
myContext.Database.Connection.ConnectionString = connectionString;
}
Now, this took me a better part of two hours to figure out, so I guess my question is this the proper way of changing the connection string at runtime or not? If it is why did they make this change?
I did find this Link but it didn't work. I received the error as detailed in the first comment of the first answer by Ladislav Mrnka. I later found this Link which seems to work fine.
UPDATE
I re-read the first link I posted and I found another solution, I simply created a partial class:
public partial class MyEntities : DbContext
{
public MyEntities(string connectionString) : base(connectionString)
{
Database.Connection.ConnectionString = connectionString;
}
}
Use the context constructor overload that takes the connection string as a parameter.
Create a class with the same name as the Target ContextClass class next to the main class
like this :
public CustomerContext( string connectionString)
: base(connectionString)
{
}
For using :
using (var context = new CustomerContext("connectionString"))
{
}
Or
var customerContext=new CustomerContext("yorConnectionString");
var customer=CustomerContext.customer.FirstOrDefault(x=>x.id==1).FirstName;
Have a look at other link Setup Entity Framework For Dynamic Connection String.
It says - " you can do it by creating another partial class as the Entities class is declared partial"