Chris Dixon
Chris Dixon

Reputation: 9167

Overriding code-generated DbContext constructor

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

Answers (5)

Gerard
Gerard

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=&quot;data source=localhost;initial catalog=local.base;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework&quot;" 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=&quot;data source=localhost;initial catalog=local.fb.base;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />

means that user F.B. uses the latter key while all others the former key.

Upvotes: 0

Dylan Hayes
Dylan Hayes

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

Developer
Developer

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

Patrick Wensel
Patrick Wensel

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

Marc Gravell
Marc Gravell

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

Related Questions