Entity Framework: una base de datos, múltiples DbContexts. ¿Es una mala idea? [cerrado]

213

Mi impresión hasta la fecha ha sido que a DbContextestá 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 DbContextclases separadas .

Creo que esto proviene de un buen lugar, un 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) asegura que todo saldrá bien.

Josh Schultz
fuente
Vea mi respuesta: stackoverflow.com/questions/8244405/…
Mohsen Alikhani

Respuestas:

168

Puede tener múltiples contextos para una sola base de datos. Puede ser útil, por ejemplo, si su base de datos contiene múltiples esquemas de base de datos y desea manejar cada uno de ellos como un área independiente.

El problema es cuando quiere usar el código primero para crear su base de datos; solo un contexto en su aplicación puede hacerlo. El truco para esto suele ser un contexto adicional que contiene todas sus entidades que se usa solo para la creación de bases de datos. Los contextos de aplicaciones reales que contienen solo subconjuntos de sus entidades deben tener inicializador de base de datos establecido en nulo.

Hay otros problemas que verá cuando use varios 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 su costos en complejidad adicional.

Ladislav Mrnka
fuente
21
El uso de un solo contexto por aplicación puede ser costoso si la aplicación tiene muchas entidades / tablas. Por lo tanto, dependiendo del esquema, también podría tener sentido tener múltiples contextos.
DarthVader
77
Como no me suscribo a pluralsight, encontré este increíble artículo de Julie Lerman ( su comentario ) escrito mucho después de este Q / A, pero muy apropiado: msdn.microsoft.com/en-us/magazine/jj883952.aspx
Dave T.
Sugiero que el marco de la entidad admita múltiples dbcontexts en la misma base de datos mediante la convención de nomenclatura. Por esta razón, todavía he estado escribiendo mi propio ORM para fines de aplicación modular. Es difícil de creer, obliga a una sola aplicación a usar una sola base de datos. Especialmente en las granjas web tiene un número limitado de bases de datos
libre albedrío
Además, me di cuenta de que puede habilitar las migraciones solo para un contexto dentro del proyecto a través de PM Console.
Piotr Kwiatek
9
@PiotrKwiatek No estoy seguro si esto cambió entre su comentario y ahora, pero Enable-Migrations -ContextTypeName MyContext -MigrationsDirectory Migrations\MyContextMigrationsfunciona ahora.
Zack
60

Escribí esta respuesta hace unos cuatro años y mi opinión no ha cambiado. Pero desde entonces ha habido desarrollos significativos en el frente de microservicios. Agregué notas específicas de microservicios al final ...

Sopesaré la idea, con experiencia en el mundo real para respaldar mi voto.

Fui llevado a una aplicación grande que tenía cinco contextos para una sola 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 ligeros y limpios. Suena como DDD, ¿verdad? Esto simplificaría nuestro acceso a los datos. Otro argumento es para el rendimiento, ya que solo 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 unir la tabla B en el contexto 2.

Esto nos dejó con un par de malas elecciones. Podríamos duplicar las tablas en los diversos contextos. Intentamos esto. Esto creó varios problemas de mapeo, incluida una restricción de EF que requiere que cada entidad tenga un nombre único. Así que terminamos con entidades llamadas Persona1 y Persona2 en los diferentes contextos. Se podría argumentar que este fue un diseño deficiente de nuestra parte, pero a pesar de nuestros mejores esfuerzos, así es como nuestra aplicación realmente 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 tuvo algunos problemas importantes. En lugar de realizar una consulta en un contexto único, tuvimos que realizar múltiples consultas en diferentes contextos. Esto tiene una penalización de rendimiento real.

Al final, la buena noticia es que fue fácil eliminar los múltiples contextos. El contexto está destinado a ser un objeto ligero. Así que 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 tendrá que implementar un montón de soluciones para que funcione.

Pensé en una situación en la que múltiples contextos podrían ser útiles. Se podría usar un contexto separado para solucionar un problema físico con la base de datos en la que realmente contiene más de un dominio. Idealmente, un contexto sería uno a uno para un dominio, que 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 dada, 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 elige no hacerlo), entonces podría ver el caso para usar un contexto separado, pero solo porque en realidad hay dos dominios separados.

Con respecto a los microservicios, un contexto único 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 para ese servicio.

Estoy interesado en tus pensamientos.

Francisco de Anconia
fuente
8
Tengo que estar de acuerdo aquí, particularmente cuando apunto a una base de datos existente. Estoy trabajando en este problema en este momento, y mi instinto hasta ahora es: 1. Tener la misma tabla física en múltiples contextos es una mala idea. 2. Si no podemos decidir que una tabla pertenece a un contexto u otro, entonces los dos contextos no son lo suficientemente distintos como para estar separados lógicamente.
jkerak
3
Yo diría que, al hacer CQRS, no tendrías ninguna relación entre contextos (cada vista podría tener su propio contexto), por lo que esta advertencia no se aplica a todos los casos en que uno quiera tener múltiples contextos. En lugar de unirse y hacer referencia, utilice la duplicación de datos para cada contexto. - Sin embargo, esto no niega la utilidad de esta respuesta :)
urbanhusky
1
¡Sentí el dolor que enfrentaste en lo profundo! : / También creo que un contexto es una mejor opción para la simplicidad.
ahmet
1
Mi único argumento en contra, señalando que, de lo contrario, estoy totalmente de acuerdo, se refiere a la identidad. Especialmente con el escalado horizontal, la capa de identidad necesita separarse en casi todos los casos en los que se introduce el equilibrio de carga. Al menos, eso es lo que estoy encontrando.
Barry
55
Para mí, parece que no te fuiste DDD hasta el final, si tus agregados necesitaban conocer otros agregados. Si necesita hacer referencia a algo, hay dos razones por las cuales: están en el mismo agregado, lo que significa que tienen que cambiarse en la misma transacción o no lo están, y se equivocaron sus límites.
Simons0n
54

Este hilo acaba de aparecer en StackOverflow, por lo que quería ofrecer otra garantía "B) de que todo estará bien" :)

Estoy haciendo exactamente esto a través del patrón de contexto limitado DDD. Lo he escrito en mi libro, Programming Entity Framework: DbContext y es el foco de un módulo de 50 minutos dentro de uno de mis cursos en Pluralsight -> http://pluralsight.com/training/Courses/TableOfContents/efarchitecture

Julie Lerman
fuente
77
El video de entrenamiento de pluralsight fue muy bueno para explicar los grandes conceptos, sin embargo, en mi opinión, los ejemplos que da son demasiado triviales en comparación con una solución empresarial (donde, por ejemplo, existen NuGet de ensamblajes con definiciones de DbContext, o los ensamblajes modulares se cargan dinámicamente). El contexto limitado DDD se rompió por completo en su ejemplo final donde se definió un DbContext duplicado para contener declaraciones duplicadas en cada DbSet. Aprecio que estés limitado por la tecnología. Realmente me gustan tus videos, pero este me dejó con ganas de más.
Victor Romeo
55
Definitivamente estaba apuntando a una gran imagen. Los paquetes de re nuget en las aplicaciones grandes están bastante fuera de contexto para un video ef. Re ejemplos "rotos" ... ¿Eh? Tal vez sea mejor llevar esto a convo privada ya que una crítica de mi curso está bastante fuera de alcance (y posiblemente inapropiada) para este foro. Creo que SO te permite contactarme directamente.
Julie Lerman
57
Hubiera sido agradable que Julie compartiera información sobre el tema / pregunta del OP. En cambio, la publicación es solo para promover una suscripción de pago a pluralinsight. Si es un complemento para un producto, al menos un enlace a la información sobre la solución sugerida (Patrón de contexto limitado DDD) sería útil. ¿Es 'DDD' lo que se describe en la p.222 de "Programming Entity Framework: DBContext"? Porque busqué (sin índice) 'DDD' o incluso 'Contexto limitado', y no puedo localizar ... No puedo esperar a que hagas nuevas revisiones para EF6 ...
Narcisista compasivo
12
lo siento, no estaba tratando de promocionar, solo agregué al OP "quiero garantías". Ladislav y otros hicieron un gran trabajo con los detalles. Así que solo estaba tratando de hacer algo que ya he creado que va mucho más allá de lo que podría transmitir en SO. Aquí hay otros recursos donde he cubierto algunas de las cosas en profundidad: msdn.microsoft.com/en-us/magazine/jj883952.aspx & msdn.microsoft.com/en-us/magazine/dn342868.aspx & oredev.org / 2013 / wed-fri-conference / ...
Julie Lerman
resumen de las sugerencias de código de @JulieLerman vea mi respuesta stackoverflow.com/a/36789520/1586498
OzBob
46

Distinguir contextos estableciendo el esquema predeterminado

En EF6 puede tener múltiples contextos, solo especifique el nombre para el esquema de base de datos predeterminado en el OnModelCreatingmétodo de su DbContextclase derivada (donde está la configuración 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 prefijará las __MigrationHistorytablas, por ejemplo Customer.__MigrationHistory. Por lo tanto, puede tener más de una __MigrationHistorytabla en una sola base de datos, una para cada contexto. Por lo tanto, los cambios que realice para 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-migrationcomando:

add-migration NAME_OF_MIGRATION -ConfigurationTypeName FULLY_QUALIFIED_NAME_OF_CONFIGURATION_CLASS


Una palabra breve sobre la clave de contexto

De acuerdo con 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 MigrationHistoryexistiera una tabla, porque en la tabla hay una columna ContextKey para distinguir las migraciones.

Sin embargo, prefiero tener más de una MigrationHistorytabla especificando el esquema predeterminado como se explicó anteriormente.


Usar carpetas de migración separadas

En este caso, es posible que también desee trabajar con diferentes carpetas de "Migración" en su proyecto. Puede configurar su DbMigrationsConfigurationclase derivada en consecuencia utilizando la MigrationsDirectorypropiedad:

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, puede decir que todo está limpio: Contextos, carpetas de migración en el proyecto y tablas en la base de datos.

Elegiría una solución así, 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 una de ellas y también accedería a ellas en diferentes proyectos, probablemente con un solo contexto en cada proyecto.

Martín
fuente
¿Qué haces cuando necesitas actualizar 2 entidades que se encuentran en diferentes contextos?
sotn
Crearía una nueva clase (de servicio) que conozca ambos contextos, piense en un buen nombre y las responsabilidades de esta clase y realice esta actualización en uno de sus métodos.
Martin
7

Ejemplo simple para lograr lo siguiente:

    ApplicationDbContext forumDB = new ApplicationDbContext();
    MonitorDbContext monitor = new MonitorDbContext();

Solo alcance las propiedades en el contexto principal: (usado para crear y mantener la base de datos) Nota: Solo use protegido: (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: exponga 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, luego cree 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, por lo que no cruza transacciones y tenga en cuenta los problemas de bloqueo. En general, diseña la separación, por lo que esto no debería suceder de todos modos.

Choco
fuente
2
Esto ayudó mucho. El contexto "secundario" no necesita declarar la tabla compartida. Simplemente agregue manualmente su DbSet<x>definición. Lo hago en una clase parcial que coincide con lo que hace EF Designer.
Glen Little
¡Me ahorró muchos dolores de cabeza, señor! Proporcionó una solución concreta en lugar de la respuesta aceptada. ¡Muy apreciado!
WoIIe
6

Recordatorio: si combina varios contextos, asegúrese de cortar y pegar todas las funcionalidades en sus varios RealContexts.OnModelCreating()en su único CombinedContext.OnModelCreating().

Simplemente perdí el tiempo buscando por qué mis relaciones de eliminación en cascada no se conservaban solo para descubrir que no había portado el modelBuilder.Entity<T>()....WillCascadeOnDelete();código de mi contexto real a mi contexto combinado.

Ilan
fuente
66
En lugar de cortar y pegar, ¿podría llamar OtherContext.OnModelCreating()desde su contexto combinado?
AlexFoxGill
4

Mi instinto me dijo lo mismo cuando me encontré con este diseño.

Estoy trabajando en una base de código donde hay tres dbContexts en una base de datos. 2 de los 3 dbcontexts dependen de la información de 1 dbcontext porque sirve los datos administrativos. Este diseño ha impuesto restricciones sobre cómo puede consultar sus datos. Me encontré con este problema en el que no puedes unirte a través de dbcontexts. En cambio, lo que debe hacer es consultar los dos dbcontexts separados y luego hacer una unión en la memoria o iterar a través de ambos para obtener la combinación de los dos como un conjunto de resultados. El problema con eso es que, en lugar de consultar un conjunto de resultados específico, ahora está cargando todos sus registros en la memoria y luego haciendo una unión contra los dos conjuntos de resultados en la memoria. Realmente puede ralentizar las cosas.

Yo haría la pregunta "solo porque puedes, ¿deberías? "

Consulte este artículo para ver el problema que encontré relacionado con este diseño. La expresión LINQ especificada contiene referencias a consultas que están asociadas a diferentes contextos

Victor J. Garcia
fuente
3
He trabajado en un sistema grande donde tuvimos múltiples contextos. Una de las cosas que encontré fue que a veces había que incluir el mismo DbSet en múltiples contextos. Por un lado, esto rompe algunas preocupaciones de pureza, pero le permite completar sus consultas. Para un caso en el que hay ciertas tablas de administración que necesita leer, puede agregarlas a una clase base de DbContext y heredarlas en los contextos del módulo de la aplicación. El propósito del contexto de administrador "real" podría redefinirse como "proporcionar mantenimiento para las tablas de administrador", en lugar de proporcionar todo el acceso a ellas.
JMarsch
1
Por lo que vale, siempre iba y venía sobre si valía la pena. Por un lado, con contextos separados, hay menos que saber para un desarrollador que solo quiere trabajar en un módulo, y se siente más seguro definiendo y usando proyecciones personalizadas (porque no le preocupan los efectos que tendrá en otro módulos). Por otro lado, te encuentras con algunos problemas cuando necesitas compartir datos entre contextos.
JMarsch
1
No tiene que incluir entidades en ambos, siempre puede obtener los identificadores y hacer una segunda consulta en un contexto diferente. Para sistemas pequeños, esto es malo, para DB / sistemas más grandes con muchos desarrolladores, la coherencia de las estructuras de múltiples tablas es un problema mucho más grande y más difícil que 2 consultas.
user1496062
4

Inspirado en [@JulieLerman 's DDD MSDN Mag Article 2013] [1]

    public class ShippingContext : BaseContext<ShippingContext>
{
  public DbSet<Shipment> Shipments { get; set; }
  public DbSet<Shipper> Shippers { get; set; }
  public DbSet<OrderShippingDetail> Order { get; set; } //Orders table
  public DbSet<ItemToBeShipped> ItemsToBeShipped { get; set; }
  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
    modelBuilder.Ignore<LineItem>();
    modelBuilder.Ignore<Order>();
    modelBuilder.Configurations.Add(new ShippingAddressMap());
  }
}

public class BaseContext<TContext>
  DbContext where TContext : DbContext
{
  static BaseContext()
  {
    Database.SetInitializer<TContext>(null);
  }
  protected BaseContext() : base("DPSalesDatabase")
  {}
}   

"Si está realizando un nuevo desarrollo y desea permitir que Code First cree o migre su base de datos en función de sus clases, deberá crear un" modelo súper "utilizando un DbContext que incluya todas las clases y relaciones necesarias para construir un modelo completo que represente la base de datos. Sin embargo, este contexto no debe heredar de BaseContext ". JL

OzBob
fuente
2

En el código primero, puede tener múltiples DBContext y solo una base de datos. Solo tiene que especificar la cadena de conexión en el constructor.

public class MovieDBContext : DbContext
{
    public MovieDBContext()
        : base("DefaultConnection")
    {

    }
    public DbSet<Movie> Movies { get; set; }
}
Daniel
fuente
Sí, puede, pero ¿cómo puede consultar desde diferentes entidades de diferentes contextos db?
Reza
2

Otro poco de "sabiduría". Tengo una base de datos para ambos, internet y una aplicación interna. Tengo un contexto para cada cara. Eso me ayuda a mantener una segregación disciplinada y segura.

Miguel Delgado
fuente
1

Quiero compartir un caso, donde creo que la posibilidad de tener múltiples DBContexts en la misma base de datos tiene sentido.

Tengo una solución con dos bases de datos. Uno es para datos de dominio, excepto la información del usuario. El otro es únicamente para información del usuario. Esta división está impulsada principalmente por el Reglamento general de protección de datos de la UE . Al tener dos bases de datos, puedo mover libremente los datos del dominio (por ejemplo, de Azure a mi entorno de desarrollo) siempre que los datos del usuario permanezcan en un lugar seguro.

Ahora para la base de datos de usuario he implementado dos esquemas a través de EF. Uno es el predeterminado proporcionado por el marco de AspNet Identity. El otro es nuestro propio implementar cualquier otra cosa relacionada con el usuario. Prefiero esta solución a extender el esquema de ApsNet, porque puedo manejar fácilmente los cambios futuros a la identidad de AspNet y, al mismo tiempo, la separación deja en claro a los programadores que "nuestra propia información de usuario" va en el esquema de usuario específico que hemos definido .

freilebt
fuente
2
No veo ninguna pregunta en mi respuesta. ¡No estoy haciendo una sola pregunta! Más bien compartiendo un escenario donde el tema de la discusión tiene sentido.
freilebt
0

Huh, pasé mucho tiempo en un problema con contextos de DB separados para cada esquema de DB, espero que ayude a alguien más ...

Recientemente comencé a trabajar en un proyecto que tenía una base de datos con 3 esquemas (primer enfoque de DB), uno de ellos para la gestión de usuarios. Había un contexto de base de datos andamio de cada esquema separado. Por supuesto, los usuarios también estaban relacionados con otros esquemas, por ejemplo. esquema KB tenía un tema de tabla, que había "creado por", "modificado por última vez por" etc. FK al esquema de identidad, usuario de tabla.

Estos objetos se cargaron por separado en C #, en primer lugar, el tema se cargó desde 1 contexto, luego los usuarios se cargaron a través de ID de usuario desde el otro contexto db, ¡no está bien, tienen que arreglar esto! (similar a usar múltiples dbcontexts en la misma base de datos con EF 6 )

Primero, intenté agregar las instrucciones FK que faltan del esquema de identidad al esquema KB, a EF modelBuilder en el contexto KB DB. Lo mismo que si hubiera solo 1 contexto, pero lo separé a 2.

modelBuilder.Entity<Topic>(entity =>
{
  entity.HasOne(d => d.Creator)
    .WithMany(p => p.TopicCreator)
    .HasForeignKey(d => d.CreatorId)
    .HasConstraintName("fk_topic_app_users");

No funcionó, porque el contexto kb db no tenía ninguna información sobre el objeto de usuario, postgres devolvió el error relation "AppUsers" does not exist. La instrucción Select no tenía información adecuada sobre el esquema, los nombres de campo, etc.

Casi me di por vencido, pero luego noté un interruptor "-d" al ejecutar dotnet ef dbcontext scaffold . Es la abreviatura de -data-annotations: use atributos para configurar el modelo (cuando sea posible). Si se omite, solo se usa la API fluida. Con este modificador especificado, las propiedades del objeto se definieron no en el contexto db OnModelCreating(), sino en el propio objeto, con atributos.

De esta manera, EF obtuvo información suficiente para generar una instrucción SQL adecuada con nombres y esquemas de campo adecuados.

TL; DR: los contextos de DB separados no manejan bien las relaciones (FK) entre ellos, cada contexto solo tiene información sobre sus propias entidades. Cuando se especifica la activación de "-data-annotations" dotnet ef dbcontext scaffold, estas informaciones no se almacenan en cada contexto por separado, sino en los objetos DB.

frenético
fuente