Estrutura de entidade: um banco de dados, vários DbContexts. Isso é uma má ideia? [fechadas]


212

Minha impressão até o momento foi que a DbContextpretende representar seu banco de dados e, portanto, se seu aplicativo usa um banco de dados, você deseja apenas um DbContext.

No entanto, alguns colegas querem dividir as áreas funcionais em DbContextclasses separadas .

Eu acredito que isso vem de um bom lugar - um desejo de manter o código mais limpo - mas parece volátil. Meu intestino está me dizendo que é uma má idéia, mas, infelizmente, meu pressentimento não é uma condição suficiente para uma decisão de design.

Então, eu estou procurando:

A) exemplos concretos de por que isso pode ser uma má idéia;

B) garantias de que tudo isso funcionará perfeitamente.


Respostas:


168

Você pode ter vários contextos para um único banco de dados. Pode ser útil, por exemplo, se seu banco de dados contiver vários esquemas de banco de dados e você desejar manipular cada um deles como uma área independente separada.

O problema é quando você deseja usar o código primeiro para criar seu banco de dados - apenas um único contexto no seu aplicativo pode fazer isso. O truque para isso é geralmente um contexto adicional contendo todas as suas entidades que é usado apenas para a criação do banco de dados. Seus contextos de aplicativos reais que contêm apenas subconjuntos de suas entidades devem ter o inicializador de banco de dados definido como nulo.

Existem outros problemas que você verá ao usar vários tipos de contexto - por exemplo, tipos de entidade compartilhada e sua passagem de um contexto para outro, etc. Geralmente é possível, ele pode tornar seu design muito mais limpo e separar diferentes áreas funcionais, mas possui suas características. custos com complexidade adicional.


21
O uso de contexto único por aplicativo pode ser caro se o aplicativo tiver muitas entidades / tabelas. Portanto, dependendo do esquema, também pode fazer sentido ter vários contextos.
DarthVader

7
Como não sou assinante do pluralsight, encontrei este incrível artigo de Julie Lerman ( seu comentário ) escrito bem após esta entrevista, mas muito apropriado: msdn.microsoft.com/en-us/magazine/jj883952.aspx
Dave T.

Sugiro, estrutura de entidade para suportar vários dbcontexts no mesmo banco de dados, nomeando convenção. Por esse motivo, ainda escrevi meu próprio ORM para fins de aplicação modular. É difícil acreditar que isso força o aplicativo único a usar o banco de dados único. Especialmente em web farms, você tem um número limitado de bancos de dados
freewill 29/11

Além disso, percebi que você pode ativar as migrações apenas para um contexto dentro do projeto via PM Console.
Piotr Kwiatek

9
@PiotrKwiatek Não tenho certeza se isso mudou entre seu comentário e agora, mas Enable-Migrations -ContextTypeName MyContext -MigrationsDirectory Migrations\MyContextMigrationsfunciona agora.
Zack

60

Escrevi esta resposta há cerca de quatro anos e minha opinião não mudou. Mas desde então, houve desenvolvimentos significativos na frente de microsserviços. Adicionei notas específicas sobre microsserviços no final ...

Eu vou pesar contra a idéia, com experiência no mundo real para apoiar meu voto.

Fui levado a um aplicativo grande que tinha cinco contextos para um único banco de dados. No final, acabamos removendo todos os contextos, exceto um - revertendo para um único contexto.

A princípio, a ideia de múltiplos contextos parece uma boa ideia. Podemos separar nosso acesso a dados em domínios e fornecer vários contextos leves e limpos. Parece DDD, certo? Isso simplificaria nosso acesso a dados. Outro argumento é para o desempenho, pois apenas acessamos o contexto que precisamos.

Mas, na prática, à medida que nosso aplicativo crescia, muitas de nossas tabelas compartilhavam relacionamentos entre nossos vários contextos. Por exemplo, as consultas à tabela A no contexto 1 também exigiam a junção da tabela B no contexto 2.

Isso nos deixou com algumas escolhas ruins. Poderíamos duplicar as tabelas nos vários contextos. Nós tentamos isso. Isso criou vários problemas de mapeamento, incluindo uma restrição EF que requer que cada entidade tenha um nome exclusivo. Então, acabamos com entidades nomeadas Person1 e Person2 nos diferentes contextos. Pode-se argumentar que esse projeto foi ruim da nossa parte, mas, apesar dos nossos melhores esforços, é assim que nosso aplicativo realmente cresceu no mundo real.

Também tentamos consultar os dois contextos para obter os dados necessários. Por exemplo, nossa lógica de negócios consultaria metade do que era necessário no contexto 1 e a outra metade no contexto 2. Isso apresentava alguns problemas importantes. Em vez de executar uma consulta em um único contexto, tivemos que realizar várias consultas em diferentes contextos. Isso tem uma penalidade de desempenho real.

No final, a boa notícia é que foi fácil remover os múltiplos contextos. O contexto pretende ser um objeto leve. Portanto, não acho que o desempenho seja um bom argumento para vários contextos. Em quase todos os casos, acredito que um único contexto é mais simples, menos complexo e provavelmente terá um desempenho melhor, e você não precisará implementar várias soluções alternativas para fazê-lo funcionar.

Pensei em uma situação em que múltiplos contextos poderiam ser úteis. Um contexto separado pode ser usado para corrigir um problema físico no banco de dados no qual ele realmente contém mais de um domínio. Idealmente, um contexto seria um para um em um domínio, que seria um para um em um banco de dados. Em outras palavras, se um conjunto de tabelas não estiver relacionado às outras tabelas em um determinado banco de dados, elas provavelmente deverão ser extraídas para um banco de dados separado. Sei que isso nem sempre é prático. Mas se um conjunto de tabelas for tão diferente que você se sinta à vontade para separá-las em um banco de dados separado (mas você optar por não), eu poderia ver o caso de usar um contexto separado, mas apenas porque na verdade existem dois domínios separados.

Em relação aos microsserviços, um único contexto ainda faz sentido. No entanto, para microsserviços, cada serviço teria seu próprio contexto, que inclui apenas as tabelas de banco de dados relevantes para esse serviço. Em outras palavras, se o serviço x acessar as tabelas 1 e 2 e o serviço y acessar as tabelas 3 e 4, cada serviço terá seu próprio contexto exclusivo, que inclui tabelas específicas para esse serviço.

Estou interessado em seus pensamentos.


8
Eu tenho que concordar aqui, principalmente ao direcionar um banco de dados existente. Estou trabalhando neste problema agora, e meu instinto até agora é: 1. Ter a mesma tabela física em vários contextos é uma má idéia. 2. Se não podemos decidir que uma tabela pertence a um contexto ou outro, então os dois contextos não são suficientemente distintos para serem separados logicamente.
jkerak

3
Eu argumentaria que, ao fazer o CQRS, você não teria nenhum relacionamento entre contextos (cada visualização poderia ter seu próprio contexto), portanto, esse aviso não se aplica a todos os casos em que se queira ter múltiplos contextos. Em vez de ingressar e referenciar, use a duplicação de dados para cada contexto. -
Porém

1
Eu senti a dor que você enfrentou profundamente! : / Eu também acho que um contexto é a melhor escolha pela simplicidade.
21418

1
Meu único argumento contra, observando que eu concordo plenamente, é em relação à identidade. Especialmente com o dimensionamento horizontal, a camada de identidade precisa ser separada em quase todos os casos em que o balanceamento de carga é introduzido. Pelo menos, é o que estou encontrando.
Barry

5
Para mim, parece que você não fez DDD o tempo todo, se seus agregados precisassem conhecer outros agregados. Se você precisar fazer referência a algo, há dois motivos: eles estão no mesmo agregado, o que significa que eles precisam ser alterados na mesma transação ou não, e você errou seus limites.
Simons0n

54

Esse tópico apenas surgiu no StackOverflow e, portanto, eu queria oferecer outra garantia "B) de que tudo ficará bem" :)

Estou fazendo exatamente isso por meio do padrão DDD Bounded Context. Eu escrevi sobre isso em meu livro, Programming Entity Framework: DbContext e é o foco de um módulo de 50 minutos em um dos meus cursos sobre Pluralsight -> http://pluralsight.com/training/Courses/TableOfContents/efarchitecture


7
No entanto, o vídeo de treinamento sobre visão geral foi muito bom para explicar os grandes conceitos. Os exemplos que você fornece são muito triviais em comparação com uma solução corporativa (onde, por exemplo, o NuGet de montagens com definições de DbContext existe ou as montagens modulares são carregadas dinamicamente). O Contexto delimitado por DDD foi completamente quebrado pelo seu exemplo final, em que um DbContext duplicado foi definido para armazenar declarações duplicadas para cada DbSet. Aprecio que você esteja limitado pela tecnologia. Eu realmente gosto dos seus vídeos, mas este me deixou querendo mais.
Victor Romeo

5
Eu estava def apontando para o quadro geral. Os pacotes re nuget nos aplicativos grandes estão fora de contexto para um vídeo ef. Re exemplos "quebrados" ... Hein? Talvez seja melhor levar isso para convo particular, já que uma crítica do meu curso está fora do escopo (e possivelmente inapropriada) para este fórum. Eu acho que SO permite que você entre em contato comigo diretamente.
Julie Lerman

57
Teria sido bom Julie compartilhar algumas informações sobre a questão / questão do OP. Em vez disso, o post é apenas para promover uma assinatura paga para a pluralinsight. Se um plug para um produto, pelo menos um link para informações sobre a solução sugerida (DDD Bounded context Pattern) seria útil. 'DDD' é o descrito na p.222 de "Programming Entity Framework: DBContext"? Porque eu olhei (sem índice) para 'DDD' ou mesmo 'Contexto Limitada', e não é possível localizar ... Mal posso esperar para que você faça novas revisões para EF6 ...
Compassivo Narcissist

12
desculpe, eu não estava tentando promover, apenas adicionando ao OP "quero garantias". Ladislav e outros fizeram um ótimo trabalho com detalhes. Então, eu estava apenas tentando algo que eu já criei que é muito mais profundo do que eu poderia contar com o SO. Aqui estão outros recursos nos quais eu abordei algumas das informações detalhadas: msdn.microsoft.com/en-us/magazine/jj883952.aspx & msdn.microsoft.com/en-us/magazine/dn342868.aspx & oredev.org / 2013 / quarta-feira-conferência /…
Julie Lerman

resumo das sugestões de código de @JulieLerman, veja minha resposta stackoverflow.com/a/36789520/1586498
OzBob

46

Distinguindo contextos configurando o esquema padrão

No EF6, você pode ter vários contextos, basta especificar o nome do esquema de banco de dados padrão no OnModelCreatingmétodo da sua DbContextclasse derivada (onde está a configuração da API do Fluent). Isso funcionará no EF6:

public partial class CustomerModel : DbContext
{   
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.HasDefaultSchema("Customer");

        // Fluent API configuration
    }   
}

Este exemplo usará "Customer" como prefixo para suas tabelas de banco de dados (em vez de "dbo"). Mais importante, ele também prefixará a __MigrationHistory(s) tabela (s), por exemplo Customer.__MigrationHistory. Portanto, você pode ter mais de uma __MigrationHistorytabela em um único banco de dados, uma para cada contexto. Portanto, as alterações feitas em um contexto não interferem no outro.

Ao adicionar a migração, especifique o nome completo da sua classe de configuração (derivada de DbMigrationsConfiguration) como parâmetro no add-migrationcomando:

add-migration NAME_OF_MIGRATION -ConfigurationTypeName FULLY_QUALIFIED_NAME_OF_CONFIGURATION_CLASS


Uma palavra curta na chave de contexto

De acordo com este artigo do MSDN " Capítulo - Vários modelos direcionados para o mesmo banco de dados ", o EF 6 provavelmente lidaria com a situação mesmo que houvesse apenas uma MigrationHistorytabela, porque na tabela há uma coluna ContextKey para distinguir as migrações.

No entanto, prefiro ter mais de uma MigrationHistorytabela especificando o esquema padrão, como explicado acima.


Usando pastas de migração separadas

Nesse cenário, você também pode querer trabalhar com diferentes pastas "Migração" em seu projeto. Você pode configurar sua DbMigrationsConfigurationclasse derivada de acordo usando a MigrationsDirectorypropriedade:

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


Resumo

Em suma, você pode dizer que tudo está bem separado: contextos, pastas de migração no projeto e tabelas no banco de dados.

Eu escolheria essa solução, se houver grupos de entidades que fazem parte de um tópico maior, mas não estão relacionados (por meio de chaves estrangeiras) entre si.

Se os grupos de entidades não tiverem o que fazer, eu criaria um banco de dados separado para cada uma delas e também as acessaria em projetos diferentes, provavelmente com um único contexto em cada projeto.


O que você faz quando precisa atualizar 2 entidades que estão em contextos diferentes?
SOTN

Eu criaria uma nova classe (serviço) que conheça os dois contextos, pense em um bom nome e nas responsabilidades dessa classe e faça essa atualização em um de seus métodos.
Martin

7

Exemplo simples para obter o abaixo:

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

Apenas escopo as propriedades no contexto principal: (usado para criar e manter o banco de dados) Nota: Apenas use protected: (A entidade não é exposta aqui)

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: exponha uma entidade separada aqui

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

Se desejar, você pode marcar todas as entidades como protegidas dentro do ApplicationDbContext principal, crie contextos adicionais conforme necessário para cada separação de esquemas.

Todos eles usam a mesma cadeia de conexão, no entanto, usam conexões separadas; portanto, não cruzam transações e estejam cientes dos problemas de bloqueio. Geralmente o seu projeto de separação, portanto, isso não deve acontecer de qualquer maneira.


2
Isso ajudou muito. O contexto "secundário" não precisa declarar a tabela compartilhada. Apenas adicione manualmente sua DbSet<x>definição. Eu faço isso em uma classe parcial que corresponde ao que o EF Designer faz.
Glen Little

Você me salvou muitas dores de cabeça, senhor! Você forneceu uma solução concreta em vez da resposta aceita. Realmente apreciado!
WoIIe 16/04

6

Lembrete: Se você combinar vários contextos, corte e cole todas as funcionalidades dos seus vários RealContexts.OnModelCreating()no seu single CombinedContext.OnModelCreating().

Eu perdi tempo procurando por que meus relacionamentos de exclusão em cascata não estavam sendo preservados apenas para descobrir que eu não havia transportado o modelBuilder.Entity<T>()....WillCascadeOnDelete();código do meu contexto real para o meu contexto combinado.


6
Em vez de recortar e colar, você poderia apenas chamar OtherContext.OnModelCreating()do seu contexto combinado?
AlexFoxGill

4

Meu intestino me disse a mesma coisa quando me deparei com este design.

Estou trabalhando em uma base de código onde existem três dbContexts em um banco de dados. 2 dos 3 dbcontexts dependem das informações de 1 dbcontext, porque ele fornece os dados administrativos. Esse design colocou restrições sobre como você pode consultar seus dados. Corri para esse problema em que você não pode ingressar nos dbcontexts. Em vez disso, o que você precisa fazer é consultar os dois dbcontexts separados e, em seguida, fazer uma junção na memória ou iterar através de ambos para obter a combinação dos dois como um conjunto de resultados. O problema é que, em vez de consultar um conjunto de resultados específico, você está carregando todos os seus registros na memória e fazendo uma junção nos dois conjuntos de resultados na memória. Pode realmente atrasar as coisas.

Eu faria a pergunta "só porque você pode, deveria? "

Consulte este artigo para o problema que me deparei relacionado a este design. A expressão LINQ especificada contém referências a consultas associadas a diferentes contextos


3
Eu trabalhei em um grande sistema em que tivemos vários contextos. Uma das coisas que descobri foi que às vezes você precisava incluir o mesmo DbSet em vários contextos. Por um lado, isso quebra algumas preocupações de pureza, mas permite que você complete suas consultas. Para um caso em que há determinadas tabelas administrativas que você precisa ler, você pode adicioná-las a uma classe DbContext base e herdá-las nos contextos do módulo de aplicativo. O objetivo "real" do contexto administrativo pode ser redefinido como "fornecer manutenção para tabelas administrativas", em vez de fornecer todo o acesso a elas.
JMarsch

1
Pelo que vale, eu sempre andava de um lado para o outro sobre se valia a pena. Por um lado, em contextos separados, há menos a saber para um desenvolvedor que deseja apenas trabalhar em um módulo e você se sente mais seguro ao definir e usar projeções personalizadas (porque não está preocupado com os efeitos que ele terá em outros módulos). Por outro lado, você se depara com alguns problemas quando precisa compartilhar dados entre contextos.
JMarsch

1
Você não precisa incluir entidades nos dois. Você sempre pode obter os IDs e fazer uma segunda consulta em um contexto diferente. Para sistemas pequenos, isso é ruim, para bancos de dados / sistemas maiores com muitos desenvolvedores, a coerência das estruturas de várias tabelas é um problema muito maior e mais difícil do que 2 consultas.
user1496062

4

Inspirado no [artigo DDD MSDN Mag de 2013 de JulieLerman] [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")
  {}
}   

"Se você está desenvolvendo um novo desenvolvimento e deseja permitir que o Code First crie ou migre seu banco de dados com base em suas classes, será necessário criar um" ubermodelo "usando um DbContext que inclua todas as classes e relacionamentos necessários para crie um modelo completo que represente o banco de dados. No entanto, esse contexto não deve herdar de BaseContext. " JL


2

No código primeiro, você pode ter vários DBContext e apenas um banco de dados. Você apenas precisa especificar a cadeia de conexão no construtor.

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

    }
    public DbSet<Movie> Movies { get; set; }
}

Sim, você pode, mas como você pode consultar de diferentes entidades de diferentes contextos de banco de dados?
Reza

2

Mais um pouco de "sabedoria". Eu tenho um banco de dados voltado para ambos, a Internet e um aplicativo interno. Eu tenho um contexto para cada rosto. Isso me ajuda a manter uma segregação segura e disciplinada.


1

Quero compartilhar um caso em que acho que a possibilidade de ter vários DBContexts no mesmo banco de dados faz sentido.

Eu tenho uma solução com dois banco de dados. Um é para dados do domínio, exceto informações do usuário. O outro é apenas para informações do usuário. Esta divisão é impulsionada principalmente pelo Regulamento Geral de Proteção de Dados da UE . Por ter dois bancos de dados, posso mover livremente os dados do domínio (por exemplo, do Azure para o meu ambiente de desenvolvimento), desde que os dados do usuário permaneçam em um local seguro.

Agora, para o banco de dados do usuário, implementei dois esquemas através do EF. Um é o padrão fornecido pela estrutura do AspNet Identity. A outra é a nossa própria implementação de qualquer outra coisa relacionada ao usuário. Prefiro esta solução do que estender o esquema ApsNet, porque posso lidar facilmente com alterações futuras no AspNet Identity e, ao mesmo tempo, a separação deixa claro para os programadores que "nossas próprias informações do usuário" vão para o esquema de usuário específico que definimos .


2
Não vejo nenhuma pergunta na minha resposta. Não estou fazendo uma única pergunta! Em vez disso, compartilhe um cenário em que o tópico da discussão faça sentido.
freilebt

0

Huh, passou bastante tempo com um problema com contextos de banco de dados separados para cada esquema de banco de dados, espero que ajude outra pessoa ...

Recentemente, comecei a trabalhar em um projeto que possuía um banco de dados com 3 esquemas (primeira abordagem do banco de dados), um deles para gerenciamento de usuários. Havia um contexto de banco de dados estruturado em cada esquema separado. Obviamente, os usuários também estavam relacionados a outros esquemas, por exemplo. O esquema KB teve uma tabela Tópico, que foi "criado por", "modificado pela última vez por" etc.) FK para esquema de identidade, tabela appuser.

Esses objetos foram carregados separadamente em C #; primeiro, o tópico foi carregado de 1 contexto; os usuários foram carregados por meio de IDs de usuários do outro contexto de banco de dados - nada bom, é preciso corrigir isso! (semelhante ao uso de vários dbcontexts no mesmo banco de dados do EF 6 )

Primeiro, tentei adicionar instruções FK ausentes do esquema de identidade ao esquema da KB, ao EF modelBuilder no contexto da base de dados da KB. O mesmo que se houvesse apenas 1 contexto, mas eu o separei para 2.

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

Não funcionou, porque o contexto kb db não tinha nenhuma informação sobre o objeto usuário, e o postgres retornou um erro relation "AppUsers" does not exist. A instrução Select não tinha informações adequadas sobre esquema, nomes de campos etc.

Eu quase desisti, mas notei uma opção "-d" ao executar dotnet ef dbcontext scaffold. Sua abreviação de -data-annotations - Use atributos para configurar o modelo (sempre que possível). Se omitido, apenas a API fluente é usada. Com essa opção especificada, as propriedades do objeto foram definidas não no contexto db OnModelCreating(), mas no próprio objeto, com atributos.

Dessa forma, a EF obteve informações suficientes para gerar uma instrução SQL adequada com nomes e esquemas de campos adequados.

TL; DR: contextos de banco de dados separados não lidam bem com relações (FKs) entre eles, cada contexto possui apenas informações sobre suas próprias entidades. Ao especificar "-data-anototations" dotnet ef dbcontext scaffold, essas informações não são armazenadas em cada contexto separado, mas nos próprios objetos do banco de dados.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.