MEF插件和EF CodeFirst - 如何?

And*_*tos 17 entity-framework fluent-interface mef entity-framework-4

背景:
我们有一个包含许多模块的项目.我们将EntityFramework 4.2与FluentAPI(CodeFirst)一起使用.

有一个名为Diverto.ORM.EntityFramework.SQLServer的中心项目,它包含使用FluentAPI构建上下文的部分类(并且它引用了解决方案中的所有其他项目).

最近我们收到了客户要求实施许多其他功能的请求,该解决方案还需要其他几个项目.其中一些项目将来自另一个系统(人力资源),一些将被创建.现有解决方案的核心是财务系统.

我们希望使用MEF"动态"启用和禁用这些新项目(以及GUI,业务逻辑和所有项目).它们将作为插件进行交互,应用程序的主菜单也将使用MEF进行填充.
但是,由于它们必须共享的数据,我们并不真正了解如何启用/禁用这些模块/项目(新模块/项目).

考虑一下:
- 使用DbSet <ClassA>和DbSet <ClassB>的DivertoContext(主上下文).
- 使用DbSet <ClassC>的PluginContext(来自插件).

现在,考虑到在GUI内部,我必须能够访问ClassA,ClassB和ClassC中的数据(如果插件存在的话).

找到解决方案 见下文

嘿,你在那里,请在回答之前阅读!

我注意到有人在检查这个并将其标记为最喜欢或最喜欢的.请记住,这个答案可以追溯到2012年,EntityFramework 自那以后发生了很大的变化.

另外,拜托,,记住,每个项目都有自己的需求.那时候,我需要这个功能.您的项目可能根本不需要这个,或者只是其中的一部分!

最后,只是为了确保一切都被掩盖,是的,可以使用EF 6.1和EF迁移来实现这一点,也可以使用其他ORM和迁移框架.

您可能需要一些其他接口,作为迁移加载的接口,并正确处理特定的插件迁移(不要将其与其他插件混合使用,因此请尝试为每个插件实现某种独特的令牌).

And*_*tos 28

找到解决方案

好吧,我会试着在这里解释,因为我在其他地方找不到.对于必须创建将接收多个插件的单个基础软件且这些插件必须与单个数据库中的现有数据交互的人来说,这很有趣.

首先,我将使用CodeFirst API和所有的Entity Framework.因此,如果您要进行此操作,我建议您从MSDN和MSDN的Code First Fluent API中读取EntityTypeConfiguration. 现在,让我们解释一些事情:


  • 您必须只有一个上下文才能正常工作.我将进入该方法,并展示一种方法,从应用程序的上下文中获取插件的类,但为了工作,您必须了解通用存储库模式.我只会在这里展示一下,但我建议你努力学习,并尝试为你的应用创建最好的界面.
  • MEF将成为我们的朋友.我会考虑你已经知道如何使用MEF创建一个简单的插件以及如何访问该插件中的方法.另外,我会尽量避免深入研究MEF,因为这不是这种情况,因为你可以使用其他解决方案.事实上,我正在使用MEF只是因为我已经以某种方式熟悉它了.
  • 如果你要进入"哦,我需要处理多个上下文创建,它们将指向一个数据库而且所有"你做错了.这完全是关于简单的配置和一些流畅的API.相信我:我已经在互联网上搜索了一个星期,最后在和朋友交谈之后我们发现了这个解决方案,这真的很容易=).


首先要做的事情

解决方案: MEFTest
项目:

  • Base.ORM(将保存ORM的接口)
  • Base.ORM.EntityFramework.SQLServer(将保存EF的基类)
  • SampleApplication.ORM.EntityFramework.SQLServer(将保存应用程序的上下文)
  • SampleApplication(可执行文件)
  • MEFPlugin(将保留我们的插件)

现在,编码

在Base.ORM项目中,根据您的需要使用方法创建Generic Repository接口,但不输入接口.它将类似于:

public interface IRepository
{
   bool Add<T>(T item);
}
Run Code Online (Sandbox Code Playgroud)

从现在开始,我将其称为IRepository以保持简单.
我将考虑一种名为Add(T item)的方法用于样本编码.

在Base.ORM.EntityFramework.SQLServer内部创建一个继承自DbContext并实现IRepository的BaseContext类.它应该如下所示:

public class BaseContext : IRepository
{
   public bool Add<T>(T item)
   {
      try { Set<T>().Add(item); return true; }
      catch { return false; }
   }
}
Run Code Online (Sandbox Code Playgroud)

您可以在此处添加自定义IDatabaseInitializer基本实现以进行数据库版本控制.我已经用SQL文件winthin标准文件夹完成了它,但这是旧的编码,因为EF现在支持迁移.

如果您仍然需要手动处理,请记住将数据库设置为单用户模式BEFORE并在之后恢复为多用户模式.记住:尝试...捕捉......最终会在这里帮助,因为你可以在最终内部恢复到多用户,所以即使在出错时也不会留下任何问题.

在SampleApplication项目中,添加:
ClassA(int Id,string Name)和ClassB(int Id,DateTime TestDate).

在SampleApplication.ORM.EntityFramework.SQLServer中创建标准上下文.
我将在这里使用三个非常有趣的名称:ClassA,ClassB和ClassC.
ClassA和ClassB都是从这个项目引用的,它将是这样的:

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);
   }
}
Run Code Online (Sandbox Code Playgroud)

现在有趣的部分:该插件将有这样的方法:

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);
}
Run Code Online (Sandbox Code Playgroud)

当然,ClassC在插件​​项目中.您没有从主项目中引用它.
您必须使用MEF以及接口找到此方法(安装程序).我只是展示WHERE放置它以及如何使它工作=)

回到Context类,OnModelCreating方法将如下所示:

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);
}
Run Code Online (Sandbox Code Playgroud)

用法

在您的APP中,您将只有一个上下文.此上下文继承自实现IRepository的BaseContext.考虑到这一点,您需要编写GUI和业务层代码以使用IRepository(来自Base.ORM)和特定类(在特定于业务的dll中).

有用!

好吧,它在这里工作.

我想我已经在这里展示了所有相关部分.
当然,类中有更多的代码,但事实并非如此.我试图只显示你真正需要创建/实现它来完成它.

别忘了:

  1. 不要忘记,您必须编写自己的代码来为数据库设定种子.对于我的情况,在插件的相同界面中,我有像Seed(IRepository)的东西,我只是处理那里的一切.
  2. 不要忘记您没有从主项目到插件的引用.这意味着您必须找到一种通过接口和提供程序加载菜单,GUI,业务和数据的方法.我已经设法使用IPlugin(业务),IFormPlugin(GUI - Winforms)和IPluginRepository(数据)之类的东西来解决这个问题.您必须找到适合您需求的名称和方法,但这应该是一个很好的起点.
  3. 不要忘记,如果加载插件,则必须在数据库中创建表,否则EF CodeFirst将无法初始化.请记住,您可能需要SQL文件并手动运行它们以根据需要创建表.
  4. 不要忘记,如果您卸载插件,您必须删除表,否则EF也会失败.
  5. 不要忘记你真的需要备份.我还没有这样做,但我已经标记了将要做的事情(在DDL命令之前和之后).
  6. 这是MY案例的解决方案.对于新项目而言,这应该是一个良好的开端,但就是这样.不要认为通过遵循我在这里所做的事情,它将在每个案例中100%工作.

谢谢

感谢来自SO和MSDN的人们,我已经找到了很多评论和其他帖子.
感谢Caio Garcia(BR)帮助我提供了有关其他基于插件的系统的一些说明.

示例代码(完整)

以下是一些示例代码:

对于基本项目(解决方案预定义项目)

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);
   }
}
Run Code Online (Sandbox Code Playgroud)

对于插件

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);
   }
}
Run Code Online (Sandbox Code Playgroud)

关于你的常规代码

public void DoSomething(IRepository repo)
{
   var classA = new ClassA() { Name = "First Name" };
   repo.Add(classA);
   repo.Save();
}
Run Code Online (Sandbox Code Playgroud)