Плагины MEF и EF CodeFirst - Как?

Фон:
У нас есть проект со многими модулями. Мы используем EntityFramework 4.2 с FluentAPI (CodeFirst).

Существует центральный проект с именем Diverto.ORM.EntityFramework.SQLServer, который содержит частичные классы, которые создают контекст с использованием FluentAPI (и который имеет ссылки на каждый другой проект в решении).

Недавно мы получили запрос от заказчика на реализацию многих других функций, и для решения потребуется несколько других проектов. Некоторые из этих проектов будут поступать из другой системы (Human Resources), и некоторые из них будут созданы. Ядром существующего решения является финансовая система.

Мы хотим включить и отключить эти новые проекты (и графический интерфейс, бизнес-логику и все) "на лету" с использованием MEF. Они будут взаимодействовать как плагины, а главное меню из приложения будет заполнено также с помощью MEF.
Однако мы действительно не имеем понятия о том, как включать/отключать эти модули/проекты (новые и HR) из-за данных, которые они должны использовать.

Рассмотрим это:
- DivertoContext (основной контекст) с DbSet <ClassA> и DbSet <ClassB> .
- PluginContext (из плагина) с DbSet <ClassC> .

Теперь рассмотрим, что внутри GUI я должен иметь доступ к данным ClassA, ClassB и ClassC (если плагин есть).

Решение найдено! См. Ниже

ЭТО, ВЫ ТЕПЕРЬ, ПРОЧИТАЙТЕ ЭТО ПЕРЕД ОТВЕТОМ!

Я заметил, что некоторые люди проверяют это и отмечают это как фаворит или первенство. Пожалуйста, имейте в виду, что этот ответ восходит к 2012 году, и EntityFramework сменил много.

Кроме того, пожалуйста, ПОЖАЛУЙСТА, помните, что у каждого проекта есть свои собственные потребности. В то время мне нужна эта функция. Возможно, вам не нужен этот проект, или просто некоторые части этого!

Наконец, чтобы убедиться, что все закрыто, да, можно сделать эту работу с EF 6.1 и EF Migrations, и это возможно с другими структурами ORM и миграции.

Вам могут потребоваться некоторые другие интерфейсы, как один для миграции для загрузки, и надлежащим образом обрабатывать определенную миграцию плагина (не смешивайте его с другими плагинами, поэтому старайтесь реализовать какой-то уникальный токен для каждого плагина).

Ответы

Ответ 1

Решение найдено!

Хорошо, я попытаюсь объяснить здесь, так как я не мог найти это в другом месте. Это интересно для людей, которым необходимо создать одно базовое программное обеспечение, которое будет получать несколько плагинов, и эти плагины должны взаимодействовать с существующими данными в одной базе данных.

Прежде всего, я буду работать с Entity Framework с API CodeFirst и всеми. Поэтому, если вы пойдете на это, я рекомендую читать EntityTypeConfiguration из MSDN и Code First Fluent API также из MSDN.

Теперь позвольте понять некоторые вещи:

  • У вас должен быть только один контекст, чтобы все работало правильно. Я расскажу об этом и покажу, как использовать классы из плагинов внутри контекста из приложения, но для этого вы ДОЛЖНЫ понимать шаблон общего репозитория. Я покажу здесь немного, но я рекомендую вам учиться на этом и попытаться создать лучший интерфейс для вашего приложения.
  • 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);
}

Отныне я просто назову его IRepository, чтобы все было просто.
Я рассмотрю один метод под названием Add (T item) для выборочного кодирования.

Внутри Base.ORM.EntityFramework.SQLServer создается класс BaseContext, который наследуется от DbContext и реализует IRepository. Он должен выглядеть следующим образом:

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

Здесь вы можете добавить базовую реализацию IDatabaseInitializer для управления версиями базы данных. Я сделал это с SQL файлами в стандартной папке, но это старое кодирование, поскольку EF теперь поддерживает миграции.

Если вы все еще будете обрабатывать это вручную, не забудьте установить базу данных в режим одного пользователя ПЕРЕД и вернуться к многопользовательскому режиму ПОСЛЕ. Помните: попробуйте... поймать... наконец-то поможет здесь, потому что вы можете вернуться к нескольким пользователям внутри, наконец, даже при ошибке не останется проблем.

Внутри проекта 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 находится внутри проекта плагина. У вас нет ссылки на него из основного проекта.
Вам нужно будет найти этот метод (Setup) с помощью 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);
}

Использование

Внутри вашего APP у вас будет только один контекст. Этот контекст наследуется от BaseContext, который реализует IRepository. Имея это в виду, вам нужно закодировать свой графический интерфейс и бизнес-уровень для использования IRepository (из Base.ORM) и определенного класса (внутри бизнес-библиотеки dll).

Он работает!

Хорошо, он работает здесь.

Я думаю, что я показал каждую важную часть здесь. Конечно, внутри классов больше кода, но это не так. Я пытаюсь показать только то, что вам действительно нужно для создания/реализации, чтобы сделать это.

Не забывайте:

  • Не забывайте, что вам нужно будет написать свой собственный код, чтобы засеять базу данных. Для моего случая здесь внутри одного и того же интерфейса для плагина у меня есть что-то вроде Seed (IRepository), и я просто обрабатываю все там.
  • Не забывайте, что у вас нет ссылок из основного проекта на плагины. Это означает, что вы ДОЛЖНЫ найти способ загрузить меню, графический интерфейс, бизнес и данные через интерфейсы и провайдеры. Мне удалось решить это, используя что-то вроде IPlugin (business), IFormPlugin (GUI - Winforms) и IPluginRepository (данные). Вам нужно будет найти свои собственные имена и методы, которые могут соответствовать вашим потребностям, но это должно быть хорошей отправной точкой.
  • Не забывайте, что если вы загружаете плагин, вы должны создать таблицы внутри базы данных или EF CodeFirst не будет инициализироваться. Помните, что вам могут понадобиться файлы SQL и вручную запустить их для создания таблиц по мере необходимости.
  • Не забывайте, что если вы выгружаете плагин, вы также должны удалить таблицы, иначе EF тоже не сработает.
  • Не забывайте, что ВАМ НУЖНЫ РЕЗЕРВЫ. Я еще не сделал этого, но я уже отметил, где это будет сделано (до и после команд DDL).
  • Это МОЕ решение для MY case. Это должно стать хорошим началом для новых проектов, но только это. Не думайте, что, следуя тому, что я здесь сделал, он будет работать 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);
   }
}

ДЛЯ 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);
   }
}

ПО ВАШЕМУ РЕГУЛЯРНОМУ КОДУ

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