Background:
We have a project with many modules. We're using EntityFramework 4.2 with FluentAPI (CodeFirst).
There is a central project named Diverto.ORM.EntityFramework.SQLServer which contains partial classes that build the context using the FluentAPI (and which has references to every other project on the solution).
Recently we received a request from the customer to implement many other features and the solution will need several other projects. Some of these projects will come from another system (Human Resources) and some will be created. The core of the existing solution is a Finance system.
We want to enable and disable these new projects (and GUI, business logic and all) "on the fly" using MEF. They will interact as plugins and the main menu from the application will get populated also using MEF.
However we don't really have a clue on how to enable/disable these modules/projects (new ones and HR ones) because of data that they must share.
Consider this:
- DivertoContext (main context) with DbSet<ClassA> and DbSet<ClassB>.
- PluginContext (from a plugin) with DbSet<ClassC>.
Now, consider that inside the GUI I must have access to data from ClassA, ClassB and ClassC (if the plugin is there).
Solution found! See bellow
I've noticed some people checking this out and marking this as favorite or upvoting. Please, bear in mind that this answer dates back to 2012 and EntityFramework has changed a lot since that.
Also, please, please, PLEASE, remember that each project has it's very own needs. I needed this feature, this way, at that time. Your project might not need this at all, or just some parts of this!
Finally, just to make sure everything is covered up, yes, it's possible to make this work with EF 6.1 and EF Migrations and it might be possible with other ORM and migration framework as well.
You might need some other interfaces, as one for the migration to load, and properly handle specific plugin migration (don't mix it with other plugins so try to implement some sort of unique token for each plugin).
Well, I'll try to explain here since I couldn't find this elsewhere. This is interesting for people that have to create a single base software that will receive multiple plugins and these plugins must interact with existing data within a single database.
First of all, I'll be working with Entity Framework with CodeFirst API and all. So if you're going into this I recomend reading of EntityTypeConfiguration from MSDN and of Code First Fluent API also from MSDN.
Now, let's understang some things:
Solution: MEFTest
Projects:
Inside the Base.ORM project create your Generic Repository interface with methods as you see fit, but the interface is not typed. It will be similar to this:
public interface IRepository
{
bool Add<T>(T item);
}
From now on I'll just call it IRepository to keep things simple.
I'll consider one method called Add(T item) for sample coding.
Inside the Base.ORM.EntityFramework.SQLServer create a BaseContext class that inherits from DbContext and that implements IRepository. It should look like this:
public class BaseContext : IRepository
{
public bool Add<T>(T item)
{
try { Set<T>().Add(item); return true; }
catch { return false; }
}
}
You might add a custom IDatabaseInitializer base-implementation here for database versioning. I've done it with SQL files winthin a standard folder but this is old coding as EF now supports migrations.
If you'll still up to handling this manually, remember to set the database to single user mode BEFORE and reverting to multi user mode AFTER. Remember: try...catch...finally will help here because you can revert to multi user inside the finally so even on error there will be no problems left behind.
Inside the SampleApplication project, add:
ClassA (int Id, string Name) and ClassB (int Id, DateTime TestDate).
Inside the SampleApplication.ORM.EntityFramework.SQLServer create your standard context.
I'll use three classes here with very interesting names: ClassA, ClassB and ClassC.
Both ClassA and ClassB are referenced from this project and it will be like this:
public class Context : BaseContext
{
public DbSet<ClassA> ClassA { get; set; }
public DbSet<ClassB> ClassB { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
/* I'll talk about this later. Just override the OnModelCreating and leave it */
base.OnModelCreating(modelBuilder);
}
}
Now the funny part: The plugin will have a method like this:
public void Setup(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ClassC>().ToTable("ClassC");
modelBuilder.Entity<ClassC>().HasKey(_classC => _classC.Id);
modelBuilder.Entity<ClassC>().Property(_classC => _classC.Date2).HasColumnType("datetime2").HasPrecision(7);
}
Of course ClassC is inside the plugin project. You don't have any reference to it from the main project.
You will have to find this method (Setup) using MEF and of course, interfaces. I'll just show WHERE to place this and how to make it work =)
Back to the Context class, the OnModelCreating method will be like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ClassA>().ToTable("ClassA");
modelBuilder.Entity<ClassA>().HasKey(_classA => _classA.Id);
modelBuilder.Entity<ClassB>().ToTable("ClassB");
modelBuilder.Entity<ClassB>().HasKey(_classB => _classB.Id);
modelBuilder.Entity<ClassB>().Property(_classB => _classB.TestDate).HasColumnType("datetime2").HasPrecision(7);
/* Use MEF to load all plugins. I'll use the mock interface IPlugin */
foreach (IPlugin plugin in MefLoadedPlugins)
plugin.Setup(modelBuilder);
}
Inside your APP you will have just one Context. This context inherits from BaseContext which implements IRepository. With that in mind you need to code your GUI and business layer to use IRepository (from Base.ORM) and the specific class (inside the business-specific dll).
Well, it's working here.
I think I've shown every relevant part here.
Of course there is more code inside the classes but it's not the case. I'm trying to show only what you really need to create/implement to get it done.
Thanks to people from SO and from MSDN that helped my a lot with comments and other posts that I've found.
Thanks to Caio Garcia (BR) that helped me with some instructions about other plugin-based systems.
Here follows some sample codes:
public class ClassA
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ClassB
{
public int Id { get; set; }
public string OtherName { get; set; }
}
public interface IRepository
{
bool Add<T>(T item);
bool Save();
}
public class BaseContext : DbContext, IRepository
{
public bool Add<T>(T item)
{
try { Set<T>().Add(item); return true; } catch { return false; }
}
public bool Save()
{
try { SaveChanges(); return true; } catch { return false; }
}
}
public class Context : BaseContext
{
// Fill this list using MEF - check for the IPluginContext interface on assemblies
public List<IPluginContext> MefLoadedPlugins;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ClassA>().ToTable("TableB", "Schema_1");
modelBuilder.Entity<ClassA>().HasKey(_a => _a.Id);
modelBuilder.Entity<ClassB>().ToTable("TableB", "Schema_1");
modelBuilder.Entity<ClassB>().HasKey(_b => _b.Id);
if (MefLoadedPlugins != null)
foreach (var pluginContext in MefLoadedPlugins)
pluginContext.Setup(modelBuilder);
}
}
public class ClassC
{
public int Id { get; set; }
public string Description { get; set; }
}
public interface IPluginContext
{
void Setup(DbModelBuilder modelBuilder);
}
public class Just_A_Sample_Plugin_Context : IPluginContext
{
public void Setup(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ClassC>().ToTable("TableC", "Schema_2");
modelBuilder.Entity<ClassC>().HasKey(_c => _c.Id);
}
}
public void DoSomething(IRepository repo)
{
var classA = new ClassA() { Name = "First Name" };
repo.Add(classA);
repo.Save();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With