How to select properties of simulated many-to-many in EFCore with single SQL generated query

c# entity-framework-core

Question

EFCore does not support many-to-many relationships without creating a linking entity. I need to efficiently select a subset of properties from the 'other end' of the one-to-many-to-one relationship.

I'd swear this would have an answer already but haven't found it.

With these Models:

public class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public Author Author { get; set; }
    public ICollection<BookCategory> BookCategories { get; set; }
} 

public class Category
{
    public int CategoryId { get; set; }
    public string CategoryName { get; set; }
    public string ExtraProperties {get; set; }
    public ICollection<BookCategory> BookCategories { get; set; }
}

public class BookCategory
{
    public int BookId { get; set; }
    public Book Book { get; set; }
    public int CategoryId { get; set; }
    public Category Category { get; set; }
}

This question is an extension of a similar, but different question titled, "Select specific properties from include ones in entity framework core"

I am looking for a query that returns a List<string> categoryNames of the Categories of the Book.

This nested select, using "projection" results in multiple SQL Queries:

var result= await _ctx.Books
    .Where(x => x.BookId == id)
    .Select(x => x.BookCategorys
        .Select(y => y.Category.CategoryName )
        .ToList())                
    .FirstOrDefaultAsync();

Any solution with .Include(x => x.BookCategory).ThenInclude(x => Category) will load all the data form the Server before applying the select.

Is there any query that meets the following criteria?:

  • Only generates 1 SQL query
  • Does not load the entire linking entity and or the entire navigation property 2 'hops' in.
  • Returns only List<string> of CategoryNames.

I infer from this Entity Framework Core generates two select queries for one-to-many relationship, it's not possible.

1
2
4/3/2018 1:28:54 PM

Accepted Answer

In general, you cannot control the generated SQL and how many SQL queries are executed by ORM. And EF Core at the time of writing (version 2.0.2) is known to produce N + 1 queries when the query contains collection projection. This is fixed in the next 2.1 release, but still will generate and execute at least 2 queries.

But every rule has exceptions. Since you want to return only a single related collection projection, you can simply use SelectMany instead of the original Select + FirstOrDefault construct. These are equivalent to this scenario, and EF Core is not smart enough to treat the later the same way as the former. Which is understandable counting the fact how many other cases need to be considered. The good thing is that rewriting the LINQ query this way produces the desired single SQL query translation:

var result = await _ctx.Books
    .Where(x => x.BookId == id)
    .SelectMany(x => x.BookCategorys
        .Select(y => y.Category.CategoryName))
    .ToListAsync();
4
4/3/2018 12:58:51 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow