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.
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 quebrarTambé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
}
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
}
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.
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?