EF code How to use lambda expresssion to implement left join? - entity-framework

Sql:
select a.id, b.name
from a
left join b on a.id = b.id
I want to use lambda in EF to get the same result in this sql
Here is what I've done:
var list = entities.a
.GroupJoin(
entities.b,
a => a.id,
b => b.id,
(a, b) => new { a, b })
.Select(o => o)
.ToList();
Here Select(o => o), I just don't know how to get the same result in sql
select a.id, b.name

I think this article from MSDN would be a lot helpful...
http://msdn.microsoft.com/en-us/library/bb397895.aspx
This is a quote from above article, that might help...
var query = from person in people
join pet in pets on person equals pet.Owner into gj
from subpet in gj.DefaultIfEmpty()
select new { person.FirstName, PetName = (subpet == null ? String.Empty : subpet.Name) };
UPDATE:
As per your request, the Lambda Expression would be something like this...
.SelectMany(#a => #a.#a.b.DefaultIfEmpty(), (#a, joineda) => new {#a, joineda})
Not sure, if it is correct, but it is a starting point, at least...

Related

EF Core aggregation query

I use EF Core 5 and this query throws an exception. Id and EntityId are uniqueidentifiers in both tables.
query:
var records = await (from r in _dbContext.Set<MedicalRecord>()
join d in _dbContext.Set<Document>()
on r.Id equals d.EntityId into grouping
select new { r, DocumentCount = grouping.Count() }).ToListAsync();
here is an exception:
System.InvalidOperationException: The LINQ expression 'DbSet<MedicalRecord>()
.GroupJoin(
inner: DbSet<Document>(),
outerKeySelector: r => r.Id,
innerKeySelector: d => d.EntityId,
resultSelector: (r, grouping) => new {
r = r,
DocumentCount = grouping
.AsQueryable()
.Count()
})' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.
Thank you

Entity Framework join with a partially hardcoded "on" due to key value pair?

So I have this:
db.Table1
.Join(db.Table2, t1 => t1.id, t2 => t2.id, (t1, t2) => new { t1, t2 })
But what I need to do is join with an attribute value / key value table as well and for a specific attribute like:
SELECT * FROM Table1 t1
JOIN Table2 t2 ON t1.id = t2.id AND t2.attributeid = 123
How do I qualify that attributeid = 123 part?
A Where clause should suffice:
db.Table1
.Join(db.Table2, t1 => t1.id, t2 => t2.id, (t1, t2) => new { t1, t2 })
.Where(x => x.t2.attributeId == 123);
Ideally Table1 should have a navigation property to Table2: (either HasOne or HasMany)
Singular Table2:
var result = db.Table1
.Include(x => x.Table2)
.Where(x => x.Table2.AttributeId == 123);
or for a collection of Table2s...
var result = db.Table1
.Include(x => x.Table2s)
.Where(x => x.Table2s.Any(t2 => t2.AttributeId == 123);
which would return any Table1 containing a Table2 with that attribute...
or with a collection that you want to filter the Table2s:
var result = db.Table1
.Where(x => x.Table2s.Any(t2 => t2.AttributeId == 123)
.Select(x => new
{
Table1 = x,
FilteredTable2s = x.Table2s.Where(t2 => t2.AttributeId == 123).ToList()
});

Quill way of doing INSERT INTO ... SELECT FROM

I'm trying to translate simple INSERT INTO...SELECT FROM query into a quote in Quill. First of all, I am failing to find a built-in way to do this, so ended up trying out to use infix query
val rawQuery = quote { (secondTableValues: List[Int]) => {
infix"""
INSERT INTO my_table (second_table_id)
VALUES (
${secondTableValues.map(stid => (SELECT id FROM second_table where id = $stid)).mkString(",")}}
)
""".as[Insert[Any]]
}}
databaseClient.run(rawQuery(List(1,2,3)))
This however does not compile as Quill can't build Ast for the query.
What I ended up doing is have a raw query and not using quotes and run it with executeAction.
So two questions
How would you do INSERT INTO...SELECT FROM in a built-in way?
What is wrong with the infix version above?
import io.getquill._
val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal)
import ctx._
case class Table1(id: Int)
case class Table2(id: Int, name: String)
def contains(list: List[Int]) = {
//SELECT e.id,'hello' FROM Table1 e WHERE e.id IN (?)
val q = quote(
query[Table1].filter(e => liftQuery(list).contains(e.id)).map(e => Table2(e.id, "hello"))
)
//insert into table2 SELECT e.id, 'hello' FROM Table1 e WHERE e.id IN (?)
// `${..}` is expect ast , not string
quote(infix"insert into table2 ${q}".as[Insert[Any]])
}
// test
val list = List(1)
ctx.run(contains(list))

Group by in many-to-many join with Quill

I am trying to achieve with Quill what the following PostgreSQL query does:
select books.*, array_agg(authors.name) from books
join authors_books on(books.id = authors_books.book_id)
join authors on(authors.id = authors_books.author_id)
group by books.id
For now I have this in my Quill version:
val books = quote(querySchema[Book]("books"))
val authorsBooks = quote(querySchema[AuthorBook]("authors_books"))
val authors = quote(querySchema[Author]("authors"))
val q: db.Quoted[db.Query[(db.Query[Book], Seq[String])]] = quote{
books
.join(authorsBooks).on(_.id == _.book_id)
.join(authors).on(_._2.author_id == _.id)
.groupBy(_._1._1.id)
.map {
case (bId, q) => {
(q.map(_._1._1), unquote(q.map(_._2.name).arrayAgg))
}
}
}
How can I get rid of the nested query in the result (db.Query[Book]) and get a Book instead?
I might be a little bit rusty with SQL but are you sure that your query is valid? Particularly I find suspicious that you do select books.* while group by books.id i.e. you directly return fields that you didn't group by. And attempt to translate that wrong query directly is what makes things go wrong
One way to fix it is to do group by by all fields. Assuming Book is declared as:
case class Book(id: Int, name: String)
you can do
val qq: db.Quoted[db.Query[((Index, String), Seq[String])]] = quote {
books
.join(authorsBooks).on(_.id == _.book_id)
.join(authors).on(_._2.author_id == _.id)
.groupBy(r => (r._1._1.id, r._1._1.name))
.map {
case (bId, q) => {
// (Book.tupled(bId), unquote(q.map(_._2.name).arrayAgg)) // doesn't work
(bId, unquote(q.map(_._2.name).arrayAgg))
}
}
}
val res = db.run(qq).map(r => (Book.tupled(r._1), r._2))
Unfortunately it seems that you can't apply Book.tupled inside quote because you get error
The monad composition can't be expressed using applicative joins
but you can easily do it after db.run call to get back your Book.
Another option is to do group by just Book.id and then join the Book table again to get all the fields back. This might be actually cleaner and faster if there are many fields inside Book

Troublesome conversion of plain PostgreSQL query to Slick query

I have a plain PostgreSQL query I'm having trouble translating into slick query. I go stuck in syntax soup when using groupBy clause.
SELECT u.id AS investor_id,
u.account_type,
i.first_name,
issuer_user.display_name AS issuer_name,
p.legal_name AS product_name,
v.investment_date,
iaa.as_of AS CCO_approval_date,
v.starting_investment_amount,
v.maturity_date,
v.product_interest_rate,
v.product_term_length,
i.user_information_id,
v.id AS investment_id
FROM investors u
JOIN
( SELECT ipi.investor_id,
ipi.first_name,
ipi.user_information_id
FROM investor_personal_information ipi
JOIN
( SELECT investor_id,
MAX(id) AS Max_Id
FROM investor_personal_information
GROUP BY investor_id ) M ON ipi.investor_id = m.investor_id
AND ipi.id = m.Max_Id ) i ON u.id = i.investor_id
JOIN investments v ON u.id = v.investor_id
JOIN sub_products AS sp ON v.sub_product_id = sp.id
JOIN products AS p ON p.id = sp.product_id
JOIN company AS c ON c.id = p.company_id
JOIN issuers AS issuer ON issuer.id = c.issuer_id
JOIN users AS issuer_user ON issuer.owner = issuer_user.id
JOIN investment_admin_approvals AS iaa ON iaa.investment_id = v.id
ORDER BY i.first_name DESC;
I've started writing it
val query = {
val investorInfoQuery = (for {
i <- InvestorPersonalInformation
} yield (i)).groupBy {
_.investorId
}.map {
case (id, rest) => {
id -> rest.map(_.id).max
}
}
}
I know I've to create base queries into one big query and apply joins on them separately. Can anybody help guiding me or providing me some examples? Slick is hard.
Looks pretty simple to write. I am not going to help you write the whole query, I will just give you an example which you can follow to wrtie your query.
Lets say you had following structure and respective table queries defined as employees, emplayeePackages and employeeSalaryCredits
case class Employee(id: String, name: String)
case class EmployeePackage(id: String, employeeId: String, baseSalary: Double)
case class EmployeeSalaryCredit(id: String, employeeId: String, salaryCredited: Double, date: ZonedDateTime)
Now lets say you want all salary credits for all employees with employee's id, employee's name, base salary, actual credited salary and date of salary credit then your query will look like
val queryExample = employees
.join(employeePackages)
.on({ case (e, ep ) => e.id === ep.employeeId })
.join(employeeSalaryCredits)
.on({ case ((e, ep), esc) => e.id === esc.employeeId })
.map({ case ((e, ep), esc) =>
(e.id, e.name, ep.baseSalary, esc.salaryCredited, esc.date)
})