Many-To-Many relationships with EFCore and C# 5.0 : how to get the fields from the both of tables - entity-framework-core

I have the following database in PostgreSQL
EDIT: there is an Unique Key in PizzaTopping built with the fields Id_Pizza, Id_Topping
As you can see it's a Many-To-Many relationship.
When I ask Linqpad 6 to scaffold from my Database I have the following result:
The same result I have it when I use the EFCore Power Tools when I ask them to reverse engineer my database.
Reading from various sources, I've found, to ask EFCore to get the list of the toppings of the pizzas I should do something like that:
Pizzas.Where(p=>p.Description=="Margherita")
.Include(p=>p.PizzaToppings)
.ThenInclude(p=>p.IdToppingNavigation)
The query EFCore 5 returns this query:
SELECT P."Id_Pizza",
P."Description",
T0."Id_PizzaTopping",
T0."Id_Pizza",
T0."Id_Topping",
T0."Id_Topping0",
T0."Description"
FROM "Pizza" AS P
LEFT JOIN
(SELECT P0."Id_PizzaTopping",
P0."Id_Pizza",
P0."Id_Topping",
T."Id_Topping" AS "Id_Topping0",
T."Description"
FROM "PizzaTopping" AS P0
INNER JOIN "Topping" AS T ON P0."Id_Topping" = T."Id_Topping") AS T0 ON P."Id_Pizza" = T0."Id_Pizza"
WHERE P."Description" = 'Margherita'
ORDER BY P."Id_Pizza",
T0."Id_PizzaTopping",
T0."Id_Topping0"
Since I want to return a list with Pizza, topping like:
margherita, mozzarella
margherita, tomato sauce
marinara, garlic
marinara, tomato sauce
I tried with add .Select(topping=>topping.description) but it gets the pizza description.
So how can I take the toppings descriptions who they are in the topping table?
I tried to put a .Select() after the .ThenInclude() but I still see the Pizza entity and the p.PizzaToppings does not contain the property description of the topping table.

In this case you do not need Include but SelectMany with custom projection:
var query =
from p in Pizzas
from pt in p.PizzaToppings
select new
{
Pizza = p.Description,
Topping = pt.IdToppingNavigation.Description
}

Related

Pony ORM JOIN syntax

I've started using Pony and haven't actually understood how to use joins yet. In examples I saw when left_join() was used with two for clauses, but when I try to repeat it in my code, I get error like "collection was expected, got "for p in Pond""
Maybe somebody could explain how to use it or point me to docs page where it's already explained?
Let's say we have the following entities:
from pony import orm
db = orm.Database()
class Person(db.Entity):
id = orm.PrimaryKey(int, auto=True)
name = orm.Required(str)
age = orm.Required(int)
contacts = orm.Set('Contact')
class Contact(db.Entity):
id = orm.PrimaryKey(int, auto=True)
person = orm.Required('Person')
type = orm.Required(str)
value = orm.Required(str)
db.generate_mapping(create_tables=True)
with orm.db_session:
john = Person(name='John', age=23)
mary = Person(name='Mary', age=21)
mike = Person(name='Mike', age=18)
arthur = Person(name='Arthur', age=25)
john.contacts.create(type='mobile', value='1234567')
john.contacts.create(type='email', value='john#example.com')
mary.contacts.create(type='mobile', value='76543321')
mary.contacts.create(type='skype', value='mary123')
mike.contacts.create(type='mobile', value='2345678')
Now we want to print person name and a contact info for each person older then 20. There are several ways how we can do it.
The first way is when we explicitly state the join condition. This way is pretty verbose:
query = orm.select(
(p.name, c.value)
for p in Person for c in Contact
if p.age > 20 and c.person == p
)
query.show()
In this query we explicitly state the join condition: c.person == p. The query will show us the following result:
p.name|c.type|c.value
------+------+----------------
John |email |john#example.com
John |mobile|1234567
Mary |mobile|76543321
Mary |skype |mary123
As you can see, Arthur was not included into result, albeit his age is greater than 20. This is because this type of join is inner join, and the result includes only persons for which it was possible to find at least one contact.
The second way of joining is when we loop over collection attribute:
query = orm.select(
(p.name, c.value)
for p in Person for c in p.contacts
if p.age > 20
)
query.show()
This type of joins is used most often. It is very convenient, because we don't need to explicitly specify the join condition. The result of query is the same as before:
p.name|c.type|c.value
------+------+----------------
John |email |john#example.com
John |mobile|1234567
Mary |mobile|76543321
Mary |skype |mary123
Arthur is still not in the list for the same reason as before. If we want to include Arthur into result, we need to use other type of join, namely, left join:
query = orm.left_join(
(p.name, c.value)
for p in Person for c in p.contacts
if p.age > 20
)
query.show()
In that case the result of query includes Arthur with the None value instead of the phone number:
p.name|c.type|c.value
------+------+----------------
Arthur|None |None
John |email |john#example.com
John |mobile|1234567
Mary |mobile|76543321
Mary |skype |mary123
When you use left_join you need to loop over collection. In that case Pony adds the join condition into the ON section of LEFT JOIN clause of SQL command.
You cannot do explicit join as in the very first query if you use left_join, because in that case Pony does not know which condition is to put into ON section of LEFT JOIN clause.
Sometimes it may be useful to specify content of the ON section manually. Right now Pony does not support such queries, but this feature may be added in the future.
When using PonyORM in many cases it is possible to retrieve data without making joins at all. For example, you can write the following loop to print person name and phone number:
with db_session:
for p in Person.select(lambda p: p.age > 20):
print(p.name)
for c in p.contacts:
print(c.type, c.value)
In other ORMs this will lead to "N+1 query" problem, where the contacts of each person are retrieved by separate SQL query. Pony tries to automatically optimize queries to avoid "N+1 query" pattern.
In some cases the joins are implicit. For example, to find all contacts of a person whose name is started with 'M', you can write:
query = select(c for c in Contact if c.person.name.startswith('M'))
for c in query:
print(c.person.name, c.type, c.value)
In that case the Person table is joined implicitly, just because you perform attribute traversing from Contact to Person.

Select most reviewed courses starting from courses having at least 2 reviews

I'm using Flask-SQLAlchemy with PostgreSQL. I have the following two models:
class Course(db.Model):
id = db.Column(db.Integer, primary_key = True )
course_name =db.Column(db.String(120))
course_description = db.Column(db.Text)
course_reviews = db.relationship('Review', backref ='course', lazy ='dynamic')
class Review(db.Model):
__table_args__ = ( db.UniqueConstraint('course_id', 'user_id'), { } )
id = db.Column(db.Integer, primary_key = True )
review_date = db.Column(db.DateTime)#default=db.func.now()
review_comment = db.Column(db.Text)
rating = db.Column(db.SmallInteger)
course_id = db.Column(db.Integer, db.ForeignKey('course.id') )
user_id = db.Column(db.Integer, db.ForeignKey('user.id') )
I want to select the courses that are most reviewed starting with at least two reviews. The following SQLAlchemy query worked fine with SQlite:
most_rated_courses = db.session.query(models.Review, func.count(models.Review.course_id)).group_by(models.Review.course_id).\
having(func.count(models.Review.course_id) >1) \ .order_by(func.count(models.Review.course_id).desc()).all()
But when I switched to PostgreSQL in production it gives me the following error:
ProgrammingError: (ProgrammingError) column "review.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT review.id AS review_id, review.review_date AS review_...
^
'SELECT review.id AS review_id, review.review_date AS review_review_date, review.review_comment AS review_review_comment, review.rating AS review_rating, review.course_id AS review_course_id, review.user_id AS review_user_id, count(review.course_id) AS count_1 \nFROM review GROUP BY review.course_id \nHAVING count(review.course_id) > %(count_2)s ORDER BY count(review.course_id) DESC' {'count_2': 1}
I tried to fix the query by adding models.Review in the GROUP BY clause but it did not work:
most_rated_courses = db.session.query(models.Review, func.count(models.Review.course_id)).group_by(models.Review.course_id).\
having(func.count(models.Review.course_id) >1) \.order_by(func.count(models.Review.course_id).desc()).all()
Can anyone please help me with this issue. Thanks a lot
SQLite and MySQL both have the behavior that they allow a query that has aggregates (like count()) without applying GROUP BY to all other columns - which in terms of standard SQL is invalid, because if more than one row is present in that aggregated group, it has to pick the first one it sees for return, which is essentially random.
So your query for Review basically returns to you the first "Review" row for each distinct course id - like for course id 3, if you had seven "Review" rows, it's just choosing an essentially random "Review" row within the group of "course_id=3". I gather the answer you really want, "Course", is available here because you can take that semi-randomly selected Review object and just call ".course" on it, giving you the correct Course, but this is a backwards way to go.
But once you get on a proper database like Postgresql you need to use correct SQL. The data you need from the "review" table is just the course_id and the count, nothing else, so query just for that (first assume we don't actually need to display the counts, that's in a minute):
most_rated_course_ids = session.query(
Review.course_id,
).\
group_by(Review.course_id).\
having(func.count(Review.course_id) > 1).\
order_by(func.count(Review.course_id).desc()).\
all()
but that's not your Course object - you want to take that list of ids and apply it to the course table. We first need to keep our list of course ids as a SQL construct, instead of loading the data - that is, turn it into a derived table by converting the query into a subquery (change the word .all() to .subquery()):
most_rated_course_id_subquery = session.query(
Review.course_id,
).\
group_by(Review.course_id).\
having(func.count(Review.course_id) > 1).\
order_by(func.count(Review.course_id).desc()).\
subquery()
one simple way to link that to Course is to use an IN:
courses = session.query(Course).filter(
Course.id.in_(most_rated_course_id_subquery)).all()
but that's essentially going to throw away the "ORDER BY" you're looking for and also doesn't give us any nice way of actually reporting on those counts along with the course results. We need to have that count along with our Course so that we can report it and also order by it. For this we use a JOIN from the "course" table to our derived table. SQLAlchemy is smart enough to know to join on the "course_id" foreign key if we just call join():
courses = session.query(Course).join(most_rated_course_id_subquery).all()
then to get at the count, we need to add that to the columns returned by our subquery along with a label so we can refer to it:
most_rated_course_id_subquery = session.query(
Review.course_id,
func.count(Review.course_id).label("count")
).\
group_by(Review.course_id).\
having(func.count(Review.course_id) > 1).\
subquery()
courses = session.query(
Course, most_rated_course_id_subquery.c.count
).join(
most_rated_course_id_subquery
).order_by(
most_rated_course_id_subquery.c.count.desc()
).all()
A great article I like to point out to people about GROUP BY and this kind of query is SQL GROUP BY techniques which points out the common need for the "select from A join to (subquery of B with aggregate/GROUP BY)" pattern.

Fairly complex LINQ to Entities query

I have two entities, assume they are called Container and Record. They have a master-child relationship: a 'container' can hold many records.
The Records table in the database has the following columns:
Id
Date
Container_Id
RecordType_Id
The Record entity does not have any navigation properties that back reference the Container.
I am writing a LINQ query for my repository that will retrieve ONLY the records for a container that have the most recent date for each RecordType_Id. All older records should be ignored.
So if a container has say 5 records, one for each RecordType_Id, with the date 24/May/2011. But also has another 5 records for each RecordType_Id but with the date 20/May/2011. Then only the first 5 with the 24/May date will be retrieved and added to the collection in the container.
I came up with an SQL query that does what I need (but maybe there is some more efficient way?):
select t.*
from Records t
inner join (
select Container_Id, RecordType_Id, max(Date) AS MaxDate
from Records
group by Container_Id, RecordType_Id ) g
on t.Date = g.MaxDate
and t.Container_Id = g.Container_Id
and t.RecordType_Id = g.RecordType_Id
order by t.Container_Id
, t.RecordType_Id
, t.Date
However I am struggling to translate this into a proper LINQ query. EF is already generating a fairly large query all by itself just to load the entities, which makes me unsure of how much of this SQL query is actually relevant to the LINQ query.
Off the top of my head:
var q = from c in Container
from r in c.Records
group r by r.RecordType.RecordType_Id into g
select new
{
Container = c,
RecordType_Id = g.Key,
Records = from gr in g
let maxDate = g.Max(d => d.Date)
where gr.Date == maxDate
select gr
};
Try using LinqPad, it helps you test linq queries easily. Even against an existing EF model (which is in your project). Visit http://www.linqpad.net/

Many-to-many sorting in Entity Framework

My application has an Entity Framework model containing a many-to-many relationship like the following:
ProductGroup:
Scalar: Id, Name
Navigation: ProductGroupProduct
Product:
Scalar: Id, Sku, Description, etc.
Navigation: ProductGroupProduct
ProductGroupProduct:
Scalar: ProductGroupId, ProductId, Position
Navigation: Product, ProductGroup
Note how the intermediate table has a scalar property called Position that specifies the order in which a product should be displayed within a product group.
How would you write a LINQ query that returns a list of products in a given product group sorted by the Position property? If I was writing good ol' SQL I'd write something like this:
SELECT p.Id, p.Sku, p.Description
FROM Product p
INNER JOIN ProductGroupProduct pgp ON p.Id = pgp.ProductId
WHERE pgp.ProductGroupId = #MyProductGroupId
ORDER BY pgp.Position
But I can't figure the LINQ out.
Um, your SQL won't work, because there is no ProductGroup.Position
But I think you want:
var q = from pgp in Context.ProductGroupProducts
where pgp.ProductGroup.Id == id
orderby pgp.Position
select pgp.Product;

Entity Sql for a Many to Many relationship

Consider two tables Bill and Product with a many to many relationship. How do you get all the bills for a particular product using Entity Sql?
Something like this
SELECT B FROM [Container].Products as P
OUTER APPLY P.Bills AS B
WHERE P.ProductID == 1
will produce a row for each Bill
Another option is something like this:
SELECT P, (SELECT B FROM P.Bills)
FROM [Container].Products AS P
WHERE P.ProductID == 1
Which will produce a row for each matching Product (in this case just one)
and the second column in the row will include a nested result set containing the bills for that product.
Hope this helps
Alex
You need to use some linq like this;
...
using (YourEntities ye = new YourEntities())
{
Product myProduct = ye.Product.First(p => p.ProductId = idParameter);
var bills = myProduct.Bill.Load();
}
...
This assumes that you have used the entitiy framework to build a model for you data.
The bills variable will hold a collection of Bill objects that are related to your product object.
Hope it helps.