How to find minimum value in a collection using linq? - c#

I have a collection where i need to find an item with lowest price if more than 1 found the by default any should be selected and it's isPriceSelected property need to set false.
I am trying something like this.
lstBtn.Where(p => p.CategoryID == btnObj.CategoryID &&
p.IsSelected == true && p.IsPriceApplied == true)
.ToList()
.Min(m=>m.Price)

Just the select the property that you want the minimum from:
var minimumPrice = lstBtn
.Where(p => p.CategoryID == btnObj.CategoryID && p.IsSelected && p.IsPriceApplied)
.Min(p => p.Price);
If you actually want to find the item with the lowest price you need to order the collection:
var itemWithMinimumPrice = lstBtn
.OrderBy(p => p.Price)
.FirstOrDefault(p => p.CategoryID == btnObj.CategoryID && p.IsSelected && p.IsPriceApplied);
or this, could be more efficient:
var itemWithMinimumPrice = lstBtn
.Where(p => p.CategoryID == btnObj.CategoryID && p.IsSelected && p.IsPriceApplied)
.OrderBy(p => p.Price)
.FirstOrDefault();
Enumerable.FirstOrDefault returns one item or null if no item matches the predicate.

You can try something like this:
var result = lstBtn
.Where(p => p.CategoryID == btnObj.CategoryID && p.IsSelected && p.IsPriceApplied)
.OrderBy(p => p.Price)
.First();
This will first find all items which have the specified CategoryID, IsSelected, and IsPriceApplied all set to true, then sort items by Price, and return the first item with the lowest price.

Out of the box, linq can only return the actual value with Min and Max methods.
You can use a good project morelinq https://code.google.com/p/morelinq/wiki/OperatorsOverview
It has the method you need. For myself, I find this project having too many methods, so I simply cut and paste only needed from its sources.
With morelinq your code should look like:
lstBtn.Where(p => p.CategoryID == btnObj.CategoryID && p.IsSelected == true && p.IsPriceApplied==true).MinBy(m=>m.Price)
Another approach, if you also need to get all duplicates:
var lowestPriceProducts = lstBtn.Where(p => p.CategoryID == btnObj.CategoryID)
.GroupBy(p => p.Price, new { p.Price, Product = p})
.OrderByDescending(x => x.Price)
.First()
.Select(x => x.Product)
.ToList()
This query will return you a list (with only one item if there are no duplicate prices) of products with minimal price. Then you can do anything with it.

Related

Merge 2 linq queries, using result from 1 to continue search

Currently I've written this:
public Task<List<BulkmailAnnouncementModel>> GetBulkmailAnnouncementsByPackageTrackingId(string packageTrackingIdentification)
{
var announcement = await Context.BulkmailAnnouncement
.Include(x => x.Tarras)
.Include(x => x.PackageTrackingIdentifications)
.Where(x => x.PackageTrackingIdentifications.Any(y => y.Value == packageTrackingIdentification) &&
x.AuditReportIndicator != true)
.FirstAsync();
return await Context.BulkmailAnnouncement
.Include(x => x.Tarras)
.Include(x => x.PackageTrackingIdentifications)
.Where(x => x.CustomerOrderId == announcement.CustomerOrderId && x.CustomerPartyAccountId == announcement.CustomerPartyAccountId)
.ToListAsync();
}
I'm trying to combine these 2 statements into 1. I want to query the database just once, because having the result of the first query load in memory and then using it for the 2nd is inefficient.
The result I want is a list of BulkmailAnnouncements which all have the same customerOrderId and CustomerPartyId.
In order to find out which customerOrderId and CustomerPartyId, I first need to find 1 bulkmailAnnouncement which has a packageTrackingIdentification equal to the parameter of the method. then use that bulkmailAnnouncement to find all other announcements with the same customerOrderId and CustomerPartyId.
Alas you didn't specify your requirement in words. So I have to look at your code to see what you want.
Apparently you have an input parameter packageTrackingIdentification and a queryable sequence of BulkmailAnnouncements.
Every BulkmailAnnouncement has a Boolean AuditReportIndicator and zero or more PackageTrackingIdentifications.
Your first query, fetches (several properties of) the first BulkmailAnnouncement that has a true AuditReportIndicator and at least one PackageTrackingIdentification that equals your input parameter packageTrackingIdentification
Your 2nd query fetches (several properties of) all BulkmailAnnouncements that have certain properties (CustomerOrderId and CustomerPartyAccountId) equal to the ones you fetched from your first query.
You could group all BulkmailAnnouncements into groups that have the same certain properties. So you know that all BulkmailAnnouncement in a group have the same values for certain properties
Keep the first group that has at least one BulkmailAnnouncement with a true AuditReportIndicator and at least one PackageTrackingIdentification that equals your input parameter packageTrackingIdentification.
Note that the latter part is equal to what you would have selected in your first query. The result is one group. You know that all BulkMailAnnouncements in this group have the same value for certain properties, and there are no BulkMailAnnouncements with the same values for certain properties that are in other groups. Besides you know that the group also contains at least one BulkMailAnnouncement with true AuditReportIndicator and at least one PackageTrackingIdentification that equals your input parameter packageTrackingIdentification. Hence your requested result equals the items you want.
var result = BulkmailAnnouncements.GroupBy(
// Key: make groups with same "certain properties"
announcement => new
{
CustomerOrderId,
CustomerPartyAccountId,
})
// Result: groups of BulkMailAnnouncements with equal "certain properties"
// keep only those groups that have at least one BulkMailAnnouncement
// that has both a true AuditReportIndicator and at least one
// PackageTrackingIdentification that equals packageTrackingIdentification
.Where(groupOfBulkMailAnnouncements =>
groupOfBulkMailAnnouncements.Any(bulkMailAnnouncement =>
bulkMailAnnouncement.AuditReportIndicator &&
bulkmailAnnouncment.PackageTrackingIdentifications
.Any(packageTrackingId == packageTrakcingIdentification)))
// from the remaining groups, take the first or default
.FirstOrDefault(); // or use async version
Other way is to use Concat() method like below
var announcement = await Context.BulkmailAnnouncement
.Where(x => x.PackageTrackingIdentifications.Any(y => y.Value == packageTrackingIdentification) &&
x.AuditReportIndicator != true)
.FirstAsync();
var announcement2 = await Context.BulkmailAnnouncement
.Include(x => x.Tarras)
.Include(x => x.PackageTrackingIdentifications)
.Where(x => x.CustomerOrderId == announcement.CustomerOrderId && x.CustomerPartyAccountId == announcement.CustomerPartyAccountId)
.ToListAsync();
var resultAnnouncement = announcement.Concat(announcement2);
return resultAnnouncement;
Move the Where predicate to the second query:
return await Context.BulkmailAnnouncement
.Include(x => x.Tarras)
.Include(x => x.PackageTrackingIdentifications)
.Where(x => x.PackageTrackingIdentifications != null
&& x.PackageTrackingIdentifications.Any(y => y.Value == packageTrackingIdentification)
&& x.AuditReportIndicator != true)
.ToListAsync();
Or call Include on the first:
var announcement = await Context.BulkmailAnnouncement
.Include(x => x.Tarras)
.Include(x => x.PackageTrackingIdentifications)
.Where(x => x.PackageTrackingIdentifications != null
&& x.PackageTrackingIdentifications.Any(y => y.Value == packageTrackingIdentification)
&& x.AuditReportIndicator != true)
.FirstAsync();
The result i want is a list of BulkmailAnnouncements which all have the same customerOrderId and CustomerPartyId. In order to find out which customerOrderId and CustomerPartyId i first need to find 1 bulkmailAnnouncement which has a packageTrackingIdentification equal to the parameter of the method. then use that bulkmailAnnouncement to find all other announcements with the same customerOrderId and CustomerPartyId.
Then group the results by the CustomerOrderId and CustomerPartyAccountId properties and take the first group, e.g.:
return (await Context.BulkmailAnnouncement
.Include(x => x.Tarras)
.Include(x => x.PackageTrackingIdentifications)
.Where(x => x.PackageTrackingIdentifications != null
&& x.PackageTrackingIdentifications.Any(y => y.Value == packageTrackingIdentification)
&& x.AuditReportIndicator != true)
.GroupBy(x => new { x.CustomerOrderId, x.CustomerPartyAccountId })
.FirstAsync()).ToArray();

C# Linq multi-word search

I have the following, where searchby is a string
var products = db.Products
.Where(p => p.Category.Name == category
&& p.Active == true
&& (searchby == null || (searchby != null && p.Keywords.Contains(searchby))))
.Include(p => p.Category)
.OrderBy(p => p.Description)
.ThenBy(p => p.Name);
and would like to change it to allow searchby to contain multiple words which would filter the results to records where Keywords contains all of the words in searchby.
Thanks in advance
You can use another collection and either Enumerable.All(not sure if supported by your LINQ provider) or !Enumerable.Any:
List<string> searchby = ... (empty if there is no filter)
var products = db.Products
.Where(p => p.Category.Name == category
&& p.Active == true
&& !searchby.Any(s => !p.Keywords.Contains(s)))
.Include(p => p.Category)
.OrderBy(p => p.Description)
.ThenBy(p => p.Name);
If supported this is more readable:
&& searchby.All(s => p.Keywords.Contains(s)))
This answer assumes, that searchby is either null or an Array.
Since contains only checks for 1 item, you need to find a way to check for all items within searchby. The Enumerable.All-Method comes to mind. Here is the relevant part:
searchby.All(searchItem => p.Keywords.Contains(searchItem))
Source

LinQ to get the latest group of records satisfying a condition

I have below stated 2 tables:
now I want to get the set of Child Table objects for whichever their parent table entries are latest(wr.r.t lastmodified). It should be something like....
List<Child_Table> List = ChildsList.Where(x=>x.name =="pqr" && status == "done")
.Select(x=>x.Parent.lastmodified == recent record).....ToList();
You can use GroupBy on the date, then OrderByDescending on the Key then take the First followed by SelectMany to flatten the results.
var result = ChildsList.Where(x => x.name == "pqr" && x.status == "done")
.GroupBy(x => x.Parent.lastmodified)
.OrderByDescending(g => g.Key)
.First()
.SelectMany(g => g)
.ToList();
You could use a join to accomplish it:
var results = children
.Join(parents.OrderByDescending(p => p.lastmodified).Take(1),
c => c.parent_id,
p => p.id,
(c, p) => c)
.Where(x => x.name == "pqr" && x.status == "done")
.ToList();

Selecting earliest date using linq/lambda

I have following expression
var list = techlinks.GetItems().Where(p => p.Status == 1).ToList();
I want to change this so that I want to select the earliest date value for example
var list = techlinks.GetItems().Where(p =>p.Date is earliest && p.Status == 1).ToList();
Please let me know what to insert for p.Date is earliest
Thanks
you can use OrderBy or OrderByDescending() to sort them on Date this way:
var list = techlinks.GetItems()
.Where(p => p.Status == 1)
.OrderBy(x=>x.Date).First(); // this will give oldest date
and:
var list = techlinks.GetItems()
.Where(p => p.Status == 1)
.OrderByDescending(x=>x.Date).First(); // this will give latest date
Here's another way.
var list=techlinks.GetItems()
.Where(p=> p.Status==1)
.Min(d => d.Date)
.Single();
If there might be multiple items all with the earliest date:
var list = techlinks.GetItems()
.Where(p => p.Status == 1)
.OrderBy(x=>x.Date)
.GroupBy(x => x.Date)
.First()
.ToList()
Student student = _context.Set<Student>()
.Where(p => p.StudentID == ID.Value)
.OrderBy(p => p.AddedDate)
.FirstOrDefault();
It slightly depends on what GetItems() of techLinks does, but something like that should work:
var list = techlinks.GetItems().Where(p => p.Date == techlinks.GetItems().Min(x => x.Date) && p.Status == 1).ToList();
If GetItems() method actually hits the database, you can store its result first and use it twice:
var allItems = techLinks.GetItems();
var list = allItems.Where(p => p.Date == allItems.Min(x => x.Date) && p.Status == 1).ToList();
If you only want 1 you could go with
techlinks.GetItems().Where(p => p.Status == 1).OrderBy(c => c.Date).FirstOrDefault();
otherwise I'd break it up into two statements
var date = techlinks.Min(c=>c.Date);
techlinks.GetItems().Where(p => p.Status == 1 && c.Date == date).ToList();
also be aware of how your dates are inserted, DateTime.Now will add time components so might have to do something gnarly like this
techlinks.GetItems().Where(p => p.Status == 1 && c.Date.Year == date.Year && c.Date.Month == date.Month && c.Date.Day == date.Day).ToList();

Linq to SQL / C#: How to Order By using property in cross reference table with lambda expressions

I am trying to order a list of products based on the zindex property of the cross reference table with the category table (in this case called 'Chassis'), but I get the following error:
Cannot order by type 'System.Collections.Generic.IEnumerable`1[System.Int32]'.
The following is the method I am using:
public IQueryable<E_Product> Product_GetList_ByChassisId(int chassisId)
{
return dc.E_Products
.Where(x => x.Deleted == false)
.Where(x => x.Published == true)
.Where(x => x.E_Product_Chassis
.Any(c => c.ChassisId == chassisId && c.Deleted == false))
.OrderBy(x => x.E_Product_Chassis.Select(c => c.Zindex));
}
I understand the .Select method returns an IEnumerable, but being a many-to-many relationship, x.E_Product_Chassis does not allow simple selection of its properties (e.g. x.E_Product_Chassis.Zindex).
Any help would be very appreciated...
FirstOrDefault(), Min(), Max() -- use one of these functions to select the appropriate z-index out of the set.
public IQueryable<E_Product> Product_GetList_ByChassisId(int chassisId)
{
return dc.E_Products
.Where(x => x.Deleted == false)
.Where(x => x.Published == true)
.Where(x => x.E_Product_Chassis
.Any(c => c.ChassisId == chassisId && c.Deleted == false))
.OrderBy(x => x.E_Product_Chassis.Min(c => c.Zindex));
}

Categories

Resources