LINQ OrderBy on inner object's property - linq

Below are the hierarchies and the required function
public class Friend
{
public string Name { get; set; }
public List<Message> Messages { get; set; }
}
public class Message
{
public string Text { get; set; }
public DateTime Time { get; set; }
}
Now the required function:
public List<string> WhatsApp(List<Friend> friends)
{
throw new NotImplementedException();
}
I need to get the list of friend names in descending order of there message time stamp. Just like Whats App or any other IM for that matter.
I am getting a feeling that this can be done in 1 or 2 lines using LINQ but since I am new to LINQ, unable to drill down the problem.
Thanks in advance for the help.

If the idea is to order by the last (i.e. max) message timestamp, the following should do the job:
return friends.OrderByDescending(f => f.Messages.Max(m => (DateTime?)m.Time))
.Select(f => f.Name)
.ToList();
Casting to DateTime? is needed to avoid exception when there are no messages for some friend.
In general when you need to order the parent having multiple children by something based on children properties, it should be some aggregate value (like Sum, Count, Min, Max etc.).

Related

LINQ: how to construct an IQueryable<CustomMadeType> based on search criteria on the properties of the entity, including many-to-many-relationship

I struggled constructing and finding easily the correct info for defining the LINQ expressing for the criterion on the many-to-many, hence the Q&A.
Please modify/improve/correct/...
Basic situation:
.Net Entity Framework code first
We work with UnitOfWork repo's, but that's not really important here
We define a IQueryable<MyCustomMadeEntity> query
User submits his search criteria through a form, each criterion matching either directly a property of MyCustomMadeEntity, or a property of an another entity that has a many-to-many-relationship with MyCustomMadeEntity, e.g.:
public class MyCustomMadeEntity : BaseEntity
{
public string ArticleCode { get; set; }
public string Comment { get; set; }
public Guid? ColorId { get; set; }
public ArticleColor Color { get; set; }
public ICollection<ArticleStatus> ArticleStatuses { get; set; }
}
with
public class MyCustomMadeEntity : BaseEntity
{
// properties
Public LocationType LocationType { get; set; }
}
(In the case LocationType is a custom made enum
Construct a base IQueryable<Entity> expressing the db source and the linkage of the base-table with other tables (join’s etc)
IQueryable<CustomMadeEntity> query = _UnitOfWork. ustomMadeEntityRepository
.Queryable()
.Include(el => el.OtherEntity);
query = PerformAdvancedSearch(criteria, query);
2.Modify the query variable step by step checking of the individual search criteria
public static IQueryable<CustomMadeEntity> PerformAdvancedSearch(SearchRequestDto criteria, IQueryable< CustomMadeEntity > query)
{
if (!string.IsNullOrWhiteSpace(criteria.ArticleCode))
{
query = query.Where(el => el. ArticleCode == criteria. ArticleCode));
}
}
Additional query logic with contains, ><, …
if (something)
{
query = query.Where(el => el.ArticleStatuses.OrderByDescending(el => el.CreatedOn).Any() &&
el.ArticleStatuses.OrderByDescending(el => el.CreatedOn)
.FirstOrDefault().LocationType == criteria.LocationType);
}

How do you avoid Client Evaluation Errors in EF Core when Ordering by a Child property?

When executing the last line of the code below I hit a client evaluation error. How do I prevent this from happening?
IQueryable<Models.Data> dataIQ = _context.Data
.Include(d => d.Quotes).ThenInclude(q => q.Owner)
.Include(d => d.Location).ThenInclude(l => l.State)
//This works
dataIQ = dataIQ.OrderBy(d => d.Quotes.FirstOrDefault().QuoteName);
//This also works
dataIQ = dataIQ.OrderBy(d => d.Location.State.StateName);
//This throws the client evaluation error
dataIQ = dataIQ.OrderBy(d => d.Quotes.FirstOrDefault().Owner.OwnerName);
For reference, this is what the classes looks like:
public class Data
{
public int DataId { get; set; }
public string DataName { get; set; }
public int LocationId { get; set; }
public Models.Location Location { get; set; }
public IList<Models.Quote> Quotes { get; set; }
}
public class Quote
{
public int QuoteId { get; set; }
public string QuoteName { get; set; }
public int DataId { get; set; }
}
public class Location
{
public int LocationId { get; set; }
public string LocationName { get; set; }
public int StateId { get; set; }
public Models.State State { get; set; }
}
Error text:
InvalidOperationException: The LINQ expression '{QUERY TEXT}' 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 either AsEnumerable(), AsAsyncEnumerable(), ToList(), or
ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for
more information.
This is a continuation of the question asked in this post. The original issue was resolved, so breaking this into its own post
Hmm... one thing that seems to work is switching to the Max function. I think the problem is there's no translation to a SQL command for FirstOrDefault, but there is one for Max. I don't really understand why this works if you're accessing a property of the child and not a sub-child though. Interested to see if anyone has other suggestions for how to address this, or if I'm doing something dumb here
//changing to this seems to work
dataIQ = dataIQ.OrderBy(d => d.Quotes.Max(q => q.Owner.OwnerName));
I also don't expect this to work if there's a many-to-one relationship, but right now mine is one-to-one. If anyone has suggestions on what to do for many-to-one I'd be interested to hear those as well!

Fetch single value with linq projection query without using FirstOrDefualt

I am using Entity Framework and this is my view model:
public class UserDetailsModel:CityModel
{
public int Id { get; set; }
public string Fullname { get; set; }
}
public class VendorInCategoryModel
{
public int CategoryId { get; set; }
public int VendorId { get; set; }
public virtual CategoryMasterModel CategoryMaster { get; set; }
public virtual UserDetailsModel UserDetails { get; set; }
}
public class CategoryMasterModel
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
}
This is my query to fetch vendor details along with category details of particular vendor say v001:
UserDetailsModel workerDetails = context.UserDetails.
Where(d => d.Id == _vendorId).
Select(d => new UserDetailsModel
{
Id = d.Id,
Fullname = d.Fullname,
CategoryId = d.VendorInCategory.Select(v => v.CategoryId).FirstOrDefault(),
}).SingleOrDefault();
Here I have used FirstOrDefault to fetch categoryId (that is single value)
But I don't want to use FirstOrDefault as I have used in so many queries and it is giving me wrong output in some cases. So that the reason why I don't want to use FirstOrDefault.
When I have written SingleOrDefualt in place of FirstOrDefault it is throwing me error
that use FirstOrDefault.
So how to overcome this? Can anybody please help me?
It looks like maybe your outer select is capable of returning multiple results (e.g. if there are more than one UserDetailsModel with the same Id). If it returns multiple results then your call to .SingleOrDefault() will throw an exception as it expects only a single result or no results. See LINQ: When to use SingleOrDefault vs. FirstOrDefault() with filtering criteria for more details.

Breeze where query for many-to-many with intermediate entity in EF

I am using EF6, WebApi2, AngularJS and BreezeJs.
I have the following entities:
Person
{
public string Name { get; set;}
public virtual ICollection<GenericProfileCountry> Countries { get; protected set; }
}
public class GenericProfileCountry
{
public string PersonId{ get; set; }
public virtual Person Person{ get; set; }
public string CountryIso { get; set; }
public virtual Country Country { get; set; }
}
public class Country
{
public string Iso { get; set; }
public string Name { get; set; }
}
Now I have a query that brings all Persons through breeze as follows:
return zEntityQuery.from('Contacts').expand('Profile, Countries')
.orderBy(contactOrderBy)
.toType(entityName)
.using(self.manager).execute()
.to$q(querySucceeded, self._queryFailed);
What I would like to do is perform a where statement on the above query with criteria that are on the intermediate entity. So say I want to bring only contacts that their first country (a person can have multiple countries) iso code is equal to 'GB'.
In Linq it would be something like Contacts.Where(contact => contact.Countries.First().CountryIso == 'GB')
Could something similar be expressed in the where(predicate) of breeze? I thought of going the other way (start from the intermediate table and filter from there), but not sure if that is the correct approach.
You can achieve that by creating a predicate with the keyword any or all
.where('Countries','any','CountryIso','eq','GB')
In case you want to create a predicate on grand children : BreezeJS Predicates on 2nd level expanded entities
Edit
If you want to get the first contacts whose countries Isos start with 'GB', you can achieve that by:
Jay's suggestion.
using Linq at Breeze controller:
public IQueryable<Person> ContactsWithFilteredCountryIso(string CountryIso)
{
return _contextProvider.Context.Persons.Where(p => p.Countries.First().CountryIso== CountryIso);
}
Then on the client:
return zEntityQuery.from('Contacts')
.withParameters({ CountryIso: "GB"})
.expand('Profile, Countries')
.orderBy(contactOrderBy)
.toType(entityName)
.using(self.manager).execute()
.to$q(querySucceeded, self._queryFailed);
Writing a select projection on countries with bringing up Contacts can be implemented by issuing a Breeze query on countries and expanding contact:
return zEntityQuery.from('Countries').expand('Contact')
.select('Country.name')
.where('CountryIso','eq','GB')
.orderBy(contactOrderBy)
.toType(entityName)
.using(self.manager).execute()
.to$q(querySucceeded, self._queryFailed);

Linq Method syntax for retrieving single nested child entity

I wonder if I could get some help with the following. I'm retrieving set of data as follows using EF.
var booking = this.GetDbSet<Booking>().Include(c => c.BookingProducts.Select(d => d.Product.PrinterProducts.Select(e => e.ProductPrices))).Single(c => c.BookingId == bookingId)
Within a PrinterProduct there is a foreign key PrinterId for an additional entity Printer. With the Booking Entity I also have PrinterId also linked by foreign key to the additonal entity Printer.
What I'm hoping to do is retrieve only the PrinterProduct relating to the PrinterId held in the booking entity rather that all the PrinterProducts as in my code. I've tried to use Join but have tied myself in knots!
Grateful for any help!
Edit:
Object structure:
public class Booking
{
public Guid BookingId { get; set; }
public string BookingName { get; set; }
public Printer Printer { get; set; }
public IEnumerable<BookingProduct> BookingProducts { get; set; }
}
public class BookingProduct
{
public int BookingProductId { get; set; }
public Booking Booking { get; set; }
public Product Product { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public IEnumerable<PrinterProduct> PrinterProducts { get; set; }
}
public class PrinterProduct
{
public int PrinterProductId { get; set; }
public Product Product { get; set; }
public Printer Printer { get; set; }
public IEnumerable<ProductPrice> ProductPrices { get; set; }
}
public class ProductPrice
{
public int ProductPriceId { get; set; }
public PrinterProduct PrinterProduct { get; set; }
public decimal Price { get; set; }
}
public class Printer
{
public int PrinterId { get; set; }
public string PrinterName { get; set; }
public IEnumerable<Booking> Bookings { get; set; }
public IEnumerable<PrinterProduct> PrinterProducts { get; set; }
}
Given the newly added class structures in your question, I hope I can clear it up now.
From what I see, Bookings and Products have a many-to-many relation (where BookingProduct is used as the connection). The same is true for Product and Printer (where PrinterProduct is used as the connection).
From what I understand, you are trying to get from a singular Booking item to a singular PrinterProduct. I don't see any efficient way to do this without introducing the possibility of inconsistency with your data. You're expecting some Lists to return you one result. If it's only one result, why is it a List in the first place?
You have a single Booking. You take its BookingProducts. Now you have many items.
You take the Product from each individual BookingProduct. If all BookingProducts have the same product, you're in luck and will only have a List<Product> with a single Product in it. However, there is nothing stopping the system from return many different products, so we are to assume that you now hold a List of several Products
From each Product in the list, you now take all of its PrinterProducts. You now hold many PrinterProducts of many Products.
As you see, you end up with a whole list of items, not just the singular entity you're expecting.
Bookings, Products and Printers are all connected to eachother individually, like a triangle. I have seen scenarios where that is correct, but nine times out of ten, this is not what you want; and only leads to possible data inconsistency.
Look at it this way: Is it ever possible for the Product to have a Printer other than the Printer that is already related to the Booking? If not, then why would you have two relations? This only introduces the possibility that Booking.Printer is not the same as PrinterProduct.Printer.
Your relational model is set up to yield many results, but I think you expect a single result in some places. I would suggest taking another look at your data model because it does not reflect the types of operation you wish to perform on it. Change the many-to-many relations to one-to-many where applicable, and you should be able to traverse your data model in a more logical fashion, akin to the answer I provided in my previous answer.
If you've set up navigational properties, you can just browse to it:
var myBooking = ... //A single Booking, don't know how you retrieve it in your case.
var myPrinter = myBooking.Printer; //the Printer that is related to the Booking.
var myPrintproducts = myPrinter.PrintProducts; //The products that are related to the printer.
You don't need to keep nesting select statements, that only creates unnecessary confusion and overhead cost.
Keep in mind that you need to do this while in scope of the db context. Every time you try to access a property, EF will fill in the needed variables from the database. As long as there is an open db connection, it works.
Edit
If you really need to optimize this, you can use a Select statement. But you only need a single one. For example:
var myPrintproducts = db.Bookings.Single( x => x.ID == some_id_variable ).Select( x => x.Printer.PrintProducts);
But unless you have a very strict performance requirement, it seems better for code readability to just browse to it.

Resources