Entity Framework: una base de datos, múltiples DbContexts. ¿Es una mala idea? [cerrado]
Mi impresión hasta la fecha ha sido que a DbContext
está destinado a representar su base de datos y, por lo tanto, si su aplicación usa una base de datos, solo querrá una DbContext
.
Sin embargo, algunos colegas quieren dividir las áreas funcionales en DbContext
clases separadas.
Creo que esto proviene de un buen motivo: el deseo de mantener el código más limpio, pero parece volátil. Mi instinto me dice que es una mala idea, pero desafortunadamente, mi instinto no es una condición suficiente para una decisión de diseño.
Entonces estoy buscando:
A) ejemplos concretos de por qué esto podría ser una mala idea;
B) garantías de que todo saldrá bien.
Puede tener múltiples contextos para una sola base de datos. Puede resultar útil, por ejemplo, si su base de datos contiene varios esquemas de base de datos y desea manejar cada uno de ellos como un área independiente.
El problema es cuando desea utilizar el código primero para crear su base de datos; solo un contexto único en su aplicación puede hacerlo. El truco para esto suele ser un contexto adicional que contiene todas sus entidades y que se usa solo para la creación de bases de datos. Los contextos de su aplicación real que contienen solo subconjuntos de sus entidades deben tener el inicializador de la base de datos configurado en nulo.
Hay otros problemas que verá al utilizar múltiples tipos de contexto, por ejemplo, tipos de entidades compartidas y su paso de un contexto a otro, etc. Generalmente es posible, puede hacer que su diseño sea mucho más limpio y separar diferentes áreas funcionales, pero tiene sus ventajas. costos en complejidad adicional.
Escribí esta respuesta hace unos cuatro años y mi opinión no ha cambiado. Pero desde entonces ha habido avances significativos en el frente de los microservicios. Agregué notas específicas de microservicios al final...
Me opondré a la idea, con experiencia del mundo real para respaldar mi voto.
Me llevaron a una aplicación grande que tenía cinco contextos para una única base de datos. Al final, terminamos eliminando todos los contextos excepto uno, volviendo a un solo contexto.
Al principio, la idea de múltiples contextos parece una buena idea. Podemos separar nuestro acceso a datos en dominios y proporcionar varios contextos limpios y livianos. Suena como DDD, ¿verdad? Esto simplificaría nuestro acceso a los datos. Otro argumento a favor del rendimiento es que sólo accedemos al contexto que necesitamos.
Pero en la práctica, a medida que nuestra aplicación creció, muchas de nuestras tablas compartieron relaciones en nuestros diversos contextos. Por ejemplo, las consultas a la tabla A en el contexto 1 también requerían unirse a la tabla B en el contexto 2.
Esto nos dejó con un par de malas decisiones. Podríamos duplicar las tablas en los distintos contextos. Probamos esto. Esto creó varios problemas de mapeo, incluida una restricción EF que requiere que cada entidad tenga un nombre único. Entonces terminamos con entidades llamadas Persona1 y Persona2 en diferentes contextos. Se podría argumentar que se trata de un mal diseño de nuestra parte, pero a pesar de nuestros mejores esfuerzos, así es como nuestra aplicación creció en el mundo real.
También intentamos consultar ambos contextos para obtener los datos que necesitábamos. Por ejemplo, nuestra lógica de negocios consultaría la mitad de lo que necesitaba del contexto 1 y la otra mitad del contexto 2. Esto tenía algunos problemas importantes. En lugar de realizar una consulta en un único contexto, tuvimos que realizar varias consultas en diferentes contextos. Esto tiene una penalización real en el rendimiento.
Al final, la buena noticia es que fue fácil eliminar los múltiples contextos. El contexto pretende ser un objeto ligero. Por tanto, no creo que el rendimiento sea un buen argumento para múltiples contextos. En casi todos los casos, creo que un contexto único es más simple, menos complejo y probablemente funcionará mejor, y no será necesario implementar un montón de soluciones para que funcione.
Pensé en una situación en la que múltiples contextos podrían resultar útiles. Se podría utilizar un contexto separado para solucionar un problema físico con la base de datos en la que en realidad contiene más de un dominio. Idealmente, un contexto sería uno a uno para un dominio, que a su vez sería uno a uno para una base de datos. En otras palabras, si un conjunto de tablas no está relacionado de ninguna manera con las otras tablas en una base de datos determinada, probablemente deberían extraerse en una base de datos separada. Me doy cuenta de que esto no siempre es práctico. Pero si un conjunto de tablas es tan diferente que se sentiría cómodo separándolas en una base de datos separada (pero decide no hacerlo), entonces podría ver el caso de usar un contexto separado, pero solo porque en realidad hay dos dominios separados.
En cuanto a los microservicios, un único contexto todavía tiene sentido. Sin embargo, para los microservicios, cada servicio tendría su propio contexto que incluye solo las tablas de la base de datos relevantes para ese servicio. En otras palabras, si el servicio x accede a las tablas 1 y 2, y el servicio y accede a las tablas 3 y 4, cada servicio tendría su propio contexto único que incluye tablas específicas de ese servicio.
Me interesan tus pensamientos.
Distinguir contextos estableciendo el esquema predeterminado
En EF6 puede tener múltiples contextos, simplemente especifique el nombre del esquema de base de datos predeterminado en el OnModelCreating
método de su DbContext
clase derivada (donde está la configuración de Fluent-API). Esto funcionará en EF6:
public partial class CustomerModel : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("Customer");
// Fluent API configuration
}
}
Este ejemplo utilizará "Cliente" como prefijo para las tablas de su base de datos (en lugar de "dbo"). Más importante aún, también antepondrá la __MigrationHistory
(s) tabla(s), por ejemplo Customer.__MigrationHistory
. Entonces puedes tener más de una __MigrationHistory
tabla en una sola base de datos, una para cada contexto. Por lo tanto, los cambios que realice en un contexto no afectarán al otro.
Al agregar la migración, especifique el nombre completo de su clase de configuración (derivada de DbMigrationsConfiguration
) como parámetro en el add-migration
comando:
add-migration NAME_OF_MIGRATION -ConfigurationTypeName FULLY_QUALIFIED_NAME_OF_CONFIGURATION_CLASS
Una breve palabra sobre la clave del contexto.
Según este artículo de MSDN " Capítulo: Múltiples modelos dirigidos a la misma base de datos ", EF 6 probablemente manejaría la situación incluso si solo MigrationHistory
existiera una tabla, porque en la tabla hay una columna ContextKey para distinguir las migraciones.
Sin embargo, prefiero tener más de una MigrationHistory
tabla especificando el esquema predeterminado como se explicó anteriormente.
Usar carpetas de migración separadas
En tal escenario, es posible que también desees trabajar con diferentes carpetas de "Migración" en tu proyecto. Puede configurar su DbMigrationsConfiguration
clase derivada en consecuencia usando la MigrationsDirectory
propiedad:
internal sealed class ConfigurationA : DbMigrationsConfiguration<ModelA>
{
public ConfigurationA()
{
AutomaticMigrationsEnabled = false;
MigrationsDirectory = @"Migrations\ModelA";
}
}
internal sealed class ConfigurationB : DbMigrationsConfiguration<ModelB>
{
public ConfigurationB()
{
AutomaticMigrationsEnabled = false;
MigrationsDirectory = @"Migrations\ModelB";
}
}
Resumen
Con todo, se puede decir que todo está claramente separado: contextos, carpetas de migración en el proyecto y tablas en la base de datos.
Elegiría esta solución si hay grupos de entidades que forman parte de un tema más amplio, pero que no están relacionadas (a través de claves externas) entre sí.
Si los grupos de entidades no tienen nada que hacer entre sí, crearía una base de datos separada para cada uno de ellos y también accedería a ellos en diferentes proyectos, probablemente con un único contexto en cada proyecto.
Ejemplo simple para lograr lo siguiente:
ApplicationDbContext forumDB = new ApplicationDbContext();
MonitorDbContext monitor = new MonitorDbContext();
Simplemente alcance las propiedades en el contexto principal: (usado para crear y mantener la base de datos) Nota: solo use protected: (La entidad no está expuesta aquí)
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("QAForum", throwIfV1Schema: false)
{
}
protected DbSet<Diagnostic> Diagnostics { get; set; }
public DbSet<Forum> Forums { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<Thread> Threads { get; set; }
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
MonitorContext: Expone una entidad separada aquí
public class MonitorDbContext: DbContext
{
public MonitorDbContext()
: base("QAForum")
{
}
public DbSet<Diagnostic> Diagnostics { get; set; }
// add more here
}
Modelo de diagnóstico:
public class Diagnostic
{
[Key]
public Guid DiagnosticID { get; set; }
public string ApplicationName { get; set; }
public DateTime DiagnosticTime { get; set; }
public string Data { get; set; }
}
Si lo desea, puede marcar todas las entidades como protegidas dentro del ApplicationDbContext principal y luego crear contextos adicionales según sea necesario para cada separación de esquemas.
Todos usan la misma cadena de conexión, sin embargo, usan conexiones separadas, así que no cruce transacciones y tenga en cuenta los problemas de bloqueo. Generalmente estás diseñando una separación, por lo que esto no debería suceder de todos modos.