Reputation: 99
I have a WPF application for which my users can create their own plugins by using MEF. Each plugin implements an interface that allows the main application to perform CRUD operations on some data source, e.g. a database.
I have created 2 plugins:
Both are using Entity Framework to do their job. Each of those plugins needs to have its own implementation of the DbConfiguration class.
Now, the problem is that the WPF application loads those 2 plugins, but fails to assign each of them their own implementation of the DbConfiguration class, because it seems that you can have only one DbConfiguration per AppDomain. So I always have only one of those plugins working.
I was thinking about having just one implementation of the DbConfiguration class and give each plugin an option to add its required configs to that, but the problem is that it creates some coupling between the WPF application and Entity Framework. I'd like to keep the Entity Framework stuff only inside the plugins without the need of modifying the WPF application. It shouldn't care about what plugins use to access their data source.
Is there any way of making it work this way? Could I maybe somehow create a separate AppDomain per each plugin, so maybe then each could use its own DbConfiguration class?
Upvotes: 0
Views: 125
Reputation: 99
I've found a solution which is a bit hacky, but it does seem to work, so I thought I'd post it, in an unlikely case that someone would face the same issue somewhere in the future.
After some additional research, I've learnt that you can use the DbConfiguration.Loaded
event to register some additional Dependency Resolvers for EF. So, in each plugin's constructor, I subscribe the event and add a new Dependency Resolver: SQLite for the LocalDatabase
and MySql for the RemoteDatabase
. I got rid of the custom DbConfiguration
classes from each plugin.
This looked promising, but actually a new problem appeared - there were cases where LocalDatabase
plugin called the MySql resolver and it actually returned the MySql implementation of the requested service type. Obviously the LocalDatabase
plugin couldn't work with that, because it expected the SQLite implementation. And vice-versa.
So, each of the Resolvers, would actually need to check who called the GetService
method - if it's some method from the same assembly that the custom resolver is in, it tries to resolve. Otherwise it's assumed that a resolver from different plugin should take care of that request and it returns null to actually let it do that.
The problem is that the GetService
method doesn't supply any information about the requester. So that's where I came up with the hacky solution, which uses StackTrace
to check whether any of the called methods belongs to the same Assembly
that the current Resolver resides in.
public class CustomMySqlDbDependencyResolver : IDbDependencyResolver
{
private readonly Assembly _executingAssembly = Assembly.GetExecutingAssembly();
private readonly MySqlDependencyResolver _mySqlResolver = new MySqlDependencyResolver();
public object GetService(Type type, object key)
{
var stackTrace = new StackTrace();
StackFrame[] stackFrames = stackTrace.GetFrames().Skip(1).ToArray();
bool shouldResolve = stackFrames.Any(f => f.GetMethod().DeclaringType.Assembly.Equals(_executingAssembly));
if (!shouldResolve)
{
return null;
}
var resolvedService = _mySqlResolver.GetService(type, key);
return resolvedService;
}
public IEnumerable<object> GetServices(Type type, object key)
{
var service = GetService(type, key);
if (service != null)
{
yield return service;
}
}
}
Upvotes: 0