Wtyczki MEF i EF CodeFirst-jak?

Background:
Mamy projekt z wieloma modułami. Używamy EntityFramework 4.2 z FluentAPI (CodeFirst).

Istnieje centralny projekt o nazwie Diverto.ORM./ Align = "left" / SQLServer, który zawiera częściowe klasy, które budują kontekst za pomocą FluentAPI (i który ma odniesienia do każdego innego projektu w rozwiązaniu).

Ostatnio otrzymaliśmy prośbę od Klienta o wdrożenie wielu innych funkcji, a rozwiązanie będzie wymagało kilku innych projektów. Niektóre z tych projektów będą pochodzić z innego systemu (zasobów ludzkich), a niektóre zostaną utworzone. Podstawą istniejącego rozwiązania jest system finansowy.

Chcemy włączyć i wyłączyć te nowe projekty (i GUI, logikę biznesową i wszystkie) "w locie" za pomocą MEF. Będą współdziałać jako wtyczki, a główne menu aplikacji zostanie wypełnione również za pomocą MEF.
Jednak tak naprawdę nie mamy pojęcia, jak włączyć / wyłączyć te moduły / projekty (nowe i HR), ponieważ dane, które muszą udostępnić.

Rozważ to:
- DivertoContext (główny kontekst) z DbSet i DbSet.
- PluginContext (z wtyczki) z DbSet.

Teraz rozważ, że wewnątrz GUI muszę mieć dostęp do danych z ClassA, ClassB i ClassC(jeśli wtyczka tam jest).

Rozwiązanie znalezione! Zobacz poniżej

HEJ, TY TAM, PRZECZYTAJ TO PRZED ODPOWIEDZIĄ!

Zauważyłem, że niektórzy ludzie sprawdzają to i zaznaczają jako ulubione lub upvoting. Proszę pamiętać, że ta odpowiedź pochodzi z 2012 roku i EntityFramework zmienił dużo od tego czasu.

Również proszę, proszę, Proszę, pamiętaj, że każdy projekt ma swoje własne potrzeby. Potrzebowałem tej funkcji, w ten sposób, w tym czasie. Twój projekt może w ogóle tego nie potrzebować, lub tylko niektóre jego części!

Wreszcie, aby upewnić się, że wszystko jest zakryte, Tak, możliwe jest, aby to działało z EF 6.1 i EF Migracje i może być to możliwe z innymi ORM i Framework migracji, jak również.

Możesz potrzebować innych interfejsów, jako jeden do załadowania migracji i prawidłowo obsługiwać migrację konkretnych wtyczek(nie mieszaj jej z innymi wtyczkami, więc spróbuj zaimplementować jakiś unikalny token dla każdej wtyczki).

Author: Kishore Kumar, 2012-01-04

1 answers

Rozwiązanie znalezione!

Cóż, postaram się wyjaśnić tutaj, ponieważ nie mogłem znaleźć tego gdzie indziej. Jest to interesujące dla osób, które muszą stworzyć jedno oprogramowanie bazowe, które otrzyma wiele wtyczek, a te wtyczki muszą współdziałać z istniejącymi danymi w jednej bazie danych.

Po pierwsze, będę pracował z Entity Framework z CodeFirst API i w ogóle. Więc jeśli masz zamiar to polecam odczyt EntityTypeConfiguration z MSDN i Kod Pierwszy Fluent API również z MSDN.

A teraz przejdźmy do rzeczy:
    Musisz mieć tylko jeden kontekst, aby wszystko działało prawidłowo. Przejdę do tego i pokażę sposób, aby klasy z wtyczek wewnątrz kontekstu z aplikacji, ale aby to działało, musisz zrozumieć ogólny wzór repozytorium. Pokażę tylko trochę tutaj, ale polecam, aby uczyć się ciężko na tym i spróbować stworzyć najlepszy interfejs dla Twojej aplikacji.
  • MEF będzie naszym przyjaciel. Rozważę, że już wiesz, jak stworzyć prostą wtyczkę z MEF i jak uzyskać dostęp do metody wewnątrz tej wtyczki. Postaram się również unikać wchodzenia głęboko w MEF, ponieważ tutaj tak nie jest i dlatego, że można użyć innego rozwiązania. W zasadzie używam MEF tylko dlatego, że jestem już z nim jakoś zaznajomiony.
  • jeśli przechodzisz do "Och, będę musiał obsługiwać tworzenie wielu kontekstów, które będą wskazywać na pojedynczą bazę danych i wszystkie" robisz to źle. To wszystko o proste konfiguracje i trochę płynnego API. Uwierz we mnie: szukałem przez Internet przez tydzień i w końcu po rozmowie z przyjacielem znaleźliśmy to rozwiązanie i jest naprawdę łatwe =).


Najpierw rzeczy pierwsze

Rozwiązanie: MEFTest
Projekty:

  • Baza.ORM (który przechowuje interfejsy dla ORM)
  • Baza.ORM./ Align = "left" / SQLServer (będzie przechowywał klasy bazowe dla EF)
  • SampleApplication.ORM./ Align = "left" / SQLServer (przechowuje kontekst dla aplikacji)
  • SampleApplication (executable)
  • MEFPlugin (będzie trzymać nasz plugin)

Teraz kodowanie

Wewnątrz bazy.Projekt ORM Utwórz ogólny interfejs repozytorium z metodami według własnego uznania, ale interfejs nie jest wpisany. Będzie to podobne do tego:

public interface IRepository
{
   bool Add<T>(T item);
}

Od teraz będę nazywał to IRepository, aby zachować rzeczy proste.
Rozważę jedną metodę o nazwie Add (t item) do kodowania próbek.

Wewnątrz bazy.ORM./ Align = "left" / SQLServer tworzy klasę BaseContext, która dziedziczy z DbContext i która implementuje IRepository. Powinno to wyglądać tak:

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

Możesz dodać tutaj niestandardową implementację bazy IDatabaseInitializer do wersjonowania baz danych. Zrobiłem to z plikami SQL winthin standardowym folderze, ale jest to stare kodowanie, ponieważ EF obsługuje teraz migracje.

If you ' ll jeszcze do obsługi tego ręcznie, pamiętaj, aby ustawić bazę danych w trybie pojedynczego użytkownika przed i powrócić do trybu wielu użytkowników po. Pamiętaj: spróbuj...łap...wreszcie pomoże tutaj, ponieważ możesz powrócić do wielu użytkowników w końcu, więc nawet w przypadku błędu nie będzie żadnych problemów.

Wewnątrz projektu SampleApplication, dodaj:
ClassA (int Id, string Name) oraz ClassB (int Id, DateTime TestDate).

Wewnątrz SampleApplication.ORM./ Align = "left" / SQLServer Stwórz swój standardowy kontekst.
Użyję tu trzech klas o bardzo ciekawych nazwach: ClassA, Clasb i ClassC.
Zarówno ClassA, jak i ClassB są odwołane z tego projektu i będzie to wyglądało tak: {]}

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

Teraz zabawna część: plugin będzie miał metodę taką jak ta:

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

Oczywiście ClassC jest wewnątrz projektu wtyczki. Nie masz do niego żadnych odniesień z głównego projektu.
Tą metodę (konfigurację) musisz znaleźć przy użyciu MEF i oczywiście interfejsów. Pokażę tylko gdzie to umieścić i jak to zrobić=)

Wracając do klasy Context, metoda OnModelCreating będzie wyglądać następująco:

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

Użycie

Wewnątrz aplikacji będziesz miał tylko jeden kontekst. Ten kontekst dziedziczy z BaseContext, który implementuje IRepository. Mając to na uwadze, musisz zakodować GUI i warstwę biznesową, aby korzystać z IRepository(z bazy.ORM) i specyficzną klasę (wewnątrz biblioteki dll specyficznej dla firmy).

It działa!

Cóż, to działa tutaj.

Myślę, że pokazałem tu każdą istotną część.
Oczywiście w klasach jest więcej kodu, ale tak nie jest. Staram się pokazać tylko to, co naprawdę trzeba stworzyć/wdrożyć, aby to zrobić.

Nie zapomnij:

  1. nie zapominaj, że będziesz musiał napisać własny kod, aby zalać bazę danych. W moim przypadku tutaj w tym samym interfejsie dla wtyczki mam coś takiego jak Seed (IRepository) i po prostu zajmij się wszystkim.
  2. nie zapominaj, że nie masz odniesień od głównego projektu do wtyczek. Oznacza to, że musisz znaleźć sposób na załadowanie menu, GUI, biznesu i danych za pośrednictwem interfejsów i dostawców. Udało mi się to rozwiązać używając czegoś takiego jak IPlugin (business), IFormPlugin (GUI - Winforms) i ipluginrepository (data). Będziesz musiał znaleźć własne nazwy i metody, które mogą pasować do Twoich potrzeb, ale to powinien być dobry punkt wyjścia.
  3. nie zapomnij że jeśli załadujesz wtyczkę musisz utworzyć tabele wewnątrz bazy danych lub EF CodeFirst nie uda się zainicjować. Pamiętaj, że możesz potrzebować plików SQL i ręcznie uruchamiać je, aby tworzyć tabele w razie potrzeby.
  4. nie zapominaj, że jeśli rozładujesz wtyczkę, musisz usunąć tabele, bo EF też się nie powiedzie.
  5. Nie zapominaj, że naprawdę potrzebujesz kopii zapasowych. Jeszcze tego nie zrobiłem, ale już zaznaczyłem, gdzie to będzie zrobione (przed i po poleceniach DDL).
  6. to jest moje rozwiązanie dla Moja sprawa. To powinno być dobrym początkiem dla nowych projektów, ale tylko to. Nie myśl, że podążając za tym, co tu zrobiłem, to zadziała w 100% w każdej sprawie.

Dzięki

Dzięki ludziom z SO I z MSDN, które pomogły mi wiele z komentarzy i innych postów, które znalazłem.
Dzięki Caio Garcia (BR), który pomógł mi z instrukcjami dotyczącymi innych systemów opartych na wtyczkach.

Przykładowe kody (pełne)

Oto przykład kody:

Dla podstawowych pozycji (rozwiązania predefiniowane pozycje)

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

DO WTYCZKI

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

NA TWOIM ZWYKŁYM KODZIE

public void DoSomething(IRepository repo)
{
   var classA = new ClassA() { Name = "First Name" };
   repo.Add(classA);
   repo.Save();
}
 28
Author: Anderson Matos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-23 16:38:12