I have a User and Product entities which are defined as follows:
public class User {
Guid Id { get; set; }
Guid ParentId { get; set; }
ICollection<Product> PermittedProducts { get; set; }
ICollection<User> Children { get; set; }
}
public class Product {
int Id { get; set; }
string Name { get; set; }
ICollection<User> PermittedUsers { get; set; }
}
Conceptually, a Product has a collection of PermittedUsers - ie users that can purchase the product. Additionally, each User has a collection of PermittedProducts, as well as a collection of child users, who also have their own collection of PermittedProducts.
I need to run a query via a repository to return a list of products. The repository method and DTO are defined as:
public ICollection<ProductListDto> GetProductsForUser(Guid userId) {
// Linq query here
}
public class ProductListDto {
int Id { get; set; }
string Name { get; set; }
ICollection<User> Users { get; set; }
}
The repository method needs to take a Guid userId and retrieve the PermittedProducts for that User AND the PermittedProducts for the user's children.
For example, if a product is available for a user and his two children, then the ProductListDto would have all three users in it's users collection.
As a further example, if a product is not available for a user, but it is available for his children, then this would need to be returned as well.
Both the Product and User are available as aggregate roots, so I can use either a ProductRepository or UserRepository to query through, via EntityFramework's DbSet.
At the moment my repository method is in the UserRepository (but could move to the ProductRepository if the query is simpler) and looks like:
public ICollection<ProductListDto> GetProductsForUser(Guid userId) {
// Linq query here - Set is the EF DbSet<User>
var products = from u in
Set.Where(x => x.Id == userId) //.... NOT SURE ABOUT THE REST!
}
My problem is I cannot work out how to write the Linq query to achieve what I need to do!
EDIT
The answers so far don't address how to achieve the projection to the ProductListDto
How I would approach this is simply build a list of Id's from the parent UserId so this will contain the Parent UserId and all of it's ChildId's. From this list we can then select from Products where PermittedUsers contains one of these Id's. This is where you can get the product list from.
var childIds = DbContext.Users.Where(x => x.Id == userId).SelectMany(y => y.Children.Select(z => z.Id)).ToList();
childIds.Add(userId);
var products = DbContext.Products.Where(x => x.Users.SelectMany(y => childIds.Contains(y.Id))).ToList();
Try this
userRepository.Where(u => u.Id == userId && u.ParentId == userId)
.SelectMany(u => u.PermittedProducts)
.GroupBy(p => p.Id)
.Select(u => u.First());
Line explanation
- 1) Retrieves target user and his children
- 2) Select all products from user collection we get in first line
- 3- 4) Remove duplicates.
Note such linq can be translated into heavy sql query that can decrease performance. Maybe it is better to call ToList after lines (1, 2 or 3). Also it is possible to write your own SQL query, store them in sql server and call them from code by name.
Related
I am beginner and I am using a function which takes approximately 20 seconds to load a record - please is there any other, more efficient and faster way to execute this? How i can include guest id with guest name so that its works faster rather then use for each loop to assign guest name with guest id
Data.Tables.Booking
[Table("Bookings")]
public class Booking : ITrackable
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int GuestId { get; set; }
public int RoomId { get; set; }
public DateTime CheckInDateTime { get; set; }
public DateTime CheckOutDateTime { get; set; }
public decimal DailyPricePerBed { get; set; }
[StringLength(1000)]
public string Memo { get; set; }
public string PriceType { get; set; }
public bool Paid { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ChangedAt { get; set; }
public string? InvoiceType { get; set; }
}
Booking model
public class Booking : BaseModel
{
[Required]
public int Id { get; set; }
[Required(ErrorMessage = Constants.ERROR_REQUIRED)]
[Display(Name = "Gast")]
public int GuestId { get; set; }
[Required(ErrorMessage = Constants.ERROR_REQUIRED)]
[Display(Name = "Zimmer")]
public int RoomId { get; set; }
[Required(ErrorMessage = Constants.ERROR_REQUIRED)]
[Display(Name = "Check-In")]
public DateTime CheckInDateTime { get; set; }
[Required(ErrorMessage = Constants.ERROR_REQUIRED)]
[Display(Name = "Check-Out")]
public DateTime CheckOutDateTime { get; set; }
[Required(ErrorMessage = Constants.ERROR_REQUIRED)]
[Display(Name = "Tagespreis pro Bett")]
public decimal DailyPricePerBed { get; set; }
[Required(ErrorMessage = Constants.ERROR_REQUIRED)]
[Display(Name = "Price Type required")]
public string PriceType { get; set; }
[StringLength(1000, ErrorMessage = Constants.ERROR_MAX_LENGHT)]
[Display(Name = "Sonstiges")]
public string Memo { get; set; }
[Display(Name = "Bezahlt")]
public bool Paid { get; set; }
public List<Guest> Guests { get; set; }
public List<Room> Rooms { get; set; }
public string GuestName { get; set; }
public string TitleBooking { get; set; }
public bool Selected { get; set; }
public int RoomNumber { get; set; }
public RoomType RoomType { get; set; }
public string EncryptedRoomId { get; set; }
public string EncryptedPartnerId { get; set; }
public string? InvoiceType { get; set; }
}
}
public List<Booking> Load(int roomId)
{
var result = _context.Bookings
.Where(item => item.RoomId == roomId)
.Select(item => _mapper.Map<Booking>(item))
.ToList();
//its takes time here to load each guest name, if i remove
//this part
//its works fast but its showing guest ids on calendar, i want
//to show guest name
foreach (var booking in result)
{
if (_context.Guests.Any(o => o.Id == booking.GuestId)) // update
{
booking.GuestName = _guestRepository.GetName(booking.GuestId);
}
}
return result;
}
You code will be slow for two main reasons. Firstly you are selecting a set of Bookings then using Automapper to "Map" these across to copies of Booking entity classes. If Bookings contain navigation property references to other entities and you have Lazy Loading enabled, this is quite likely resulting in a LOT of lazy load hits as Mapper.Map "touches" each navigation property as it's iterating through the Bookings to copy the object graph across. You are then going and iterating over each booking to call to the GuestRepository to get the guest name, and that could also be of varying efficiency. For instance does the repository do something like:
return _context.Guests.Where(g => g.GuestId == guestId).Select(g => g.GuestName).Single();
or does it do something like:
var guest = _context.Guests.Single(g => g.GuestId == guestId);
return guest.GuestName;
The first runs an SQL Statement to retrieve one column for one row. The second reads all columns from Guest and builds a Guest entity for one row, just to return one value.
The first thing would be to ensure that you have navigation properties defined for all relationships, such as between Bookings and Guests.
From your example I'm guessing this code is part of a BookingRepository which has a dependency on a GuestRepository to get guest information. If there is one piece of advice I can give, it is to avoid the Generic Repository pattern in EF, or thinking of Repositories as serving individual entities. Instead, design Repositories to serve business needs, like a Controller in MVC. If I have a BookingController set up to serve everything to do with making/reviewing bookings, then I can have a BookingRepository to handle all Domain interactions for that Controller. Not just Booking entities, but everything needed for making/reviewing bookings.
The next thing is handling projection. Returning entities outside of the scope of a DbContext that reads them is generally not a great idea. Entities represent data domain. Views have their own concerns with regards to what data they need, and how they want to present it. They should have a purpose-built representation of the domain they are concerned with, a View Model.
So for instance if we have a Booking entity, it should be associated with a Room and a Guest. Each of these would be entities for their respective tables and linked by FKs within the Booking table. What the view is concerned with isn't everything in the Booking, Room, and Guest, just bits of details which can be flattened down from the respective tables and columns. For instance:
public class BookingViewModel
{
public int BookingId { get; set; }
public int RoomId { get; set; }
public int GuestId { get; set; }
public string RoomNumber { get; set;}
public string GuestName { get; set; }
}
Now when we fetch bookings for a given room, without diving into Repositories yet, just working with a DbContext and it's entities:
var bookings = _context.Bookings
.Where(b => b.RoomId == roomId)
.Select(b => new BookingViewModel
{
BookingId = b.BookingId,
RoomId = b.Room.RoomId,
GuestId = b.Guest.GuestId,
RoomNumber = b.Room.RoomNumber,
GuestName = b.Guest.LastName + ", " + b.Guest.FirstName
}).ToList();
With Automapper, we can configure mapping rules for translating a Booking and it's related structure into this BookingViewModel and the above can be simplified to something looking like:
var bookings = _context.Bookings
.Where(b => b.RoomId == roomId)
.ProjectTo<BookingViewModel>(mapperConfig)
.ToList();
Where "mapperConfig" is an instance of a MapperConfiguration set up with the rules to translate Booking -> BookingViewModel. This could be one global Config, or a config built as requested by a factory method.
The benefits with either Select or ProjectTo is that the projection goes directly to the SQL Query so the only data returned is what is needed to populate the view model. There are no risks of lazy loading surprises, or even worrying about tracked entities bogging down the DbContext.
When starting out I would get the hang of using the DbContext and projection without introducing a Repository pattern. The EF DbContext acts as both a Unit of Work and Repository in the sense, and trying to abstract that fact from your application can mean introducing significant performance and flexibility penalties.
For introducing repositories I would recommend either a Repository pattern and Unit of Work pattern that leverage IQueryable so that callers can project details as they need, or having repositories that abstract the domain (entities) into the needs of the consumer. (view models) IQueryable provides a lot of flexibility making Repositories easy to mock for testing, but are coupled to Entity Framework as consumers need to know that fact and manage the DbContext's Scope to use them effectively. Designing repositories that return ViewModels creates a cleaner boundary to isolate consumers from EF, but requires the Repository to have a larger footprint to accommodate all methods, variants, and concerns for the consumer(s). For instance supporting sorting, pagination, etc. Structuring Repositories to serve individual controllers can certainly help compared to repositories-per-entity that serve many controllers with different concerns.
Edit: If updating the result to a view model represents too big of a change, do keep these details in mind for future work because the approach your code base is using is highly inefficient. You Can mitigate the problem to a degree with some smaller changes including loading the entities detached and using Include to Eager-load the required relationships.
The first change would be moving the GuestName into a domain concern that the entity can resolve. Inside the Booking entity change:
public string GuestName { get; set; }
to:
private string _guestName = null;
public string GuestName
{
get
{
return _guestName ?? (_guestName = Guests.SingleOrDefault(g => g.GuestId == GuestId)?.Name;
}
set { _guestName = value; }
}
If you are using .Net Core 6 or 7 this can be simplified to:
private string? _guestName = null;
public string? GuestName
{
get => _guestName ??= Guests.SingleOrDefault(item => item.GuestId == GuestId)?.Name;
set => _guestName = value;
}
Rather than going to the repository for every guest record to get the name, let the entity go to its Guests collection. (if available) This is written as to not break existing code, so any code that "Sets" the guest name will still take precedence.
Alternatively you could use the setter like you are and just go to booking.Guests to get the applicable guest's name rather than going to the repository:
foreach (var booking in result)
{
booking.GuestName = booking.Guests.FirstOrDefault(item => item.GuestId == booking.GuestId)?.Name;
}
If the guest name needs to be formatted from a Guest First and Last Name:
foreach (var booking in result)
{
var mainGuest = booking.Guests.FirstOrDefault(item => item.GuestId == booking.GuestId);
if (mainGuest == null) continue;
booking.GuestName = $"{mainGuest.LastName}, {mainGuest.FirstName}";
}
For methods like this to work, whether using the property or getting the guest from the Guests collection and using the Setter, the entity must have the Guests collection eager loaded, which is the next step:
Eager load any required details your view is going to need about the guest, and detach them:
var result = _context.Bookings
.Include(item => item.Guests)
.Where(item => item.RoomId == roomId)
.AsNoTracking()
.ToList();
This will do something similar to your original code, but it will eager load the Bookings collection, and it will detach the resulting entities so that they won't be lazy-loadable proxies or have changes tracked by the DbContext. The issue with using Mapper.Map without AsNoTracking is that if your DbContext is set up to use lazy loading, the Mapper.Map call will go through each property, which when it hits a navigation property, trigger a lazy load. This will ensure that all data is mapped, but it is extremely slow and inefficient. The above example eager loads the Bookings collection. If there are other navigation properties your view will touch, these will very likely currently be #null now, so you will need to ensure they are eager loaded using Include as well.
Eager loading with Include does come with some performance issues when dealing with one-to-many relationships, especially eager loading several one-to-many relationships in that it produces Cartesian Products where the total volume of data grows by factors with the more relationships you load. This will typically be faster than lazy loading, but still represents a significant resource and performance cost. This is why projecting to a view model is recommended. You will still generate a Cartesian Product, but across far fewer fields as the projection only selects the fields you actually need rather than everything in the associated table. If you are using Eager Loading, eager load only what you know you will need, not everything, or you will soon be facing similar performance and memory bloat problems.
As comments have pointed out, you have not specified which of the parts of that function is consuming the time, nor is it clear what your model looks like.
However, I shall make two assumptions:
the problem is that you are looping through result and then hitting the database for each booking, perhaps twice.
the Guest name property is in the Guests entity / table.
If so, something like the following should speed it up by minimising the database calls to two for the whole function.
// Database Call 1
var result = _context.Bookings
.Where(item => item.RoomId == roomId)
.Select(item => _mapper.Map<Booking>(item))
.ToList();
// Get the Guest Ids used in the booking retrieved above
List<int> guestIds = result.Select(b => b.GuestId).Distinct().ToList();
// Get all Guests that are used - Database Call 2
List<Guest> guests = _context.Guests.Where(g => guestIds.Contains(g.Id)).ToList();
// Now put the name on the bookings
foreach (var booking in result)
{
Guest? guest = guests.SingleOrDefault(g => g.Id == booking.GuestId);
if (guest is not null)
{
booking.GuestName = guest.Name;
}
}
Alternatively, if you have a navigation property to guest from booking, then you can retrieve the guest along with the booking in a single database call like so:
var result = _context.Bookings
.Include(b => b.Guest)
.Where(b => b.RoomId == roomId)
... etc.
I want to pull data from multiple tables using LINQ in my .NET Core application. Here's an example:
public class Customer {
public Guid Id { get; set; }
public string Name { get; set; }
public DateTime Created { get; set; }
public HashSet<Transaction> Transactions { get; set; }
}
public class Transaction {
public Guid Id { get; set; }
public decimal Amount { get; set; }
public DateTime Created { get; set; }
public Guid CustomerId { get; set; }
public Customer Customer { get; set; }
}
These have a one-to-many relation in my solution. One customer has many transactions and one transaction has one customer. If I wanted to grab the 10 latest transactions and 10 lastest customers in one LINQ query, how would I do that? I've read that .Union() should be able to do it, but it won't work for me. Example:
var ids = _context
.Customers
.OrderByDescending(x => x.Created)
.Take(10)
.Select(x => x.Id)
.Union(_context
.Transactions
.OrderByDescending(x => x.Created)
.Take(10)
.Select(x => x.CustomerId)
)
.ToList();
This gives me two lists of type Guid, but they contain the same elements. Not sure if it's just me who understands this wrong, but it seems a bit weird. I am happy as long as it asks the database once.
You wrote:
I wanted to grab the 10 latest transactions and 10 latest customers in one LINQ query
It is a bit unclear what you want. I doubt that you want one sequence with a mix of Customers and Transactions. I guess that you want the 10 newest Customers, each with their last 10 Transactions?
I wonder why you would deviate from the entity framework code first conventions. If your class Customer represents a row in your database, then surely it doesn't have a HashSet<Transaction>?
A one-to-many of a Customer with his Transactions should be modeled as follows:
class Customer
{
public int Id {get; set;}
... // other properties
// every Customer has zero or more Transactions (one-to-many)
public virtual ICollection<Transaction> Transactions {get; set;}
}
class Transaction
{
public int Id {get; set;}
... // other properties
// every Transaction belongs to exactly one Customer, using foreign key
public int CustomerId {get; set;}
public virtual Customer Customer {get; set;}
}
public MyDbContext : DbContext
{
public DbSet<Customer> Customers {get; set;}
public DbSet<Transaction> Transactions {get; set;}
}
This is all that entity framework needs to know to detect the tables you want to create, to detect your one-to-many relationship, and to detect the primary keys and foreign keys. Only if you want different names of tables or columns, you'll need attributes and/or fluent API
The major differences between my classes and yours, is that the one-to-many relation is represented by virtual properties. The HashSet is an ICollection. After all, your Transactions table is a collection of rows, not a HashSet
In entity framework the columns of your tables are represented by non-virtual properties; the virtual properties represent the relations between the tables (one-to-many, many-to-many, ...)
Quite a lot of people tend to (group-)join tables, when they are using entity framework. However, life is much easier if you use the virtual properties
Back to your question
I want (some properties of) the 10 newest Customers, each with (several properties of) their 10 latest Transactions
var query = dbContext.Customers // from the collection of Customer
.OrderByDescending(customer => customer.Created) // order this by descending Creation date
.Select(customer => new // from every Customer select the
{ // following properties
// select only the properties you actually plan to use
Id = Customer.Id,
Created = Customer.Created,
Name = Customer.Name,
...
LatestTransactions = customer.Transactions // Order the customer's collection
.OrderBy(transaction => transaction.Created) // of Transactions
.Select(transaction => new // and select the properties
{
// again: select only the properties you plan to use
Id = transaction.Id,
Created = transaction.Created,
...
// not needed you know it equals Customer.Id
// CustomerId = transaction.CustomerId,
})
.Take(10) // take only the first 10 Transactions
.ToList(),
})
.Take(10); // take only the first 10 Customers
Entity framework knows the one-to-many relationship and recognizes that a group-join is needed for this.
One of the slower parts of your query is the transfer of the selected data from the DBMS to your local process. Hence it is wise to limit the selected data to the data you actually plan to use. If Customer with Id 4 has 1000 Transactions, it would be a waste to transfer the foreign key for every Transaction, because you know it has value 4.
If you really want to do the join yourself:
var query = dbContext.Customers // GroupJoin customers and Transactions
.GroupJoin(dbContext.Transactions,
customer => customer.Id, // from each Customer take the primary key
transaction => transaction.CustomerId, // from each Transaction take the foreign key
(customer, transactions) => new // take the customer with his matching transactions
{ // to make a new:
Id = customer.Id,
Created = customer.Created,
...
LatestTransactions = transactions
.OrderBy(transaction => transaction.Created)
.Select(transaction => new
{
Id = transaction.Id,
Created = transaction.Created,
...
})
.Take(10)
.ToList(),
})
.Take(10);
Try following. I models you database _context as a class just so I could test the syntax. Remember that one customer may map to more than one transaction. You may want to use GroupBy ID so you get 10 different customers.
class Program
{
static void Main(string[] args)
{
Context _context = new Context();
var ids = (from c in _context.customers
join t in _context.transactions on c.Id equals t.CustomerId
select new { c = c, t = t})
.OrderByDescending(x => x.c.Created)
.Take(10)
.ToList();
}
}
public class Context
{
public List<Customer> customers { get; set; }
public List<Transaction> transactions { get; set; }
}
public class Customer
{
public Guid Id { get; set; }
public string Name { get; set; }
public DateTime Created { get; set; }
public HashSet<Transaction> Transactions { get; set; }
}
public class Transaction
{
public Guid Id { get; set; }
public decimal Amount { get; set; }
public DateTime Created { get; set; }
public Guid CustomerId { get; set; }
public Customer Customer { get; set; }
}
You may want to try this instead :
var ids = (from c in _context.customers
join t in _context.transactions on c.Id equals t.CustomerId
select new { c = c, t = t})
.OrderByDescending(x => x.c.Created)
.GroupBy(x => x.c.Id)
.SelectMany(x => x.Take(10))
.ToList();
Eliminating the Join will speed up results. You always can get the customer info in another query.
var transactions = _context.transactions
.OrderByDescending(x => x.Created)
.GroupBy(x => x.CustomerId)
.Select(x => x.Take(10))
.ToList();
try this:
var customers = customerService.GetAll().OrderByDescending(c => c.Created).Take(10).ToList().AsQueryable();
var transactions = transactionService.GetAll().OrderByDescending(t => t.Created).Take(10).ToList().AsQueryable();
transactions = transactions.Where(t => customers.Any(c => c.CustomerId == t.Id));
How can the following be accomplished using LINQ
SELECT r.BrandID
FROM dbo.Items AS r
JOIN Brands AS d ON r.BrandID = d.BrandID
WHERE CategoryID IN (SELECT CategoryID
FROM dbo.Categories
WHERE Name = 'Bread - Bakery')
Code for Brand class:
public class Brand
{
public int BrandID { get; set; }
[DisplayName("Brand Name")]
public string Name { get; set; }
public virtual List<Category> Categories { get; set; }
public virtual List<Item> Items { get; set; }
}
Code for Item class:
public class Item
{
[Key]
public int ItemID { get; set; }
public virtual Category Category { get; set; }
public virtual Brand Brand { get; set; }
public int CategoryID { get; set; }
public int BrandID { get; set; }
}
code for Category class:
public class Category
{
[Key]
public int CategoryID { get; set; }
[DisplayName("Category Name")]
public virtual string Name { get; set; }
public virtual List<Brand> Brands { get; set; }
public virtual List<Item> Items { get; set; }
}
dbContext.Items
.Where(x => x.Category.Name.Equals("Bread - Bakery"))
.Select(x => x.BrandID);
I am not sure why you need to use below join. It seems that it is not needed (unless intentionally inner joined with brands to remove non-matching records from items)
JOIN Brands AS d ON r.BrandID = d.BrandID
Hm, pity you didn't write your requirements, now I have to guess what they are from your SQL code.
So you have a database with Brands, Items and Categories. Every Item has a Category, every Category can be used by zero or more Items: a one-to-many relation
Every Item is of a certain Brand, Every Brand can have zero or more items: also a straightforward one-to-many relation.
Finally every Brand has zero or more Categories, every Category has zero or more Brands: many-to-many
Now you take your collection of Items, you only want to keep those Items that have a Category with a Name that equals Bread - Bakery. From the remaining items you want all their BrandIds.
The requirement would be: "Give me the BrandIds of all Items that have a Category with a Name that equals 'Bread - Bakery`.
If you use entity framework, it is usually easier if you use the virtual ICollection instead of doing the join yourself. Entity framework knows the relations between the tables and will compose the proper joins for it.
var result = myDbContext.Items // from the collection of Items
.Where(item => item.Category.Name == "Bread - Bakery") // keep only those with a Category
// that has a Name equal to ...
.Select(item.BrandId); // from the remaining items select the BrandId
If you really want, and you can convince your project leader that entity framework can't be trusted to do the proper joins you can do the join yourself:
// get the sequence of categories that I'm interested in:
var breadBakeryCategories = myDbContext.Categories
.Where(category => category.Name == "Bread - Bakery");
// join the items with these categories
// and select the BrandIds
var requestedBrandIds= myDbContext.Items
.Join(breadBakeryCategories,
item => item.CategoryId, // from every Item take the CategoryId,
category => category.CategoryId, // from every Category take the CategoryId
(item, category) => item.BrandId); // when they match, select the BrandId
TODO: consider concatenating this into one big ugly LINQ statement.
Remark 1
You do realize that your result might have the same BrandIds several times, don't you?
If you don't want that, start with the Brands:
var result = myDbContext.Brands
.Where(brand => brand.Items.Select(item => item.Category.Name)
.Where(name => name == "Bread - Bakery")
.Any())
.Select(brand => brand.brandId);
In words: from the collection of Brands, keep only those Brands that have at least one Category with a name equal to "Bread - Bakery". From the remaining Brands select the BrandId.
** Remark 2 **
Why are your one-to-many Lists instead of ICollections? Are you sure that brand.Categories[4] has a proper meaning?
var result = myDbContext.Brands
.Where(brand => brand.Category[4].Name == "Bread - Bakeries");
Your compiler won't complain, but you'll get runtime errors.
Consider using virtual ICollection<...> for your one-to-many and many-to-many relations. This way you'll have exactly the functionality you expect with a database table, and your compiler will complain if you try to use functionality that can't be translated into SQL
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.
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();
my EF Poco classes structure as below and what I try to achieve is to get all CategoryProducts Including Products and ProductName but only ProductNames having languageid=1
I dont want to filter Root objects. I need not all productnames are loaded but only productname with languageid=1
I dont know how to achieve this. for example, I tried query below
var products = db.CategoryProduct.Include("Product.ProductName").
Where(p=>p.Product.ProductName.Any(a=>a.LanguageId==1)).ToList();
But this one filters all categoryProducts which have ProductName with languageid=1. this is not what I want because all products have names in 5 different languages. I just dont want to load eagerly for each product 5 times but only 1 time for languageid=1
public partial class CategoryProduct
{
[Key]
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
public partial class Product
{
public virtual ICollection<ProductName> ProductName { get; set; }
}
public partial class ProductName
{
public int ProductId { get; set; }
public int LanguageId { get; set; }
public string Name { get; set; }
public virtual Product Product { get; set; }
}
I'm afraid that using eager loading you can't filter the related entities unless you project your query in an anonymous type or a DTO:
var products = db.CategoryProduct.Include(c=>c.Product.ProductName)
.Select(c=> new CategoryProductDTO()
{
//...
ProductNames= c.Product.ProductName.Where(a=>a.LanguageId==1)
})
.ToList();
If you don't want to project your query and you want to load specific related entities, then I suggest you to use Explicit Loading:
var catproduct = db.CategoryProduct.Include(c=>c.Product).FirstOrDefault();// This is just an example, select the category product that you need to load the related entities
context.Entry(catproduct.Product)
.Collection(b => b.ProductName)
.Query()
.Where(pn => pn.LanguageId==1)
.Load();
But IMHO the first variant is the way to go
this is not easily doable, but something like the following may do it:
from cp in db.CategoryProduct.Include(x => x.Product)
from pn in cp.Product.ProductName.Where(x => x.LanguageId == 1).DefaultIfEmpty()
select new {
Cat = cp,
Name1 = pn.Name
}
then you have you product in Cat.Product, and the name in Name1.
The basic idea is to set a LEFT JOIN on ProductName.