Skip to content

Commit 0e48b9c

Browse files
committed
docs: projections and sql
1 parent 5c969a6 commit 0e48b9c

1 file changed

Lines changed: 71 additions & 0 deletions

File tree

README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,77 @@ var appliedQueryable = queryableRecipe.ApplyQueryKitFilter(input, config);
314314
var recipes = await appliedQueryable.ToListAsync();
315315
```
316316

317+
#### Filtering Raw SQL Projections
318+
319+
QueryKit can also be used with raw SQL queries via EF Core's `SqlQueryRaw`. This is particularly useful when you need to work with complex SQL queries that include joins, aggregations, or database-specific functions.
320+
321+
> 💡 You can use projections without using raw sql
322+
323+
Here's an example using a school domain:
324+
325+
```csharp
326+
public class StudentEnrollmentDto
327+
{
328+
public Guid Id { get; set; }
329+
public string StudentFirstName { get; set; }
330+
public string StudentLastName { get; set; }
331+
public string StudentFullName { get; set; }
332+
public int StudentAge { get; set; }
333+
public Guid CourseId { get; set; }
334+
public string CourseName { get; set; }
335+
public DateTime EnrolledOn { get; set; }
336+
public DateTime? CourseStartDate { get; set; }
337+
}
338+
339+
var sql =
340+
$"""
341+
SELECT
342+
e.id as "Id",
343+
COALESCE(s.first_name, '') as "StudentFirstName",
344+
COALESCE(s.last_name, '') as "StudentLastName",
345+
COALESCE(s.first_name, '') || ' ' || COALESCE(s.last_name, '') as "StudentFullName",
346+
s.age as "StudentAge",
347+
e.course_id as "CourseId",
348+
c.name as "CourseName",
349+
e.enrolled_on as "EnrolledOn",
350+
c.start_date as "CourseStartDate"
351+
FROM enrollments e
352+
LEFT JOIN students s ON e.student_id = s.id
353+
LEFT JOIN courses c ON e.course_id = c.id
354+
WHERE e.is_deleted = false
355+
""";
356+
357+
var projection = dbContext.Database.SqlQueryRaw<StudentEnrollmentDto>(sql);
358+
359+
var queryKitConfig = new QueryKitConfiguration(config =>
360+
{
361+
config.Property<StudentEnrollmentDto>(x => x.StudentFirstName).HasQueryName("studentFirstName");
362+
config.Property<StudentEnrollmentDto>(x => x.StudentLastName).HasQueryName("studentLastName");
363+
config.Property<StudentEnrollmentDto>(x => x.StudentFullName).HasQueryName("studentName");
364+
config.Property<StudentEnrollmentDto>(x => x.StudentAge).HasQueryName("studentAge");
365+
config.Property<StudentEnrollmentDto>(x => x.CourseId).HasQueryName("courseId");
366+
config.Property<StudentEnrollmentDto>(x => x.CourseName).HasQueryName("courseName");
367+
config.Property<StudentEnrollmentDto>(x => x.EnrolledOn).HasQueryName("enrolledOn");
368+
config.Property<StudentEnrollmentDto>(x => x.CourseStartDate).HasQueryName("courseStartDate");
369+
});
370+
371+
var queryKitData = new QueryKitData
372+
{
373+
Filters = request.QueryParameters.Filters,
374+
SortOrder = request.QueryParameters.SortOrder,
375+
Configuration = queryKitConfig
376+
};
377+
378+
var appliedCollection = projection.ApplyQueryKit(queryKitData);
379+
var results = await appliedCollection.ToListAsync();
380+
```
381+
382+
**Key Points:**
383+
- Raw SQL projections work seamlessly with QueryKit's filtering and sorting
384+
- You can include computed fields (like `StudentFullName`) and nested fields from joins (like `CourseStartDate`)
385+
- Property mappings allow you to use friendly query names that differ from the DTO property names
386+
- The resulting queryable can be further filtered and sorted by QueryKit before materializing to a list
387+
317388
#### Filtering Collections
318389

319390
You can also filter into collections with QueryKit by using most of the normal operators. For example, if I wanted to filter for recipes that only have an ingredient named `salt`, I could do something like this:

0 commit comments

Comments
 (0)