Gorm: Criar restrição de chave estrangeira entre referências

Criado em 2 dez. 2013  ·  19Comentários  ·  Fonte: go-gorm/gorm

Começando pelo seu exemplo;

type User struct {
        Id                int64     // Id: Primary key
       ................
        Emails            []Email       // Embedded structs
        BillingAddress    Address       // Embedded struct
        BillingAddressId  sql.NullInt64 // Embedded struct's foreign key
        ShippingAddress   Address       // Embedded struct
        ShippingAddressId int64         // Embedded struct's foreign key
        IgnoreMe          int64 `sql:"-"`
}

você não está criando restrições de chave estrangeira entre duas tabelas (User-Address), isso é útil para a integridade dos dados.

Comentários muito úteis

Este é um problema bastante sério, pois qualquer pessoa que use um ORM esperaria uma abstração adequada de chaves estrangeiras com restrição. Houve algum progresso desde a última atualização?

Todos 19 comentários

Poderia me dar mais detalhes sobre isso? Não entendi muito bem sua pergunta, obrigado.

@siesta está pedindo para adicionar restrições de chave estrangeira quando você tem uma relação. No exemplo acima você tem um usuário e o usuário tem um endereço de entrega. Uma restrição de chave estrangeira diria ao banco de dados para certificar-se de que ShippingAddressId existe na tabela shipping_address. Além da integridade dos dados, também ajuda com ferramentas que refletem nas restrições de chaves estrangeiras para modelar o banco de dados.

Ei pessoal, desculpe, esqueci de responder, e obrigado @pmorton , isso é exatamente o que eu queria declarar nesta edição.

Parece que é difícil torná-lo independente do banco de dados, especialmente para o postgres, pode quebrar o fluxo de criação da tabela porque o postgres é realmente estritamente sobre isso.

Também para a estrutura abaixo, não parece fácil obter a tabela estrangeira users .

type Email {
  Id int64
  UserId int64
  Email string
}

Então talvez seja melhor fazer isso sozinho por enquanto:

type User {
  ...
  BillingAddressId  sql.NullInt64 `sql:"type:bigint REFERENCES address(id)"`
}

Vou pensar mais sobre isso no futuro, por favor me sugira se você tiver alguma sugestão.

Obrigada.

Oi @jinzhu

Parece que é difícil torná-lo agnóstico de banco de dados especialmente para postgres

Na verdade, os bancos de dados que você está suportando agora suportam chaves estrangeiras, da mesma forma, até as definições são as mesmas
mysql = http://dev.mysql.com/doc/refman/5.6/en/create-table-foreign-keys.html
postgres = http://www.postgresql.org/docs/9.3/static/tutorial-fk.html
sqlite = http://www.sqlite.org/foreignkeys.html

pode quebrar o fluxo de criação da tabela porque o postgres é realmente estritamente sobre isso.
não vai, não deve quebrar

Também para a estrutura abaixo, não parece fácil obter os usuários da tabela estrangeira.

Porque a estrutura está um pouco errada, eu acho. Você só deve adicionar (incorporar) struct referenciado no pai
em vez deste

 type Email {
    Id int64
    UserId int64
    Email string
}

estrutura do banco de dados deve ser

 type Email {
    Id int64
    User User  //<--
    Email string
}

se um desenvolvedor quiser saber o que é userId, ele deve encontrá-lo através da propriedade User, como myStruct.User.Id
Com essa abordagem, é fácil fornecer esse recurso.

Portanto, adicionar UserId e User na mesma estrutura -imo- não é uma boa abordagem.
Por outro lado, esta abordagem irá ajudá-lo no problema sql.NullInt64 . -sim- eu acho que definir este campo é um problema e está em conflito com o seu lema developer friendly orm .

Então talvez seja melhor fazer isso sozinho por enquanto:

com certeza, eu poderia fazer isso, mas o meu problema é :) gostei muito do seu trabalho e quero fazer este pacote melhor, mesmo com o status atual, este é o MELHOR pacote orm que eu vi até agora! Obrigada pelos teus esforços.

Enquanto isso, você pode investigar Hibernate for Java, Doctrine for PHP, para definir tabelas referenciadas
aqui está um resultado rápido do google http://d.pr/V6lZ

Obrigado por sua resposta.

Parece que é difícil torná-lo agnóstico de banco de dados especialmente para postgres

Quero dizer que o postgres é realmente estritamente e lançaria um erro se a tabela de referência não existisse. mas o mysql está ok sobre isso.

Portanto, você precisa ajustar a ordem de migração com cuidado com base nas relações para evitar problemas, e isso seria complicado se duas tabelas referenciassem uma à outra. ;(

para a segunda pergunta

 type Email struct {
    Id int64
    User User
}

type User struct {
   Email Email
}

Eu acho que é difícil saber a chave estrangeira com base na definição acima? Então temos que usar tag para declará-lo se não tiver o campo UserId, assim:

 type Email struct {
    Id int64
    User User `sql:foreign_key("user_id")`
}

se um desenvolvedor quiser saber o que é userId, ele deve encontrá-lo através da propriedade User, como myStruct.User.Id
Com essa abordagem, é fácil fornecer esse recurso.

Obter user_id com myStruct.User.Id não é uma boa implementação porque é bastante confuso.

Por exemplo, você está recebendo user_id com myStruct.User.Id , e o nome? também obtê-lo com myStruct.User.Name ?

Então o gorm precisa descobrir o usuário relacionado ao consultar myStruct, isso seria um custo desnecessário se você não precisar de nenhuma informação do usuário.

De qualquer forma, eu apenas dou outro pensamento, talvez possamos implementá-lo com base nas ideias abaixo:

 type Email struct {
    Id int64
    User User
    UserId int64
}
  • Mantemos o UserId aqui para evitar confusão.
  • Se existir uma estrutura incorporada, para este exemplo, User , descobriremos a chave estrangeira com base na lógica atual e criaremos referências de chave estrangeira para ela.
 type Email struct {
    Id int64
    UserId int64
}
  • Se nenhuma estrutura incorporada for definida, não criará as referências.
  • Não sei se é possível fazer a migração automática funcionar com ele, vou verificar mais tarde.

Alguma outra ideia?

Para migração automática, que tal se você apenas listar as estruturas em ordem

DB.AutoMigrate(Role{})
DB.AutoMigrate(User{}) // User has a role_id field
 type Email struct {
    User User
}

type User struct {
   Email Email
}

Para as duas estruturas acima, elas dependem uma da outra, e para postgres, você não pode criar nenhuma referência se a tabela referida não existir.

por que não usar ALTER TABLE para as restrições? Afinal CREATE TABLE.

Ah, vejo que o sqlite não suporta adicionar restrições com alter table.

Feche este problema primeiro porque não tem uma solução perfeita para resolvê-lo.

foi descoberta uma solução para isso? faz quase 1 ano

sql tags deste formulário não impõem a restrição no mysql quando a tabela é criada:

type User {
  ...
  BillingAddressId  sql.NullInt64 `sql:"type:bigint REFERENCES address(id)"`
}

Também não funciona para mim. Quero dizer a coisa " type:int REFERENCES parentTable(id)".

Oi, Que tal fornecer a capacidade de definir referências estrangeiras na definição da estrutura.

Igual a:
db.Model(&User{}).AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")

Portanto, se quisermos definir a chave estrangeira, podemos tê-la em uma função que usa o esquema de struct e adiciona restrições por meio de instruções como abaixo antes de ser importada:
db.Model(&User{}).AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")

Este é um problema bastante sério, pois qualquer pessoa que use um ORM esperaria uma abstração adequada de chaves estrangeiras com restrição. Houve algum progresso desde a última atualização?

O seguinte funciona para mim com gorm v1.0 e PostgreSQL 9.6.3

type Address struct {
    ID string `gorm:"primary_key"`
}

type UserAddress struct {
    BillingAddressId string `sql:"type:varchar REFERENCES addresses(id)"`
}

Observe o uso do nome plural addresses ao definir a restrição de chave estrangeira.

Saída psql

> \dS user_addresses
           Table "public.user_addresses"
       Column       |       Type        | Modifiers 
--------------------+-------------------+-----------
 billing_address_id | character varying | 
Foreign-key constraints:
    "user_addresses_billing_address_id_fkey" FOREIGN KEY (billing_address_id) REFERENCES addresses(id)

Aqui está uma maneira que encontrei para fazê-lo. O processo não é automático e você deve chamar uma função manualmente, mas é uma boa solução sólida:

type User struct {
    gorm.Model
    Username string
    Email string
    Profiles []Profile       `gorm:"many2many:user_profiles;"`
    Groups []Group        `gorm:"many2many:user_groups;"`
}

type Profile struct {
    gorm.Model
    ProfileName string
}

type Group struct {
    gorm.Model
    GroupName string
}

func UserExample1(db *gorm.DB) {
    db.AutoMigrate(&User{})
    db.AutoMigrate(&Group{})
    db.AutoMigrate(&Profile{})

    Many2ManyFIndex(db, &User{}, &Profile{})
    Many2ManyFIndex(db, &User{}, &Group{})
}

func Many2ManyFIndex(db *gorm.DB, parentModel interface{}, childModel interface{}) {
    table1Accessor := ReduceModelToName(parentModel)
    table2Accessor := ReduceModelToName(childModel)

    table1Name := inflection.Plural(table1Accessor)
    table2Name := inflection.Plural(table2Accessor)

    joinTable := fmt.Sprintf("%s_%s", table1Accessor, table2Name)

    db.Table(joinTable).AddForeignKey(table1Accessor+"_id", table1Name+"(id)", "CASCADE", "CASCADE")
    db.Table(joinTable).AddForeignKey(table2Accessor+"_id", table2Name+"(id)", "CASCADE", "CASCADE")
    db.Table(joinTable).AddUniqueIndex(joinTable+"_unique", table1Accessor+"_id", table2Accessor+"_id")
}

func ReduceModelToName(model interface{}) string {
    value := reflect.ValueOf(model)
    if value.Kind() != reflect.Ptr {
        return ""
    }

    elem := value.Elem()
    t := elem.Type()
    rawName := t.Name()
    return gorm.ToDBName(rawName)
}

Isso definiria os índices estrangeiros para a tabela de junção e adicionaria um índice exclusivo à tabela de junção para que as junções duplicadas não sejam possíveis.

Uma solução ainda melhor é usar o método embutido "TableName(*db)"

type User struct {
   ID uint         `gorm:"primary_key;AUTO_INCREMENT"`
   Referrer *User
   ReferrerID *uint
}

func Migrate(db *gorm.DB) {
   db.AutoMigrate(&User{})
   userTableName := db.NewScope(&User{}).GetModelStruct().TableName(db)
   db.Model(&User{}).AddForeignKey("referrer_id", userTableName + "(id)", "RESTRICT", "RESTRICT")
}

@jinzhu @xming

por que não usar ALTER TABLE para as restrições? Afinal CREATE TABLE.

Para as duas estruturas acima, elas dependem uma da outra, e para postgres, você não pode criar nenhuma referência se a tabela referida não existir.

Corrija-me se estiver errado, mas esse problema pode ser abordado de diferentes ângulos, dependendo de qual back-end está sendo usado. Então, não seria possível inserir referências para tabelas ainda existentes em bancos de dados que o suportam (mysql, sqlite?) e voltar para alterar a tabela e adicionar as referências posteriormente para o postgres? Contanto que o usuário chame DB.AutoMigrate() com todos os seus modelos de uma vez, não deve ser um problema, eu acho.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

koalacxr picture koalacxr  ·  3Comentários

satb picture satb  ·  3Comentários

izouxv picture izouxv  ·  3Comentários

fieryorc picture fieryorc  ·  3Comentários

sredxny picture sredxny  ·  3Comentários