gorm many to one returns empty - go

I want to use gorm the use a many to one relationship in my project.
My structs are like this:
type Book struct {
ID uint
Title string
Subtitle string
Chapters []Chapter `gorm:"foreignkey:BookID;association_foreignkey:ID"`
}
}
// TableName is book
func (Book) TableName() string {
return "book"
}
// Chapter of books
type Chapter struct {
ID uint
BookID string
Chapter string
}
What I want is to get chapters of a book by using this command:book.chapters.
I use the following codes to get books and chapters:
var book models.Book
db.First(&book, "id = ?", 4)
fmt.Println(book.ID, book.Chapters) // returns []
when I query chapters with book_id = 4, I get 11 results:
var chapters []models.Chapter
db.Find(&chapters, "book_id = ? ", 4)
fmt.Println("len(chapters) = ", len(chapters)) // len(chapters) =11
and when I set db.LogMode(true) and see what happens in the code I see that querying book only queries the books not joining that with chapters:
SELECT * FROM "book" WHERE (id = 4) ORDER BY "book"."id" ASC LIMIT 1
Is there something that I missed from the documentation? how should I make the book struct the get non-empty book.chapters.

Your problem is probably in Preloading: http://gorm.io/docs/preload.html#Preload
Try the following:
var book models.Book
db.Preload("Chapters").First(&book, "id = ?", 4)
fmt.Println(book.ID, book.Chapters)

Related

GORM: Select fields from two tables while joining

I have products table which has one-to-many relationship with items and brands tables. brands also have one-to-many relationship with items.
I was trying to query products and group them based on product_id and brand_id and it seems to work fine.
The only problem I have right now is that I can't map these fields brandId and BrandName, They always nil. But querying the raw SQL statement it generates the desired result.
Here are my models. Some fields have been omitted for simplicity.
type Brand struct {
ID int `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"index;not null;type:varchar(50);default:null"`
ProductID int `json:"productId"`
Product *Product `json:"product" gorm:"foreignKey:ProductID;"`
}
type Product struct {
ID int `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"index;not null;type:varchar(50);default:null"`
StoreID *int `json:"storeId"`
Store *Store `json:"store" gorm:"foreignKey:StoreID;constraint:OnUpdate:RESTRICT,OnDelete:RESTRICT;"`
BrandID *int `json:"brandId" gorm:"-"` //SEE THIS
BrandName *string `json:"brandName" gorm:"-"` //SEE THIS TOO
Brands []*Brand `json:"brands" gorm:"constraint:OnUpdate:CASCADE,OnDelete:RESTRICT;"`
}
type Item struct {
ID int `json:"id" gorm:"primaryKey"`
Quantity int `json:"quantity" gorm:"type:integer;not null;unsigned;"`
ProductID int `json:"productId"`
Product *Product `json:"product" gorm:"foreignKey:ProductID;not null;constraint:OnUpdate:RESTRICT,OnDelete:CASCADE;"`
BrandID *int `json:"brandId"`
Brand *Brand `json:"brand" gorm:"foreignKey:BrandID;constraint:OnUpdate:RESTRICT,OnDelete:CASCADE;"`
}
var products []*model.Product
var result *gorm.DB
query := DB.Table("products").
Where(&model.Product{StoreID: &StoreID}).
Joins("INNER JOIN items ON items.product_id = products.id").
Joins("LEFT JOIN brands ON brands.id = items.brand_id").
Where("items.quantity > 0").
Group("products.id, brands.id").
Select("products.*,brands.id AS brand_id, brands.name AS brand_name")
if err := query.Find(&products).Error; err != nil {
panic(err)
}
//Here results contains no brand_id nor brand_name
fmt.Printf("result %+v\n", products)
Here is raw SQL generated by gorm. And work as expected outside gorm
SELECT products.*,brands.id AS brand_id, brands.name AS brand_name
FROM "products"
INNER JOIN items ON items.product_id = products.id
LEFT JOIN brands ON brands.id = items.brand_id
WHERE "products"."store_id" = 2 AND items.quantity > 0
GROUP BY products.id, brands.id
First solution that comes in mind is to use -> instead of - gorm tag.
Like this
BrandID : Int #goTag(key: "gorm", value: "->")
BrandName : String #goTag(key: "gorm", value: "->")
The only drawback with this is that it saves these fields on database.
Any alternative?
has_many
type Brand struct {
ID int `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"index;not null;type:varchar(50);default:null"`
ProductID int `json:"productId"`
}
type Product struct {
ID int `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"index;not null;type:varchar(50);default:null"`
StoreID *int `json:"storeId"`
Store *Store `json:"store" gorm:"foreignKey:StoreID;constraint:OnUpdate:RESTRICT,OnDelete:RESTRICT;"`
Brands []*Brand `json:"brands" gorm:"constraint:OnUpdate:CASCADE,OnDelete:RESTRICT;"`
}
you shouold read BrandId and BrandName in model-Brand but model-Product
Product have many Brands, so it have many BrandID/BrandName, why you defiene just a BrandNameor BrandID?

How to query a many2many relationship with a Where clause on the association with go-gorm?

I have the following entities:
type User struct {
ID string
Name string
Groups []Groups `gorm:"many2many:users_groups"`
}
type Groups struct {
ID string
Name string
}
I know I can preload the groups using
var users []Users
db.Preload("Groups").Find(&users)
And I can also filter Users using
var users []Users
db.Preload("Groups").Where("name IN ?", []string{"name1", "name2"}).Find(&users)
This will bring all the User that have name equals "name1" or "name2"
But I cannot filter Users based on Groups
var users []Users
db.Preload("Groups", "name IN ?", []string{"groupname"}).Find(&users)
I expect it to bring all Users that have Group Name equal "groupname"
How can I achieve this using database only? (my database is big and I cannot load all users into memory and filter it in the application)
I opened an issue on gorm repository as well
You may fetch users by group names very easily:
db.Preload("Groups").Where("id IN (SELECT user_id FROM users_groups WHERE name IN ?)", []string{"name1", "name2"})
Or, a more strict way:
db.Where("id IN (?)", db.Table("users_groups").
Select("user_id").
Where("name IN ?", []string{"name1", "name2"}),
)
I had the same problem and I solve it with using Join.
Probably it's late for your problem but this could help folks when they have the same problem.
solution for your case would be :
var users []Users
db.Preload("Groups").
Joins("inner join user_groups ug on ug.user_id = user.id ").
Joins("inner join groups g on g.id= ug.user_id ").
Where("g.name IN ?", []string{"name1", "name2"}).Find(&users)
ref: https://gorm.io/docs/query.html
you can use:
db.Preload("Groups", "name IN (?)", []string{"name1", "name2"}).Find(&users)
(edited) this is also just filter the Groups, not the users (by there groups)
Actually I did came up with a solution of querying a gorm model containing 3-4 many 2 many relationships with filters for main model as well as filters for fields inside association.
Here filters type is map[string]string
noOfAssociationFilters stores how many associations we have found as for the first association our col name will be id (field in Lawyer Model) and for other nested queries it will be lawyer_id (field name in associations).
var lawyers []models.Lawyer
var noOfAssociationFilters int = 0
var statement string = "SELECT * FROM lawyers WHERE 1=1"
if filters["experience"] != "" {
statement += " AND `experience` = " + filters["experience"]
}
if filters["location_id"] != "" {
statement += " AND `location_id` = " + filters["location_id"]
}
if filters["gender"] != "" {
statement += " AND `gender` = \"" + filters["gender"] + "\""
}
if filters["practice_area_id"] != "" {
colName := func() string {
if noOfAssociationFilters == 0 {
return " `id`"
} else {
return " `lawyer_id`"
}
}()
statement += " AND" + colName + " IN ( SELECT `lawyer_id` FROM lawyer_practice_areas WHERE practice_area_id = " + filters["practice_area_id"]
noOfAssociationFilters += 1
}
if filters["court_id"] != "" {
colName := func() string {
if noOfAssociationFilters == 0 {
return "`id`"
} else {
return "`lawyer_id`"
}
}()
statement += " AND" + colName + " IN ( SELECT `lawyer_id` FROM lawyer_courts WHERE court_id = " + filters["court_id"]
noOfAssociationFilters += 1
}
if filters["language_id"] != "" {
colName := func() string {
if noOfAssociationFilters == 0 {
return "`id`"
} else {
return "`lawyer_id`"
}
}()
statement += " AND" + colName + " IN ( SELECT `lawyer_id` FROM lawyer_languages WHERE language_id = " + filters["language_id"]
noOfAssociationFilters += 1
}
for i := 0; i < noOfAssociationFilters; i++ {
statement += ")"
}
statement += ";"
fmt.Println(statement)
result := db.Raw(statement).Find(&lawyers)
fmt.Println("RowsAffected", result.RowsAffected)
I have tested the solution and it was working fine for me. It generates the complete statement you need in the end based upon your filter requirements. If you pass a filter then it generates a statement for that.
It generates statements like these:-
SELECT * FROM lawyers WHERE 1=1 AND experience = 5 AND location_id = 4 AND gender = "female" AND id IN ( SELECT lawyer_id FROM lawyer_practice_areas WHERE practice_area_id = 3 ANDlawyer_id IN ( SELECT lawyer_id FROM lawyer_courts WHERE court_id = 3));
SELECT * FROM lawyers WHERE 1=1 AND experience = 5 AND location_id = 4 AND gender = "female";
By this logic we can generate nested queries and get required data for m2m relations.
For reference this was my Lawyer Model
type Lawyer struct {
ID uint `gorm:"primaryKey" json:"id"`
FirstName string `gorm:"type:varchar(100) not null" json:"first_name"`
LastName string `gorm:"type:varchar(100) not null" json:"last_name"`
FullName string `gorm:"->;type:text GENERATED ALWAYS AS (concat(first_name,' ',last_name)) VIRTUAL;" json:"full_name"`
LocationID uint `gorm:"not null" json:"location_id"`
Location Location `gorm:"foreignKey:location_id" json:"location"`
Email string `gorm:"unique;not null" json:"email"`
Phone string `gorm:"type:varchar(100);not null" json:"phone"`
Password string `gorm:"type:varchar(100);not null" json:"password"`
ImageURL string `gorm:"type:text" json:"image_url"`
Education string `gorm:"not null" json:"education"`
Experience uint `gorm:"not null" json:"experience"`
Verified bool `gorm:"not null" json:"verified"`
Gender string `gorm:"not null" json:"gender"`
Reviews []Review `gorm:"foreignKey:LawyerID" json:"reviews"`
Courts []Court `gorm:"many2many:lawyer_courts" json:"courts"`
Languages []Language `gorm:"many2many:lawyer_languages" json:"languages"`
PracticeAreas []LawyerPracticeArea `gorm:"foreignKey:LawyerID" json:"practice_areas"` }
And one of the many2many association was of this type
type LawyerPracticeArea struct {
ID uint `gorm:"primaryKey" json:"id"`
LawyerID uint `gorm:"not null" json:"lawyer_id"`
Lawyer Lawyer `gorm:"foreignKey:LawyerID" json:"lawyer"`
PracticeAreaID uint `gorm:"not null" json:"practice_area_id"`
PracticeArea PracticeArea `gorm:"foreignKey:PracticeAreaID" json:"practice_area"`
Charge int `gorm:"" json:"charge"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` }
Upvote if you found this helpful :) !!

Querying many to many graphql and sqlite

I'm trying to create an Angular7 + graphql + sqlite game app and i'm stuck.
I've 3 db tables, one for games, other for genres and a third one as a relational table between games and genres.
What I want to do on graphql is basically this join:
SELECT G.*, GN.genre FROM Games AS G
LEFT JOIN Game_Genre AS GG ON G.id = GG.game_id
LEFT JOIN Genre AS GN ON GG.genre_id = GN.id
WHERE G.id={GAMEID}
expected result
I managed to create queries to list all games and individual games, but I can't make any queries to list the games with their genres.
Here's the schema I'm using:
const schema = buildSchema(`
type Query {
games(offset:Int = 0, limit:Int = 100): [Games]
game(id:ID!): Games
}
type Games {
id: ID
game_art: String
game_title: String
game_serial: String
game_region: String
release_year: Int
players: Int
developer: String
publisher: String
}
type Genre {
id: ID
genre: String
}
type Game_Genre {
id: Int
game_id: Games
genre_id: Genre
}`);
const root = {
games: args => {
return query(
`SELECT * FROM Games LIMIT ${args.offset}, ${args.limit}`,
false
);
},
game: args => {
return query(`SELECT * FROM Games WHERE id='${args.id}'`, true);
}};
Thank you in advance

Return a column value with the input ID

I was trying to return a column value in a table called with its ID .
public String PenTypes(int?id, Pen pen)
{
int num;
var query = from d in db.Pens
where d.ID == 1
select d.Type;
num=Convert.ToInt(query);
return num;
I have no clue as to where i'm going wrong. I do know its simple, but I'm really new to using Entity Framework. Any Help would be appreciated.
I suggest you to use DbSet<TEntity>.Find method if you want to get entity by id:
var pen = db.Pens.Find(id);
if (pen == null)
// handle case when pen is not found
return pen.Type; // if type is string, then you possibly need to parse it
Or you can use FirstOrDefault/SingleOrDefault:
var pen = db.Pens.FirstOrDefault(p => p.ID == id);
Your current code has several problems:
query will have type of IQueryable<T> (where T is type of pen.Type property). This value cannot be converted to integer
id should not be nullable if you are searching by primary key
if num is integer, then return type of method should be int instead of string

Linq "reporting" question with list properties

I have an entity object that contains a list property. I'd like to expand the list values to the right. Being new to LINQ, I'm not sure how to do this. I could strongly type an object, but then I'd have to know the count/values at compile time and I'd like to make it more dynamic.
The output that I'm wanting is something like:
Name Demo1 Demo2 Demo3
Person Name1 TX TX
Person Name2 TX OK
Person Name3 TX TX OK
Main Class
public Main()
{
List<Event> events = new List<Event>();
events.Add(new Event()
{
EventDate = DateTime.Now,
EventLocation = new Location() { State = "TX" },
EventName = "Demo1"
});
events.Add(new Event()
{
EventDate = DateTime.Now,
EventLocation = events[0].EventLocation,
EventName = "Demo2"
});
events.Add(new Event()
{
EventDate = DateTime.Now,
EventLocation = new Location() { State = "OK" },
EventName = "Demo3"
});
List<Person> people = new List<Person>();
Person person1 = new Person();
person1.Name = "Person Name1";
person1.Events.Add(events[0]);
person1.Events.Add(events[1]);
Person person2 = new Person();
person2.Name = "Person Name2";
person2.Events.Add(events[0]);
person2.Events.Add(events[2]);
Person person3 = new Person();
person3.Name = "Person Name3";
person3.Events.Add(events[0]);
person3.Events.Add(events[1]);
person3.Events.Add(events[2]);
people.Add(person1);
people.Add(person2);
people.Add(person3);
}
It depends on whether you want to run the query in memory or in databse. In any case, you'll need to return a list with the "dynamic" part of the results, because you cannot dynamically generate members of anonymous types (and working with them would be difficult).
In memory (as in your example), you can write the following query:
// Find names of all events (for all people)
var allNames =
(from p in people
from e in p.Events
select e.EventName).Distinct();
// Find events for every person
var res =
from p in people
let known = p.Events.ToDictionary(e => e.EventName)
select new {
p.Name,
Events = allNames.Select(n =>
known.ContainsKey(n)?known[n].EventLocation:"N/A")
};
The first query gets names of all events (we use it later to find a value for all event for every person). The second query iterates over all people. It first creates dictionary with events (for fast lookup in memory) and then iterates over all event names and tries to find them in the dictionary (returning "N/A" if it is not found).

Resources