Ответ 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();
}