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();
Related
I have a scenario where I need to filter on a child list<object> ResponseIssues that is being included with the parent Question which is also a list<object>. For this example, I have 10 questions I'm pulling back from a table that I will always need to pull back whether or not there are ResponseIssues.
There appears to be a couple of problems with my query. The first problem is that the number of Questions goes from 10 to 1 since I currently only have one question associated with ResponseIssues. I need all questions to come back.
The second problem is that when I look closer at the ResponseIssues child list<object>. While I'm seeing records that are associated with the question, it's not filtering out rows by SuveryPeriod and RespondentByQuarterId. I'm expecting one row and I'm getting three rows where two of the rows where from a previous period. The same issue happens for the Responses child list.
Here's my current code below. Any ideas on how to restructure the query where it factors in the above issues and returns a Questions object and not something anonymous?
var question = await _dbContext.Questions
.Include(x => x.Responses)
.Include(x => x.ResponseIssues)
.Include(x => x.SurveySection)
.Include(x => x.Survey)
.Where(x => x.SurveyId == surveyId &&
x.Responses.Any(r => r.SiteUserId == siteUserId &&
r.SurveyPeriodId == surveyPeriodId &&
r.RespondentByQuarterId == 2
) &&
x.ResponseIssues.Any(ri => ri.SurveyPeriodId == surveyPeriodId &&
ri.RespondentByQuarterId == 2
))
.OrderBy(x => x.Position)
.ToListAsync();
I was able to do the above by breaking it out into three separate queries rather than just one. I still would be curious to know if someone in the community has a way to do it as one query though.
Anyway, here's my code below. I'm able to update the Questions Parent with the correct number of rows for both Responses and ResponseIssues along with returning all of the questions.
var question = await _dbContext.Questions
.Include(x => x.SurveySection)
.Include(x => x.Survey)
.Where(x => x.SurveyId == surveyId)
.OrderBy(x => x.Position)
.ToListAsync();
var responses = await _dbContext.Responses
.Where(x => x.SiteUserId == siteUserId &&
x.SurveyPeriodId == surveyPeriodId)
.ToListAsync();
var responseIssues = await _dbContext.ResponseIssues
.Where(x => x.SurveyPeriodId == surveyPeriodId &&
x.SiteUserId == siteUserId)
.ToListAsync();
foreach (var item in question)
{
var foundResponse = responses.Where(x => x.QuestionId == item.Id).ToList();
var foundResponseIssue = responseIssues.Where(x => x.QuestionId == item.Id).ToList();
if (foundResponse != null)
{
item.Responses = foundResponse;
}
if (foundResponseIssue != null)
{
item.ResponseIssues = foundResponseIssue;
}
}
As shown in the below code, the API will hit the database two times to perform two Linq Query. Can't I perform the action which I shown below by hitting the database only once?
var IsMailIdAlreadyExist = _Context.UserProfile.Any(e => e.Email == myModelUserProfile.Email);
var IsUserNameAlreadyExist = _Context.UserProfile.Any(x => x.Username == myModelUserProfile.Username);
In order to make one request to database you could first filter for only relevant values and then check again for specific values in the query result:
var selection = _Context.UserProfile
.Where(e => e.Email == myModelUserProfile.Email || e.Username == myModelUserProfile.Username)
.ToList();
var IsMailIdAlreadyExist = selection.Any(x => x.Email == myModelUserProfile.Email);
var IsUserNameAlreadyExist = selection.Any(x => x.Username == myModelUserProfile.Username);
The .ToList() call here will execute the query on database once and return relevant values
Start with
var matches = _Context
.UserProfile
.Where(e => e.Email == myModelUserProfile.Email)
.Select(e => false)
.Take(1)
.Concat(
_Context
.UserProfile
.Where(x => x.Username == myModelUserProfile.Username)
.Select(e => true)
.Take(1)
).ToList();
This gets enough information to distinguish between the four possibilities (no match, email match, username match, both match) with a single query that doesn't return more than two rows at most, and doesn't retrieve unused information. Hence about as small as such a query can be.
With this done:
bool isMailIdAlreadyExist = matches.Any(m => !m);
bool isUserNameAlreadyExist = matches.LastOrDefault();
It's possible with a little hack, which is grouping by a constant:
var presenceData = _Context.UserProfile.GroupBy(x => 0)
.Select(g => new
{
IsMailIdAlreadyExist = g.Any(x => x.Email == myModelUserProfile.Email),
IsUserNameAlreadyExist = g.Any(x => x.Username == myModelUserProfile.Username),
}).First();
The grouping gives you access to 1 group containing all UserProfiles that you can access as often as you want in one query.
Not that I would recommend it just like that. The code is not self-explanatory and to me it seems a premature optimization.
You can do it all in one line, using ValueTuple and LINQ's .Aggregate() method:
(IsMailIdAlreadyExist, IsUserNameAlreadyExist) = _context.UserProfile.Aggregate((Email:false, Username:false), (n, o) => (n.Email || (o.Email == myModelUserProfile.Email ? true : false), n.Username || (o.Username == myModelUserProfile.Username ? true : false)));
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.
I've tried some combinations but I just don't understand how to do the following:
Lets say I have tables Requests and RequestActivities. I need to get all request sorted by RequestActivity.TimeOfCreation in descending order but RequestActivity may be null.
List<DA.GeneralRequest> ongoingGeneralRequests = db.GeneralRequests
.Where(t => t.GeneralRequestStatusID != 3 && (t.SupervisorID == currentUserId || t.CreatorID == currentUserId || t.AssignedUsers.Any(au => au.UserID == currentUserId)))
.OrderByDescending(x => x.GeneralRequestActivities.OrderBy(ga => ga.GeneralRequestActivityDate).Last().GeneralRequestActivityDate) //gives exeption
.ThenBy(a => a.Deadline).ToList();
I'm not really familiar with LINQ-To-SQL but doesn't work MAX in this case?
.OrderByDescending(x => x.GeneralRequestActivities
.Max(ga => ga.GeneralRequestActivityDate))
.ThenBy(a => a.Deadline)
.ToList();
You need to first cache the ordering value, and then order by the date if it is not null, else by some default date you want:
List<DA.GeneralRequest> ongoingGeneralRequests = db.GeneralRequests
.Where(t => t.GeneralRequestStatusID != 3 && (t.SupervisorID == currentUserId || t.CreatorID == currentUserId || t.AssignedUsers.Any(au => au.UserID == currentUserId)))
.Select(x => new {
Value = x,
OrderByValue = x.GeneralRequestActivities
.OrderBy(ga => ga.GeneralRequestActivityDate)
.LastOrDefault()) // cache value
.OrderByDescending(x => x.OrderByValue != null ?
OrderByValue.GeneralRequestActivityDate
: some default value)
.ThenBy(a => a.Value.Deadline)
.Select(a => a.Value)
.ToList();
Note that you can't use Last() extension method on empty IEnumerable. This is why you get the exception:
InvalidOperationException : The source sequence is empty.
In this line:
x.GeneralRequestActivities.OrderBy(ga => ga.GeneralRequestActivityDate).Last()
x.GeneralRequestActivities is empty, so calling Last() on it result on the exception.
Instead, use the LastOrDefault() extension method, which return null if the IEnumerable is empty.
Return Value Type: TSource default (TSource) if the source sequence is
empty; otherwise, the last element in the IEnumerable.
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));
}