MEF插件和EF代码优先 - 如何?

时间:2012-01-04 17:05:10

标签: entity-framework entity-framework-4 mef fluent-interface

背景
我们有一个包含许多模块的项目。我们正在使用EntityFramework 4.2和FluentAPI(CodeFirst)
有一个名为Diverto.ORM.EntityFramework.SQLServer的中央项目,它包含使用FluentAPI构建上下文的部分类(并且它引用了解决方案中的所有其他项目)。

最近我们收到了客户要求实施许多其他功能的请求,该解决方案还需要其他几个项目。其中一些项目将来自另一个系统(人力资源),一些将被创建。现有解决方案的核心是财务系统
我们希望使用MEF“动态”启用和禁用这些新项目(以及GUI,业务逻辑和所有项目)。它们将作为插件进行交互,应用程序的主菜单也将使用MEF进行填充 但是,由于他们必须共享的数据,我们并不真正了解如何启用/禁用这些模块/项目(新模块/项目)。
考虑一下:
- 具有DbSet< ClassA>的DivertoContext(主要上下文)和DbSet< ClassB>。
- 具有DbSet< ClassC>的PluginContext(来自插件)
现在,考虑到在GUI内部,我必须能够访问ClassA,ClassB和ClassC中的数据(如果插件存在的话)。

找到解决方案!见下文

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

我注意到有人在检查这个并将其标记为收藏或赞成。请记住,这个答案可以追溯到2012年,EntityFramework已经改变了很多

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

最后,只是为了确保所有内容都被掩盖,是的,可以使用EF 6.1和EF迁移来实现这一点,并且也可以使用其他ORM和迁移框架。

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

1 个答案:

答案 0 :(得分:28)

找到解决方案!

好吧,我会尝试在这里解释,因为我无法在其他地方找到这个。对于那些必须创建单个基础软件以接收多个插件并且这些插件必须与单个数据库中的现有数据进行交互的人来说,这很有趣。
首先,我将使用CodeFirst API与所有实体框架一起工作。因此,如果您要进行此操作,我建议您从MSDN和EntityTypeConfiguration读取MSDN中的Code First Fluent API

现在,让我们解释一些事情:

  • 您必须只有一个上下文才能使所有内容正常工作。我将进入该方法,并展示一种方法,从应用程序的上下文中获取插件类,但为了实现这一点,您必须了解通用存储库模式。我在这里只展示了一点,但我建议你努力学习,并尝试为你的应用创建最好的界面。
  • MEF将成为我们的朋友。我认为您已经知道如何使用MEF创建一个简单的插件以及如何访问该插件中的方法。另外,我会尽量避免深入研究MEF,因为这不是这种情况,因为你可以使用其他解决方案。事实上,我之所以使用MEF只是因为我已经熟悉它了。
  • 如果您要进入&#34;哦,我需要处理多个上下文创建,这些上下文创建将指向单个数据库,并且所有&#34;你做错了。这完全是关于简单的配置和一些流畅的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);
}

从现在开始,我只是将其称为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; }
   }
}

您可以在此处添加自定义IDatabaseInitializer基础实现,以进行数据库版本控制。我已经用标准文件夹中的SQL文件完成了它,但这是旧的编码,因为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);
   }
}

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

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);
}

当然,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);
}

用法

在您的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%工作。
  7. 感谢

    感谢来自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);
       }
    }
    

    对于插件

    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();
    }