Reputation: 9167
I'm sure I've done this before at some stage, but I can't figure out how to now! My scenario:
// This is generated from EDMX
public partial class HOLDbEntities : DbContext
{
public HOLDbEntities()
: base("name=HOLDbEntities")
{
}
}
Now, I want this connection string to be easily changeable (I want to Implement from the HOLDbEntities), so I need to override this constructor.
I've tried:
public partial class HOLDbEntities
{
private const string _contextName = "HOLDbEntities";
public static string ContextName { get { return _contextName; } }
public HOLDbEntities()
: base(ContextName)
{
}
}
But this throw an error:
HOLDbEntities already defines a member called "HOLDbEntities" with the same parameter types.
I can understand why this errors, but how would I stop the constructor being auto-generated in the first place in order to do what I'm trying to achieve?
Upvotes: 12
Views: 16338
Reputation: 13397
I used the string interpolations feature of C# 6 in the .tt
code generation file.
The generated code becomes
public MyEntities()
: base($"name={MyConfigurationManager.ConnectionStringKey("MyEntities")}")
{
}
when you use
public <#=code.Escape(container)#>()
: base($"name={MyConfigurationManager.ConnectionStringKey("<#=container.Name#>")}")
in the .tt
file.
public static string ConnectionStringKey(string key)
in static class MyConfigurationManager
in my case adds the initials of the login to the key and checks whether any connection string in ConfigurationManager.ConnectionStrings
has that key in which case that key is returned and otherwise just returns the default key.
So now the connectionstring may be different per-user.
E.g.
<add name="MyEntities" connectionString="metadata=res://*/Base.csdl|res://*/Base.ssdl|res://*/Base.msl;provider=System.Data.SqlClient;provider connection string="data source=localhost;initial catalog=local.base;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
<add name="MyEntitiesFB" connectionString="metadata=res://*/Base.csdl|res://*/Base.ssdl|res://*/Base.msl;provider=System.Data.SqlClient;provider connection string="data source=localhost;initial catalog=local.fb.base;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
means that user F.B. uses the latter key while all others the former key.
Upvotes: 0
Reputation: 2366
I up-voted the previous accepted answer because it is a fairly elegant way of doing it. However another approach would be to modify the T4 template that generates the dbContext Class.
When using EF DB first you have a .edmx file and under that you have an [Entity].Context.tt file. Go into that file and remove (or modify) the following code:
public <#=code.Escape(container)#>()
: base("name=<#=container.Name#>")
{
<#
if (!loader.IsLazyLoadingEnabled(container))
{
#>
this.Configuration.LazyLoadingEnabled = false;
<#
}
foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>())
{
// Note: the DbSet members are defined below such that the getter and
// setter always have the same accessibility as the DbSet definition
if (Accessibility.ForReadOnlyProperty(entitySet) != "public")
{
#>
<#=codeStringGenerator.DbSetInitializer(entitySet)#>
<#
}
}
#>
now your context class will generate without a constructor, so you should be able to go and create one in an extended class.
Upvotes: 21
Reputation: 788
i changed the context.tt as follows:
<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext
{
public <#=code.Escape(container)#>()
: base("name=<#=container.Name#>")
{
<#
if (!loader.IsLazyLoadingEnabled(container))
{
#>
this.Configuration.LazyLoadingEnabled = false;
<#
}
foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>())
{
// Note: the DbSet members are defined below such that the getter and
// setter always have the same accessibility as the DbSet definition
if (Accessibility.ForReadOnlyProperty(entitySet) != "public")
{
#>
<#=codeStringGenerator.DbSetInitializer(entitySet)#>
<#
}
}
#>
var Method = (typeof(Entities)).GetMethods(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).FirstOrDefault(x => x.Name == "OnModelConstructed");
if (Method!=null) Method.Invoke(this,null);
}
so i can declare a OnModelConstructed method in a partial class of the context.
Upvotes: 3
Reputation: 77
Here is my solution to the problem. I edit the TT file as Dylan Hayes suggested and replaced the constructor with my own. In my case, i needed to change the schema names of certain schemas only. I set a variable in the config file to tell me what environment I was in and used the right schema.
using System.Configuration;
using System.Data.Entity;
using System.Data.Entity.Core.Mapping;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Reflection;
using System.Xml;
namespace WS.Framework.WSJDEData
{
public partial class WSJDE : DbContext
{
public WSJDE()
: base("name=WSJDE")
{
ObjectContext context = (this as IObjectContextAdapter).ObjectContext;
string environment = ConfigurationManager.AppSettings.Get("Environment");
const string devCTL = "TESTCTL";
const string devDTA = "TESTDTA";
const string qaCTL = "CRPCTL";
const string qaDTA = "CRPDTA";
const string prodCTL = "PRODCTL";
const string prodDTA = "PRODDTA";
var x = Assembly.GetExecutingAssembly().GetManifestResourceStream("WSJDEData.WSJDE.ssdl");
XmlReader[] sReaders = new XmlReader[]
{
XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("WSJDEData.WSJDE.ssdl"))
};
XmlReader[] mReaders = new XmlReader[]
{XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("WSJDEData.WSJDE.msl"))};
StoreItemCollection sCollection = new StoreItemCollection(sReaders);
ObjectContext objContext = ((IObjectContextAdapter) context).ObjectContext;
MetadataWorkspace workspace = objContext.MetadataWorkspace;
EdmItemCollection cCollection = workspace.GetItemCollection(DataSpace.CSpace) as EdmItemCollection;
StorageMappingItemCollection csCollection = new StorageMappingItemCollection(cCollection, sCollection,
mReaders);
workspace.RegisterItemCollection(sCollection);
workspace.RegisterItemCollection(csCollection);
EntityContainer container = workspace.GetItem<EntityContainer>("WSJDEModelStoreContainer", DataSpace.SSpace);
foreach (EntitySetBase entitySetBase in container.BaseEntitySets)
{
string schema = entitySetBase.Schema;
if (schema != null)
{
string name = schema.Substring(schema.Length - 3);
if (name == "CTL")
{
switch (environment)
{
case "Dev":
typeof (EntitySetBase).GetField("_schema",
BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(entitySetBase, devCTL);
break;
case "QA":
typeof (EntitySetBase).GetField("_schema",
BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(entitySetBase, qaCTL);
break;
case "Prod":
typeof (EntitySetBase).GetField("_schema",
BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(entitySetBase, prodCTL);
break;
}
}
if (name == "DTA")
{
switch (environment)
{
case "Dev":
typeof (EntitySetBase).GetField("_schema",
BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(entitySetBase, devDTA);
break;
case "QA":
typeof (EntitySetBase).GetField("_schema",
BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(entitySetBase, qaDTA);
break;
case "Prod":
typeof (EntitySetBase).GetField("_schema",
BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(entitySetBase, prodDTA);
break;
}
}
}
}
}
}
}
Upvotes: 0
Reputation: 1062975
The best I can suggest is a factory method:
private HOLDbEntities(string contextName) : base(contextName) { }
public static HOLDbEntities Create() {
return new HOLDbEntities(ContextName);
}
and use HOLDbEntities.Create()
rather than new HOLDbEntities()
.
Upvotes: 10