MEF 插件和 EF CodeFirst - 如何?

发布于 2024-12-24 03:04:24 字数 1065 浏览 2 评论 0原文

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

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

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

我们希望使用 MEF“即时”启用和禁用这些新项目(以及 GUI、业务逻辑等)。它们将作为插件进行交互,应用程序的主菜单也将使用 MEF 进行填充。
然而,由于它们必须共享数据,我们并不真正了解如何启用/禁用这些模块/项目(新模块/项目和 HR 模块/项目)。

考虑一下:
- DivertoContext(主上下文)与 DbSet和 DbSet
- PluginContext(来自插件)与 DbSet

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

找到解决方案!请参阅下文

嘿,您在,请在回答之前阅读此内容!

我注意到有些人查看了此内容并将其标记为最喜欢或赞成。请记住,这个答案可以追溯到 2012 年,自那以后 EntityFramework 发生了很大变化。

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

最后,为了确保所有内容都被覆盖,是的,可以在 EF 6.1 和 EF 迁移中使用此功能,并且也可以在其他 ORM 和迁移框架中使用。

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

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

HEY, YOU THERE, READ THIS BEFORE THE ANSWER!

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).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

请远离我 2024-12-31 03:04:24

找到解决方案了!

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

首先,我将使用带有 CodeFirst API 等的实体框架。因此,如果您要了解此内容,我建议您阅读 EntityTypeConfiguration 来自 MSDN 和 Code First Fluent API 也来自 MSDN。< br>

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

  • 您必须只有一个上下文才能使一切正常工作。我将深入探讨这一点,并展示一种从应用程序的上下文中获取来自插件的类的方法,但要使其发挥作用,您必须了解通用存储库模式。我只会在这里展示一点,但我建议您努力学习并尝试为您的应用程序创建最佳界面。
  • MEF 将成为我们这里的朋友。我认为您已经知道如何使用 MEF 创建一个简单的插件以及如何访问该插件内的方法。另外,我会尽量避免深入研究 MEF,因为这里的情况并非如此,而且您可以使用其他解决方案。事实上,我使用 MEF 只是因为我已经对它有点熟悉了。
  • 如果您要“哦,我需要处理多个上下文创建,这些上下文将指向单个数据库和所有内容”,那么您就做错了。这都是关于简单的配置和一些流畅的 API。相信我:我在互联网上搜索了一周,最后在与朋友交谈后,我们找到了这个解决方案,而且非常简单 =)。

首先要事

解决方案: MEFTest
项目

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

现在,Base.ORM 项目内部的编码

使用您认为合适的方法创建通用存储库接口,但该接口没有类型化。它将与此类似:

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 现在支持迁移。

如果您仍要手动处理此问题,请记住之前将数据库设置为单用户模式,之后恢复为多用户模式。请记住:try...catch...finally 在这里会有所帮助,因为您可以在finally 中恢复为多用户,因此即使出现错误也不会留下任何问题。

在 SampleApplication 项目中,添加:
ClassA(int Id,字符串名称)和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,当然还有接口来找到此方法(设置)。我将只展示在哪里放置它以及如何使其工作 =)

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

用法

在您的应用程序内部,您将只有一个 Context。该上下文继承自实现 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. 这是我针对我的案例的解决方案。这应该是新项目的良好开端,但仅此而已。不要认为按照我在这里所做的操作就可以 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);
   }
}

插件

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

Solution found!

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:

  • You must have only one context for everything to work properly. I'll go into that and show a way to have classes from plugins inside the context from the application, but for that to work you MUST understand the Generic Repository Pattern. I'll show only a bit here but I recomend you to study hard on that and try to create the best interface for your app.
  • MEF will be our friend here. I'll consider that you already know how to create a simple plugin with MEF and how to access a method inside that plugin. Also, I'll try to avoid going deep into MEF because it's not the case here and because you can use other solution. In fact, I'm using MEF just because I'm already familiar with it somehow.
  • If you're going into "Oh, I'll need to handle multiple context creation that will point into a single database and all" you're doing it wrong. This is all about simple configs and just some fluent API. Believe in me: I've searched through the Internet for a week and finally after talking to a friend we found this solution and it's really easy =).

First things first

Solution: MEFTest
Projects:

  • Base.ORM (that will hold interfaces for ORM)
  • Base.ORM.EntityFramework.SQLServer (will hold base classes for EF)
  • SampleApplication.ORM.EntityFramework.SQLServer (will hold the context for the application)
  • SampleApplication (executable)
  • MEFPlugin (will hold our plugin)

Now, the coding

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

Usage

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).

It works!

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.

Don't forget:

  1. Don't forget that you will have to write your own code to seed the database. For my case here inside the same interface for the plugin I have something like Seed(IRepository) and I just handle everything there.
  2. Don't forget that you don't have references from the main project to the plugins. This means that you MUST find a way to load menu, GUI, business and data all through interfaces and providers. I've managed to solve that using something like IPlugin (business), IFormPlugin (GUI - Winforms) and IPluginRepository (data). You will have to find your own names and methods that might fit your needs but this should be a good start point.
  3. Don't forget that if you load a plugin you must create the tables inside the database or EF CodeFirst will fail to initialize. Remember that you might need SQL files and manually run them to create tables as needed.
  4. Don't forget that if you unload a plugin you must remove tables too or EF will fail too.
  5. Don't forget that you REALLY NEED BACKUPS. I didn't do this yet but I've already marked where this will be done (before and after DDL commands).
  6. This is MY solution for MY case. This should be a good start for new projects but just that. Don't think that by following what I've done here it will work 100% for every case.

Thanks

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.

Sample codes (full)

Here follows some sample codes:

FOR THE BASIC ITEMS (solution predefined items)

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

FOR THE PLUGIN

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

ON YOUR REGULAR CODE

public void DoSomething(IRepository repo)
{
   var classA = new ClassA() { Name = "First Name" };
   repo.Add(classA);
   repo.Save();
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文