3

GOAL using Database-First Paradigm (not Code-First) for a deployed desktop wpf application, with unique databases for end users:

1) Have EntityFramework use a connection string determined at run time.
2) Not deploy different app.config files.

Things attempted:

1) Overload the constructor - while successful, this solution is undesired as it leaves the door open for developers to make mistakes, makes unit testing more difficult.
2) Attempted modifying the connection / context factory - threw Exception.
3) Change the default constructor - could be successful, this solution is undesired as the default constructor is autogenerated.
4) Attempted modifying the ConfigurationSettings - threw Exception, it is read-only.
5) Have a customer side deployment of app.config - while plausible, this solution is undesired as it requires a rewrite of our deployment engine.

Help?

EDIT: Some code related to first item we tried (overloading the constructor):

public partial class DatabaseContext
{
    public DatabaseContext(EntityConnection con)
        : base(con, true)
    {
    }
}

public static class DbContextHelper
{
    public static string ConnectionString { get; set; }

    public static CounterpartDatabaseContext GetDbContext()
    {
        EntityConnectionStringBuilder builder = new EntityConnectionStringBuilder
        {
            Provider = "System.Data.SqlClient",
            ProviderConnectionString = ConnectionString,
            Metadata = @"res://*/DatabaseContext.csdl|res://*/DatabaseContext.ssdl|res://*/DatabaseContext.msl"
        };
        EntityConnection con = new EntityConnection(builder.ToString());
        return new DatabaseContext(con);
    }
}

Usage:

public void SomeMethod()
{
    using(DatabaseContext db = DbContextHelper.GetDbContext())
    {
       // db things
    }
}

EDIT code for adding connection string with config manager:

public MainWindow()
{
    InitializeComponent();
        ConfigurationManager.ConnectionStrings.Add(new ConnectionStringSettings("DatabaseContext", @"metadata=res://*/DatabaseContext.csdl|res://*/DatabaseContext.ssdl|res://*/DatabaseContext.msl;provider=System.Data.SqlClient;provider connection string="data source=sqldev;initial catalog=Dev;persist security info=True;user id=user;password=password;MultipleActiveResultSets=True;App=EntityFramework"", "System.Data.EntityClient"));
}

the config manager code just throws an exception, so no point in any code after that.

2
  • It's good you've told us what have you tried, but you should have shown us some examples. After all, we're programmers, we want to see the code :) Commented Jan 7, 2015 at 23:02
  • No longer have the code for factory changes (as it threw exceptions never committed the changes). Commented Jan 7, 2015 at 23:13

1 Answer 1

3

Generated DatabaseContext class is both partial. With partial you can add code in another file (just remember about partial keyword there) and still be to re-generate everything. Generator will only overwrite the file it generated, all other files with extra additions to that partial class will not evaporate. No problem with mantaining generated and handwritten parts there.

Also, the generated class is not sealed. You can inherit from it. So, instead of using DatabaseContext directly, you might try inheriting from it and start using the derived class. This derived class will not inherit the constructors, but will inherit all other public important things. You will be able then to provide your own constructor, even default one, that will i.e. call parameterized base class ctor. Actually, I have not tried it that way, but it looks simple and may work.

What I propose is not using DbContextHelper.GetContext() (which is static obviously) (which you think the devs may misuse or forget), but rolling in your own DbContext class.

In the project where you have the EDMX and generated DatabaseContext context, add a file with:

public partial class DatabaseContext
{
    protected DatabaseContext(string nameOrConnstring) : base(nameOrConnstring) { }
}

it will add a new overload, it will expose the base DbContext constructor that takes the connstring.

Then add another file to that:

public class ANewContext : DatabaseContext
{
    public ANewContext() : base(DbContextHelper.FindMyConnectionString()){ }
}

and that's all. Since your helper was static anyways, then we can call it like that. Just change it to return the connstring props, which it had needed to determine anyways.

Now rename the classes:

DatabaseContext -> InternalDatabaseContextDontUseMe
ANewContext -> DatabaseContext

or something like that, and I bet noone will be ever confused as to which one of them should be used everywhere. Usage:

public void SomeMethod()
{
    using(var db = new DatabaseContext()) // that's ANewContext after renaming
    {
       ...
    }
}

With partial in the InternalDatabaseContextDontUseMe, you will be able to regenerate the model, and the extra added ctor will not be deleted. With one extra inheritance level, the autogenerated default constructor will be hidden, and devs using the derived class will not be able to accidentally call it, they'll receive new default ctor that will do what's needed.


If you are really interested in hearing about what I found while digging in the EF source, LazyContext, Factories, Resolvers and etc, look at this article of fine. I put there everything I could recall today and while it's somewhat chaotic, it may help you if you like digging&decompiling. Especially the EntityConnection-DbProviderFactory-Resolvers mentioned at the end.

Sign up to request clarification or add additional context in comments.

5 Comments

You are correct that the Context is a partial. However, in order to instantiate a context with a different connection string than what is in the app.config file, one must call the base constructor with the connection. Upon decompiling, the base constructor calls an internal method: InitializeLazyInternalContext. If we inherit from the context, then we lose access to this base constructor. That was why we thought of the work around of a DbContextHelper to get a context instantiated in the manner we need. Tell me more about the ConnectionFactory/Provider, as we only got that to half work.
@Greg: InitializeLazyInternalContext - yes, I remember that one. However, it was one only a tip of an iceberg. I've found the project where I played with it and it's turned out it was CodeFirst (but since InitializeLazyInternalContext is in your case, it probalby Code/Db-First is not important) and that I researched that topic in context of .. testing. I was trying to magically reroute all DB-Ops to a test database without touching the appconfig. Currently, I find no trace of that, and I seem to have moved to mocking with using InMemoryAsyncQueryable instead of real test database.
@Greg: but I can't believe I just deleted all the findings.. it must be somewhere..
@Greg: ok, I found out something, but the text has grown so much, that I've moved it to a side page. I've added a link at the end of the answer, it will point you to the article. Also, I've edited the answer and clarified what I meant about exploiting partial and inheritance to get what you want. Really, I think that this simple approach will be enough.
So, that inheritance does work. And with the helper came up with a great way (using a bool) to make unit tests happy. Also, thank you so much :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.