tutorial net name mvc framework foreign first español data column code attribute asp entity-framework entity-framework-4 mef fluent-interface

entity-framework - net - entity framework foreign key



Complementos MEF y EF CodeFirst-¿Cómo? (1)

Fondo:
Tenemos un proyecto con muchos modulos. Estamos utilizando EntityFramework 4.2 con FluentAPI (CodeFirst).

Hay un proyecto central llamado Diverto.ORM.EntityFramework.SQLServer que contiene clases parciales que construyen el contexto utilizando FluentAPI (y que tiene referencias a todos los demás proyectos de la solución).

Recientemente recibimos una solicitud del cliente para implementar muchas otras características y la solución necesitará varios otros proyectos. Algunos de estos proyectos provendrán de otro sistema (Recursos Humanos) y algunos se crearán. El núcleo de la solución existente es un sistema de Finanzas.

Queremos habilitar y deshabilitar estos nuevos proyectos (y GUI, lógica de negocios y todo) "sobre la marcha" utilizando MEF. Ellos interactuarán como complementos y el menú principal de la aplicación se llenará también usando MEF.
Sin embargo, realmente no tenemos una pista sobre cómo habilitar / deshabilitar estos módulos / proyectos (nuevos y recursos humanos) debido a los datos que deben compartir.

Considera esto:
- DivertoContext (contexto principal) con DbSet <ClassA> y DbSet <ClassB>.
- PluginContext (desde un complemento) con DbSet <ClassC>.

Ahora, considere que dentro de la GUI debo tener acceso a los datos de ClassA, ClassB y ClassC (si el complemento está ahí).

¡Solución encontrada! Ver abajo

¡Oye, ahí estás, lee esto antes de la respuesta!

He notado que algunas personas lo han comprobado y lo han marcado como favorito o upvoting. Por favor, tenga en cuenta que esta respuesta se remonta a 2012 y EntityFramework ha cambiado mucho desde entonces.

Además, por favor , POR FAVOR , recuerde que cada proyecto tiene sus propias necesidades. Necesitaba esta característica, de esta manera, en ese momento. Es posible que su proyecto no necesite esto en absoluto, o solo algunas partes de esto.

Finalmente, solo para asegurarse de que todo esté cubierto, sí, es posible hacer que esto funcione con EF 6.1 y EF Migrations y también podría ser posible con otro ORM y el marco de migración.

Es posible que necesite algunas otras interfaces, como una para cargar la migración, y manejar adecuadamente la migración de complementos específicos (no la mezcle con otros complementos, así que intente implementar algún tipo de token único para cada complemento).


¡Solución encontrada!

Bueno, intentaré explicar aquí porque no pude encontrar esto en otro lugar. Esto es interesante para las personas que tienen que crear un software de base única que recibirá múltiples complementos y estos complementos deben interactuar con los datos existentes dentro de una sola base de datos.

En primer lugar, trabajaré con Entity Framework con CodeFirst API y todo. Así que si vas a entrar en esto, te recomiendo leer EntityTypeConfiguration de MSDN y de la API de Code First EntityTypeConfiguration también de MSDN.

Ahora, entendamos algunas cosas:

  • Debe tener un solo contexto para que todo funcione correctamente. Entraré en eso y mostraré una forma de tener clases de complementos dentro del contexto de la aplicación, pero para que eso funcione, DEBES entender el patrón genérico de repositorio. Mostraré solo un poco aquí, pero te recomiendo que estudies mucho sobre eso e intentes crear la mejor interfaz para tu aplicación.
  • MEF será nuestro amigo aquí. Consideraré que ya sabe cómo crear un complemento simple con MEF y cómo acceder a un método dentro de ese complemento. Además, trataré de evitar profundizar en MEF porque no es el caso aquí y porque puede usar otra solución. De hecho, estoy usando MEF solo porque ya estoy familiarizado con él de alguna manera.
  • Si va a "Oh, tendré que manejar la creación de múltiples contextos que apuntará a una sola base de datos y todo" lo está haciendo mal. Se trata de configuraciones simples y solo de una API fluida. Cree en mí: he buscado en Internet durante una semana y, finalmente, después de hablar con un amigo, encontramos esta solución y es muy fácil =).


Lo primero es lo primero

Solución: MEFTest
Proyectos:

  • Base.ORM (que contendrá interfaces para ORM)
  • Base.ORM.EntityFramework.SQLServer (mantendrá las clases base para EF)
  • SampleApplication.ORM.EntityFramework.SQLServer (mantendrá el contexto para la aplicación)
  • SampleApplication (ejecutable)
  • MEFPlugin (mantendrá nuestro plugin)

Ahora, la codificación

Dentro del proyecto Base.ORM, cree la interfaz del repositorio genérico con los métodos que considere oportunos, pero la interfaz no está escrita. Será similar a esto:

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

De ahora en adelante, lo llamaré IRepository para simplificar las cosas.
Consideraré un método llamado Agregar (elemento T) para la codificación de muestra.

Dentro de Base.ORM.EntityFramework.SQLServer crea una clase BaseContext que hereda de DbContext y que implementa IRepository. Debe tener un aspecto como este:

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

Puede agregar una implementación de base de IDatabaseInitializer personalizada aquí para la versión de la base de datos. Lo he hecho con archivos SQL en una carpeta estándar, pero esta es una codificación antigua, ya que EF ahora admite migraciones.

Si aún puede manejar esto manualmente, recuerde configurar la base de datos en modo de usuario único ANTES y volver al modo de usuario múltiple DESPUÉS. Recuerda: intenta ... atrapar ... finalmente te ayudará aquí porque puedes revertir a varios usuarios dentro de finalmente, así que incluso en caso de error, no habrá problemas.

Dentro del proyecto SampleApplication, agregue:
ClassA (int Id, string Name) y ClassB (int Id, DateTime TestDate).

Dentro de SampleApplication.ORM.EntityFramework.SQLServer cree su contexto estándar.
Usaré tres clases aquí con nombres muy interesantes: ClassA, ClassB y ClassC.
Tanto ClassA como ClassB están referenciadas en este proyecto y será así:

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

Ahora la parte divertida: el plugin tendrá un método como este:

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

Por supuesto que ClassC está dentro del proyecto plugin. No tienes ninguna referencia al mismo desde el proyecto principal.
Tendrá que encontrar este método (Configuración) utilizando MEF y, por supuesto, las interfaces. Solo mostraré DÓNDE colocar esto y cómo hacerlo funcionar =)

De vuelta a la clase de contexto, el método OnModelCreating será así:

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

Uso

Dentro de tu APP solo tendrás un Contexto. Este contexto se hereda de BaseContext que implementa IRepository. Teniendo esto en cuenta, debe codificar su GUI y la capa empresarial para usar IRepository (de Base.ORM) y la clase específica (dentro de la dll específica de la empresa).

¡Funciona!

Bueno, está funcionando aquí.

Creo que he mostrado todas las partes relevantes aquí.
Por supuesto, hay más código dentro de las clases pero no es el caso. Estoy tratando de mostrar solo lo que realmente necesitas crear / implementar para hacerlo.

No olvides

  1. No olvide que tendrá que escribir su propio código para sembrar la base de datos. Para mi caso aquí, dentro de la misma interfaz para el complemento, tengo algo como Seed (IRepository) y solo manejo todo allí.
  2. No olvide que no tiene referencias del proyecto principal a los complementos. Esto significa que DEBE encontrar una manera de cargar menús, GUI, negocios y datos a través de interfaces y proveedores. Logré resolver eso utilizando algo como IPlugin (negocio), IFormPlugin (GUI - Winforms) y IPluginRepository (datos). Tendrá que encontrar sus propios nombres y métodos que puedan ajustarse a sus necesidades, pero este debe ser un buen punto de partida.
  3. No olvide que si carga un complemento, debe crear las tablas dentro de la base de datos o EF CodeFirst no podrá inicializarse. Recuerde que puede necesitar archivos SQL y ejecútelos manualmente para crear tablas según sea necesario.
  4. No olvide que si descarga un complemento, también debe eliminar las tablas o EF también fallará.
  5. No olvides que REALMENTE NECESITAS BACKUPS. No hice esto todavía, pero ya he marcado dónde se hará esto (antes y después de los comandos DDL).
  6. Esta es MI solución para MI caso Esto debería ser un buen comienzo para nuevos proyectos, pero solo eso. No pienses que siguiendo lo que he hecho aquí funcionará al 100% en todos los casos.

Gracias

Gracias a las personas de SO y de MSDN que me ayudaron mucho con los comentarios y otras publicaciones que he encontrado.
Gracias a Caio Garcia (BR) que me ayudó con algunas instrucciones sobre otros sistemas basados ​​en complementos.

Códigos de muestra (completos)

Aquí sigue algunos códigos de muestra:

PARA LOS ARTÍCULOS BÁSICOS (elementos predefinidos de la solución)

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

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

EN TU CODIGO REGULAR

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