Here's the long nontrimmed version of my answer, with all the babbling. If somebody is interested in, the question came from StackOverflow: Dynamic Connection String in Entity Framework.
Ok, as I have not found any trace of the code I produced back then, I started digging again, trying to remember what I tried doing. I remember that finally everything almost worked but I got stuck at ... not being able to provide nor build an instance of EntityConnection because its connection string required three mapping files and as I used Code-First I simply didn't have them. However, in your case it should do the trick.
So.. the most important thing is of course the InternalContext. It's the brain here and it can be initialized in many ways, drived by various DbContext constructors. I first targetted the "ObjectContext" overload and tried to provide my own already-connected instance, but that proved impossible as I finally managed to get hold on a service that returns a provider that returns a factory that builds ObjectContexts ... but this factory required me to pass a DbContext. Remember that I wanted to have ObjCtx to be able to create call DbCtx ctor's overload.. Zonk.
I decompiled the factory and yet again it turned out that current implementation of DbCtx/ObjCtx and this factory that the only point where you can actually callable is inside the DbContext's constructors. The ObjectContext produced by the factory gets bound to that specific DbCtx and it's not possible to untangle them later. I remember that it's all about Connections and Metadata. It was something like "DbCtx provides connection", "DbCtx delegates all other jobs to ObjCtx", "ObjCtx provides metadata" but "ObjCtx does not know where to get metadata from", so it asks a 'service' to find it, which in turn looks up for the connection, which it gets from DbCtx..
There was also some play with IObjectContextAdapter, but I don't remember it now, except for the fact that I got to a point where I rolled in my own ObjectContext2
class (I think I just subclassed the ObjectContext) which provided everything all by itself and didn't need that factory, but I got stuck at not being able to manually construct and initialize Metadata(Workspace?) properly.
So, that was the end of "EagerInternalConnection" path, which was the most promising one, since it did not rely on any defaults and just took everything I provided to it.
The last thing left was InitializeLazyInternalContext
(you mentioned), which basically initializes everything according to defaults. That's either the true defaults related to the name of the context class, or you can provide a "nameOrConnectionString" (which I could not use since it expects EntityConnectionString and I used Code-First). Or, there's third option that takes DbCompiledModel
. It's protected
but when you inherit from DbContext you can call it easily.
It's a bit tricky to get an instance of DbCompiledModel though. It can't be built directly, it has to be obtained from DbModelBuilder, which in turn is often only temporarily available during your XYZContext initialization. Surely you remember OnModelCreating(DbModelBuilder)
method where all model configurations take place. So.. but it's inside XYZContext and you need the CompiledModel before you start construction your XYZContext/DbContext again. You can refactor all the model-setup out of the context, or you may simply hack in and expose the protected OnModelCreating method even statically to be able to build models manually. However, there are few bits missing in the OnModelCreating method. There are some base policies and conventions that are set before this method is called, so if you'll be creating them manually.. you'd need to again decompile&find the part that sets them, and I remember it's well private/hidden so you'd need to invoke it via reflection or copy all the setup the code..
Anyways, after refactoring some code I managed to have the DbModelBuilder configured, model build, compiled, passed to new MyDbModel constructor -- and it gridnded to halt again, because of some issues I dont recall now, Sorry..
The factories/services I talked about. It's all around IDbDependencyResolver
interface. Look at System.Data.Entity.Infrastructure.DependencyResolution
. You'll find it inside. There's handy implementation of a service/instace resolver called SingletonDependencyResolver<T>
that will be sufficient in most cases.
Registering resolvers is tricky. If I remember well, I injected the resolvers through (same namespace)DbConfigurationManager.(static)Instance.GetConfiguration()
which returns the IoC configuration object and .AddDependencyResolver(..)
or .RegisterSingleton<TService>
. All internal though, so must use reflection here. I remember that the IoC and resolvers can also be configured with special entries in app.config settings, but I didn't use it as I was focused at 100% runtime configuration. Now I see that actually I didn't need that, when I wanted to inject the resolvers I could certainly do that from appconfig, as the resolver classes would not change in the runtime. You may try so instead of reflection.
Ok, so the resovlers.. There are a bunch of them you can override. I remember playing with ProviderFactoryResolver
, InvariantNameResolver
, and ProviderServiceResolver
. The first one is meant to provide DbProviderFactory
that is the root of all connection builders. The second one extracts 'invariant name' from the factory - that's in fact the "dialect" name like "sql2008". Many things are keyed/identified by this "invariant name". I don't remember much about the third ProviderServiceResolver
one.
Look first at DbProviderFactoryResolver
. If you register your own resolver if this type, you will be able to inject your own DbProviderFactory
-ies. Your own DbProviderFactory
seems crucial. Look at decompiled EntityConnection
and its ChangeConnection
method. It is one of the core methods that, well, sets or changes the connection string.
private void ChangeConnection(string ..blah..) {
...
DbProviderFactory fac = null;
...
fac = DbDependencyResolverExtensions.GetService<DbProviderFactory> ( ... )
...
connection = EntityConnection.GetStoreConnection(fac);
...
connstr = ..blah..["provider connection string"];
DbInterception.Dispatch.Connection.SetConnection(connection, new DbConnectionPropertyInterceptionContext<string>(..).WithValue(connstr) );
}
Yep, the DbProviderFactory
you see there is just it. Its CreateConnection
returns a DbConnection
(it can be yours) and then it configured with 'provider connection string' right from the settings from the entityconnectionstring section. So, if you additionally roll in your own DbConnection
, build by your own DbProviderFactory
, it could then ignore the incoming connstring in favor of whatever you want.
Also, I have not investigated the DbInterception.Dispatch.Connection.SetConnection
part because back then my play-time has run out. There is some slight chance that Interception
is mean literally and that you will be able to "just register" an interceptor that will override the "SetConnectionString".
But, well.. that's enormous amount of work. Really. Using separate file to exploit partial
to expose additional base DbContext constructor and then inheriting from the autogenerated context class and providing the connstring through that is much much simpler!
No comments:
Post a Comment