Pergunta

Exemplo

Eu tenho Person, SpecialPerson, e User. Person e SpecialPerson são apenas pessoas - eles não têm nome de usuário ou senha em um site, mas são armazenados em um banco de dados para manutenção de registros.O usuário tem todos os mesmos dados que Person e potencialmente SpecialPerson, junto com um nome de usuário e senha conforme registrados no site.


Como você resolveria esse problema?Você teria um Person tabela que armazena todos os dados comuns a uma pessoa e usa uma chave para procurar seus dados em SpecialPerson (se for uma pessoa especial) e Usuário (se for usuário) e vice-versa?

Foi útil?

Solução

Geralmente existem três maneiras de mapear a herança de objetos para tabelas de banco de dados.

Você pode fazer uma grande tabela com todos os campos de todos os objetos com um campo especial para o tipo.Isso é rápido, mas desperdiça espaço, embora os bancos de dados modernos economizem espaço ao não armazenar campos vazios.E se você estiver procurando apenas por todos os usuários da tabela, com todos os tipos de pessoas nela as coisas podem ficar lentas.Nem todos os mapeadores ou suportam isso.

Você pode criar tabelas diferentes para todas as classes filhas com todas as tabelas que contêm os campos da classe base.Isso é bom do ponto de vista do desempenho.Mas não do ponto de vista da manutenção.Cada vez que sua classe base muda, todas as tabelas mudam.

Você também pode fazer uma tabela por turma como sugeriu.Dessa forma, você precisa de junções para obter todos os dados.Portanto, tem menos desempenho.Acho que é a solução mais limpa.

O que você deseja usar depende, é claro, da sua situação.Nenhuma das soluções é perfeita, então você deve pesar os prós e os contras.

Outras dicas

Dê uma olhada em Martin Fowler Padrões de arquitetura de aplicativos empresariais:

  • Herança de tabela única:

    Ao mapear para um banco de dados relacional, tentamos minimizar as junções que podem surgir rapidamente ao processar uma estrutura de herança em múltiplas tabelas.Herança de tabela única mapeia todos os campos de todas as classes de uma estrutura de herança em uma única tabela.

  • Herança de tabela de classe:

    Você deseja estruturas de banco de dados que mapeiem claramente os objetos e permitam links em qualquer lugar da estrutura de herança.A herança de tabela de classe oferece suporte a isso usando uma tabela de banco de dados por classe na estrutura de herança.

  • Herança de tabela concreta:

    Pensando em tabelas do ponto de vista de uma instância de objeto, uma rota sensata é pegar cada objeto na memória e mapeá-lo para uma única linha do banco de dados.Isso implica Herança de Tabela Concreta, onde há uma tabela para cada classe concreta na hierarquia de herança.

Se Usuário, Pessoa e Pessoa Especial tivessem as mesmas chaves estrangeiras, eu teria uma única tabela.Adicione uma coluna chamada Tipo que é restrita a Usuário, Pessoa ou Pessoa Especial.Então, com base no valor de Type, há restrições nas outras colunas opcionais.

Para o código objeto não faz muita diferença se você tem tabelas separadas ou múltiplas tabelas para representar o polimorfismo.Porém, se você tiver que fazer SQL no banco de dados, será muito mais fácil se o polimorfismo for capturado em uma única tabela... desde que as chaves estrangeiras para os subtipos sejam as mesmas.

O que vou dizer aqui vai deixar os arquitetos de banco de dados em apuros, mas aqui vai:

Considere um banco de dados visualizar como o equivalente a uma definição de interface.E uma mesa equivale a uma classe.

Portanto, no seu exemplo, todas as classes de 3 pessoas implementarão a interface IPerson.Então você tem 3 tabelas - uma para cada 'User', 'Person' e 'SpecialPerson'.

Em seguida, tenha uma visualização 'PersonView' ou qualquer outra coisa que selecione as propriedades comuns (conforme definido pela sua 'interface') de todas as 3 tabelas na visualização única.Use uma coluna 'PersonType' nesta visualização para armazenar o tipo real da pessoa que está sendo armazenada.

Portanto, quando você estiver executando uma consulta que pode ser operada em qualquer tipo de pessoa, basta consultar a visualização PersonView.

Pode não ser isso que o OP pretendia perguntar, mas pensei em incluir isso aqui.

Recentemente tive um caso único de polimorfismo db em um projeto.Tínhamos entre 60 a 120 classes possíveis, cada uma com seu próprio conjunto de 30 a 40 atributos exclusivos e cerca de 10 a 12 atributos comuns em todas as classes.Decidimos seguir o caminho SQL-XML e acabamos com uma única tabela.Algo como :

PERSON (personid,persontype, name,address, phone, XMLOtherProperties)

contendo todas as propriedades comuns como colunas e, em seguida, um grande pacote de propriedades XML.A camada ORM ficou então responsável por ler/escrever as respectivas propriedades do XMLOtherProperties.Um pouco como :

 public string StrangeProperty
{
get { return XMLPropertyBag["StrangeProperty"];}
set { XMLPropertyBag["StrangeProperty"]= value;}
}

(acabamos mapeando a coluna xml como um documento Hastable em vez de um documento XML, mas você pode usar o que melhor se adequar ao seu DAL)

Não vai ganhar nenhum prêmio de design, mas funcionará se você tiver um número grande (ou desconhecido) de aulas possíveis.E no SQL2005 você ainda pode usar XPATH em suas consultas SQL para selecionar linhas com base em alguma propriedade armazenada como XML.é apenas uma pequena penalidade de desempenho a ser aceita.

Existem três estratégias básicas para lidar com herança em um banco de dados relacional e uma série de alternativas mais complexas/sob medida, dependendo de suas necessidades exatas.

  • Tabela por hierarquia de classes.Uma tabela para toda a hierarquia.
  • Tabela por subclasse.Uma tabela separada é criada para cada subclasse com uma associação 0-1 entre as tabelas da subclasse.
  • Tabela por classe concreta.Uma única tabela é criada para cada classe concreta.

Cada uma dessas abordagens levanta suas próprias questões sobre normalização, código de acesso a dados e armazenamento de dados, embora minha preferência pessoal seja usar tabela por subclasse a menos que haja um desempenho específico ou uma razão estrutural para escolher uma das alternativas.

Correndo o risco de ser um 'astronauta de arquitetura' aqui, eu estaria mais inclinado a usar tabelas separadas para as subclasses.Faça com que a chave primária das tabelas de subclasses também seja uma chave estrangeira vinculada ao supertipo.

A principal razão para fazer isso dessa maneira é que ele se torna muito mais consistente logicamente e você não acaba com muitos campos NULOS e sem sentido para aquele registro específico.Esse método também torna muito mais fácil adicionar campos extras aos subtipos à medida que você itera no processo de design.

Isso adiciona a desvantagem de adicionar JOINs às suas consultas, o que pode afetar o desempenho, mas quase sempre opto primeiro por um design ideal e depois procuro otimizar mais tarde, se for necessário.Nas poucas vezes em que segui primeiro o caminho “ideal”, quase sempre me arrependi depois.

Então meu design seria algo como

PESSOA (pessoa, nome, endereço, telefone, ...)

SPECIALPERSON (personid REFERENCES PERSON(personid), campos extras...)

USUÁRIO (personid REFERENCES PERSON(personid), nome de usuário, senha criptografada, campos extras...)

Você também pode criar VIEWs posteriormente que agreguem o supertipo e o subtipo, se necessário.

A única falha nessa abordagem é se você estiver procurando intensamente pelos subtipos associados a um supertipo específico.Não há uma resposta fácil para isso, você pode rastreá-la programaticamente, se necessário, ou então executar algumas consultas globais e armazenar em cache os resultados.Realmente vai depender da aplicação.

Eu diria que, dependendo do que diferencia Person e Special Person, você provavelmente não deseja polimorfismo para esta tarefa.

Eu criaria uma tabela User, uma tabela Person que possui um campo de chave estrangeira anulável para User (ou seja, a Person pode ser um usuário, mas não é obrigatório).
Então eu faria uma tabela SpecialPerson que se relacionasse com a tabela Person com quaisquer campos extras nela.Se um registro estiver presente em SpecialPerson para um determinado Person.ID, ele/ela é uma pessoa especial.

Em nossa empresa lidamos com polimorfismo combinando todos os campos em uma tabela e seu pior e nenhuma integridade referencial pode ser imposta e um modelo muito difícil de entender.Eu recomendaria contra essa abordagem, com certeza.

Eu usaria Tabela por subclasse e também evitaria impacto no desempenho, mas usando ORM, onde podemos evitar a união com todas as tabelas de subclasse, criando consultas dinamicamente, baseando-se no tipo.A estratégia mencionada acima funciona para pull de nível de registro único, mas para atualização ou seleção em massa você não pode evitá-la.

sim, eu também consideraria um TypeID junto com uma tabela PersonType, se for possível que haja mais tipos.No entanto, se houver apenas 3, isso não deveria ser necessário.

Este é um post mais antigo, mas pensei em avaliar do ponto de vista conceitual, processual e de desempenho.

A primeira pergunta que eu faria é sobre a relação entre pessoa, pessoa especial e usuário, e se é possível que alguém seja ambos uma pessoa especial e um usuário simultaneamente.Ou qualquer outra das 4 combinações possíveis (classe a + b, classe b + c, classe a + c ou a + b + c).Se esta classe for armazenada como um valor em um type campo e, portanto, reduziria essas combinações, e esse colapso é inaceitável, então eu pensaria que uma tabela secundária seria necessária, permitindo um relacionamento um-para-muitos.Aprendi que você não julga isso até avaliar o uso e o custo de perder as informações da sua combinação.

O outro fator que me faz inclinar-me para uma única mesa é a sua descrição do cenário. User é a única entidade com um nome de usuário (digamos varchar(30)) e senha (digamos varchar(32)).Se o comprimento possível dos campos comuns for em média 20 caracteres por 20 campos, então o aumento do tamanho da coluna será de 62 sobre 400, ou cerca de 15% - há 10 anos, isso teria sido mais caro do que com sistemas RDBMS modernos, especialmente com um tipo de campo como varchar (por exemplopara MySQL) disponível.

E, se a segurança é uma preocupação para você, pode ser vantajoso ter uma tabela um-para-um secundária chamada credentials ( user_id, username, password).Esta tabela seria invocada em um JOIN contextualmente no momento do login, mas estruturalmente separada de apenas "qualquer pessoa" na tabela principal.E, um LEFT JOIN está disponível para consultas que possam considerar "usuários registrados".

Minha principal consideração durante anos ainda é considerar o significado do objeto (e, portanto, a possível evolução) fora do DB e no mundo real.Neste caso, todos os tipos de pessoas têm corações pulsantes (espero) e também podem ter relações hierárquicas entre si;então, no fundo, mesmo que não seja agora, talvez precisemos armazenar essas relações por outro método.Isso não está explicitamente relacionado à sua pergunta aqui, mas é outro exemplo da expressão do relacionamento de um objeto.E agora (7 anos depois) você deve ter uma boa ideia de como sua decisão funcionou :)

No passado, fiz exatamente como você sugeriu - tenho uma tabela Person para coisas comuns e, em seguida, SpecialPerson vinculada à classe derivada.Porém, estou repensando isso, como o Linq2Sql quer ter um campo na mesma tabela indique a diferença.Porém, não olhei muito para o modelo de entidade - tenho certeza de que isso permite o outro método.

Pessoalmente, eu armazenaria todas essas classes de usuários diferentes em uma única tabela.Você pode então ter um campo que armazena um valor de 'Tipo' ou pode indicar com que tipo de pessoa você está lidando através dos campos preenchidos.Por exemplo, se UserID for NULL, esse registro não será um usuário.

Você pode vincular a outras tabelas usando um tipo de junção um para um ou nenhum, mas em cada consulta você adicionará junções extras.

O primeiro método também é suportado pelo LINQ-to-SQL se você decidir seguir esse caminho (eles o chamam de 'Tabela por Hierarquia' ou 'TPH').

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top