How to create a LINQ request - c#

I can't create a LINQ request.
I have a base request:
var result = from i in _dbContext.Users
where i.ID != CurrentUserID &&
//i.UserType.UserTypeID == (from a in _dbContext.UserTypes where a.UsersSelectMeetingCriteria.Any(p => p.ID == CurrentUserID) select a.UserTypeID).FirstOrDefault() &&
i.Services.Any(p => p.UsersSelectMeetingCriteria.Any(k => k.ID == CurrentUserID)) &&
i.GeographicalAreas.Any(p=>p.UsersSelectMeetingCriteria.Any(o=>o.ID == CurrentUserID)) &&
i.MultiplyItems.Any(r => (r.UsersSelectMeetingCriteria.Any(q => q.ID == CurrentUserID) && r.ItemType == MultiplyItemKeys.USER_TYPE)) &&
i.MultiplyItems.Any(s => (s.UsersSelectMeetingCriteria.Any(q => q.ID == CurrentUserID) && s.ItemType == MultiplyItemKeys.COMPANY_INVOLVED)
)
select new DataTable.UserModel()
{ ... };
But I need 4 level search. If all 4 where is ok then this is first level of users, if only 3 (but 4th is NOT) - level #2, if 2 - yes, 2 - no then level #3, if only one match - level #4. How to do it?

Untested, and a rather wild stab in the dark, but maybe this will point you in the right direction:
var result = from i in _dbContext.Users
let check1 = i.Services.Any(p => p.UsersSelectMeetingCriteria.Any(k => k.ID == CurrentUserID))
let check2 = i.GeographicalAreas.Any(p=>p.UsersSelectMeetingCriteria.Any(o=>o.ID == CurrentUserID))
let check3 = i.MultiplyItems.Any(r => (r.UsersSelectMeetingCriteria.Any(q => q.ID == CurrentUserID) && r.ItemType == MultiplyItemKeys.USER_TYPE))
let check4 = i.MultiplyItems.Any(s => (s.UsersSelectMeetingCriteria.Any(q => q.ID == CurrentUserID) && s.ItemType == MultiplyItemKeys.COMPANY_INVOLVED))
let level = 5 - (check1 ? 1 : 0) - (check2 ? 1 : 0) - (check3 ? 1 : 0) - (check4 ? 1 : 0)
where i.ID != CurrentUserID && level <= 4
select new {i, level};
What this does is performs your checks independently then subtracts the number of succeeded checks from 5. Thus if three succeed and one fails, you will have 5-3 = "level 2".
Note, if I am actually on the right track here, this looks like a giant mess and an even bigger hack. I'd be inclined move the logic to a stored procedure or try to simplify it somewhere else in the data model.
Note 2, I've also dutifully ignored your commented code.
Note 3, I'm sure there is a better way to do this if I were to know your data model and reasoning behind the query.

Related

Entity Framework Core: Group by using calculated field

I'm trying to write a query. What I have so far is shown below.
var x = from d in DbContext.StorageDetails
let state = (d.ReleaseDate.HasValue && d.ReleaseDate < date) || (d.TakeOrPayEndDate.HasValue && d.TakeOrPayEndDate < date) ?
StorageState.Closed :
(d.RailcarNumber == null || d.RailcarNumber == string.Empty) ?
d.TakeOrPayStartDate.HasValue ? StorageState.TakeOrPay : StorageState.Open :
(d.ArrivalDate.HasValue && d.ArrivalDate <= date) ? StorageState.Filled : StorageState.EnRoute
group d by new
{
d.CustomerId,
d.Customer.Name,
d.LocationId,
d.Location.City,
d.Location.State
} into g
select new
{
// ...
};
The part that's giving me trouble is that I want to include the calculated state value with each item. I don't want to group by this value but I want to be able to sum it.
// Note: This is after my group statement
select new
{
// state is a let variable and not part of x!
TotalOpen = g.Sum(x => x.state == StorageState.Open),
TotalClosed = g.Sum(x => x.state == StorageState.Closed),
// Etc.
};
Is there a way to do this? I don't seem able to select my own set of columns prior to group by. How can I insert this calculated field into each item?
Note: StorageState is an enum. I can just as easily cast it to an int in all of these expressions. I can figure that part out, but figuring it out is separate from my question here.
I don't know how you would write this as a query expression, nor if EF can translate that expression to sql. I would do something like the following;
var query = ....
// Select all the values you need
.Select(d => new {
d.CustomerId,
d.Customer.Name,
d.LocationId,
d.Location.City,
d.Location.State,
....
state = [insert expression]
})
// group by this key
.GroupBy(d => new {
d.CustomerId,
d.Name,
d.LocationId,
d.City,
d.State
},
// and define the final result set
(d, g) => new {
d.CustomerId,
d.Name,
d.LocationId,
d.City,
d.State,
TotalOpen = g.Sum(x => x.state == StorageState.Open ? 1 : 0),
TotalClosed = g.Sum(x => x.state == StorageState.Closed ? 1 : 0)
});
I think the sql EF will generate, will put the first select in the from clause, surrounded by your typical select / group by statement.
What about this:
TotalOpen = g.Sum(x => (x.ReleaseDate.HasValue && x.ReleaseDate < date) || (x.TakeOrPayEndDate.HasValue && x.TakeOrPayEndDate < date) ?
StorageState.Closed :
(x.RailcarNumber == null || x.RailcarNumber == string.Empty) ?
x.TakeOrPayStartDate.HasValue ? StorageState.TakeOrPay : StorageState.Open :
(x.ArrivalDate.HasValue && x.ArrivalDate <= date) ? StorageState.Filled : StorageState.EnRoute
== StorageState.Open? 1 : 0)
You would repeat the entire expression four times.
There seems to be two things happening here:
StorageDetails is queried from DbContext and somehow for every item(d) found, there's some execution that is being processed to determine the value of state...
state is determined for each d and then all the d's are grouped, and then after only few properties of d are selected...
This might be another approach:
var x = DbContext.StorageDetails
.GroupBy(d => new
{
d.CustomerId,
d.Name,
d.LocationId,
d.City,
d.State
}).Select(d => new
{
d.CustomerId,
d.Name,
d.LocationId,
d.City,
d.State,
d.state = getState(d)
}).ToList();
int getState(d)
{
return (d.ReleaseDate.HasValue && d.ReleaseDate < date) || (d.TakeOrPayEndDate.HasValue && d.TakeOrPayEndDate < date) ?
StorageState.Closed :
(d.RailcarNumber == null || d.RailcarNumber == string.Empty) ?
d.TakeOrPayStartDate.HasValue ? StorageState.TakeOrPay : StorageState.Open :
(d.ArrivalDate.HasValue && d.ArrivalDate <= date) ? StorageState.Filled : StorageState.EnRoute;
}
var TotalOpen = x.Count(x => x.state == StorageState.Open),
var TotalClosed = x.Count(x => x.state == StorageState.Closed)
Basically you first get your data from database group it. After grouping it, you then select specific properties and introduce one property called state... But state is calculated, using another function which takes a row from database return a value from enum StorageState like you have done on the question. Then return the result as list.
Only after you have your list will it be easier to check which of the rows returned contains an open or closed...
Now you can do normal looping and loop through your x and calculate TotalClosed and TotalClosed.

How to use temp local data when creating Anonymous type?

I'm getting some Entities from EF, which I iterate and creating Anonymous type objects such as:
var payments = ctx.Payments.ToList();
var data = ctx.Activities.OrderBy(p => p.ID).ToList().Select(p => new
{
ID = p.ID,
Date = p.Date?.ToString("dd/MM/yyyy"),
PaymentMethod = p.PaymentMethods != null ? p.PaymentMethods.Description : "",
ActivityStatusID = payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count() == 0 ? 1 : .. // I need some other check
}).ToList();
Now, I'd like to check the payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count() several times before set a value.
Such as:
if .Count() == 0, value 1
if .Count() == 1, value 8
if .Count() > 1, value 10
else 87
and so on. I won't do somethings like this:
ActivityStatusID = payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count() == 0 ? 1 : payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count() == 1 ? 8 : payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count() > 1 ? 10 : 87
Is there any way to do payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count() once for each p, and than evalutate the conditions?
People suggested to convert your query into query syntax, which would enable your to use a let statement to create a variable in which you could save count.
If you investigate what let does, it adds one column value to your result. Every row in your result has the same let value in this column. See this answer on stackoverflow
Keep your query in Method Syntax
If you want to keep your query in method syntax, simply add a Count to your anonymous type:
var result = ctx.Activities
.OrderBy(p => p.ID)
.Select(p => new
{
Id = p.Id,
Date = p.Date?.ToString("dd/MM/yyyy"),
PaymentMethod = p.PaymentMethods != null ? p.PaymentMethods.Description : "",
PaidCount = payments
.Where(q => q.ActivityID == p.ID && !q.Paid)
.Count();
})
.Select(p => new
{
Id = p.Id,
Date = p.Date,
ActivityStatusId =
{
// count == 0 => 1
// count == 1 => 8
// count > 1 => 10
// count < 0 => 87
if (p.PaidCount < 0) return 87;
switch (p.PaidCount)
{
case 0:
return 0;
case 1:
return 8;
default:
return 10;
}
},
});
Note, the switch is only possible because you brought your complete ctx.Activities to local memory using the ToList.
Improved efficiency
You do a ToList before your select. This means that your complete ctx.payments are materialized in local memory into a List, before you start selecting and counting items in your sequence.
If ctx.Payments is from an external source, like a database, or a file, then ctx.Payments is an IQueryable instead of an IEnumerable. Fetching your complete Payments to local memory is not an efficient approach.
Advise: Whenever you have an IQueryable, try to keep it IQueryable as long as possible. Your source data provider can process your queries much more efficiently than your local processor. Only materialize it to local memory if your source data provider can't process it anymore, for instance because you need to call local procedures, or because there is nothing to process anymore.
Furthermore, don't move values to local memory that you don't plan to use. Only Select the properties that you actually will use in your local memory.
One improvement would be:
var result = ctx.Payments.Select(payment => new
{ // select only the properties you plan to use locally:
Id = payment.Id,
Date = payment.Date,
PaymentMethod = payment.PaymentMethods?.Description,
PaidCount = ctx.Payments
.Where(q => q.ActivityID == p.ID && !q.Paid)
.Count(),
})
.OrderBy(fetchedPaymentData => fetchedPaymentData.Id)
// from here you need to move it to local memory
// Use AsEnumerable instead of ToList
.AsEnumerable()
.Select(fetchedPaymentData => new
{
Id = fetchedPaymentData.Id,
PaymentMethod = fetchedPaymentData.PaymentMethod ?? String.Empty,
ActivityStatusId = {...}
});
AsEnumerable is more efficient than ToList, especially if you don't need all items at once. For instance if you would end with FirstOrDefault, or only Take(5), then it would be a waste to move all items to local memory.
Finally: with some trying you can get rid of the switch statement, thus allowing your DBMS to calculate the ActivityStatusId. But as the selecting of your source data and the transport of the selected data to local memory is the slower part of your complete query, I doubt whether this would lead to shorter execution time. The switch surely makes your requirement better readable, especially if your numbers 1 / 8 / 87 are put into enums.
I would change the query from a lambda expression to an ordinary query. Then use the let syntax to set a variable for each iteration. Something like this:
var payments = ctx.Payments.ToList();
var data = (
from p in ctx.Activities.ToList()
orderby p.ID
let paymentCount = payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count()
select new
{
ID = p.ID,
Date = p.Date?.ToString("dd/MM/yyyy"),
PaymentMethod = p.PaymentMethods != null ? p.PaymentMethods.Description : "",
ActivityStatusID = paymentCount == 0 ? 1 : .. // I need some other check
}
).ToList();
And btw you can do this part diffrently as well. This:
payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count()
You can write like this:
payments.Count(q => q.ActivityID == p.ID && !q.Paid)

LINQ to Entities does not recognize the method - subquery count

Hi this question has been asked before but I'm struggling to find an answer that matches my question (the error comes up a lot).
This looks to be similar but with 4 years ago has there been any progress.
LINQ to Entities does not recognize the method 'Int32
Basically I want to move a subquery that returns a count into a function anyway here's the code.
vm.SicknessEpisodes = _db.SicknessEpisodes.Where(x => x.Status == true && x.LastDay == null).OrderByDescending(x => x.FirstDay).
Select(x => new HomeSicknessEpisode
{
SicknessEpisode = x,
EpisodesIn12Months = _db.SicknessEpisodes.Where(y => y.StaffID == x.StaffID && y.Status == true &&
(y.LastDay == null || (y.LastDay.HasValue && y.LastDay >= prevDate))).Count()
}).
ToPagedList(p, 100);
This works
This
vm.SicknessEpisodes = _db.SicknessEpisodes.Where(x => x.Status == true && x.LastDay == null).OrderByDescending(x => x.FirstDay).
Select(x => new HomeSicknessEpisode
{
SicknessEpisode = x,
EpisodesIn12Months = _db.episodes12Months(x,prevDate).Count()
}).
ToPagedList(p, 100);
public IQueryable<SicknessEpisode> episodes12Months(SicknessEpisode x, DateTime prevDate)
{
return this.SicknessEpisodes.Where(y => y.StaffID == x.StaffID && y.Status == true &&
(y.LastDay == null || (y.LastDay.HasValue && y.LastDay >= prevDate)));
}
Does not.
Can anyone tell me how I can make it work - ideally without going into a List and then foreach each episode to run the count.
I'm just trying to make it as efficient as possible for Entity Framework.
If it's not possible - then that's fine - one of those things.

Performance Between Sql Server Computed Function and In Memory List

I have a question about the performance hit on 2 scenarios and which would be faster.
Option #1
Would it be faster to grab a list of all travel reports in memory and use C# to remove the ones that I dont want, ex:
var travelReports = db.TravelReports.Include("ApprovalStatuses")
.Where(s => s.IsDeleted == false).ToList();
travelReports.RemoveAll(s => s.Status == TravelReportStatus.Draft);
travelReports.RemoveAll(s => s.Status == TravelReportStatus.NeedsInformation);
in which case the Status would be calculated on the model, using a NotMapped property and getter, using Approval Status like:
if (ApprovalStatuses.Count == 0)
{
return TravelReportStatus.Draft;
}
else if (JobRoles.All(j => ApprovalStatuses.Any(a => a.Role == j && a.Status == ApprovalStatus.Approved)))
{
return TravelReportStatus.Processed;
}
else if (ApprovalStatuses.Any(s => s.Status == ApprovalStatus.Denied))
{
return TravelReportStatus.Denied;
}
else
{
return TravelReportStatus.PendingApproval;
}
Option #2
Put a function on my Model in SQL and call the column Status:
CREATE FUNCTION dbo.GetTravelReportStatus(#travelReportId int)
RETURNS int
AS
-- Returns a Enum Int Value of the Status --
BEGIN
DECLARE #value int;
DECLARE #count int;
DECLARE #isReject int = 0;
SELECT #isReject = Count(*) FROM dbo.Approvals WHERE TravelReportId = #travelReportId and Status = 3;
SELECT #count = Count(*) FROM dbo.Approvals WHERE TravelReportId = #travelReportId;
SELECT #value =
CASE
WHEN #isReject > 0 THEN 3
WHEN #isReject = 0 and #count = 0 THEN 1
WHEN #isReject = 0 and #count > 0 and #count < 6 THEN 2
WHEN #isReject = 0 and #count >= 6 THEN 4
END;
return #value;
END;
Sql("ALTER TABLE dbo.TravelReports ADD Status AS dbo.GetTravelReportStatus(Id)");
and then use SQL and entity framework to only get travel reports with the statuses I want Ex.
var travelReports = db.TravelReports.Include("ApprovalStatuses")
.Where(s => s.Status == TravelReportStatus.PendingApproval || s.Status == TravelReportStatus.Processed || s.Status == TravelReportStatus.Denied)
.Where(s => s.IsDeleted == false).ToList();
Travel reports will continuously grow but after around 4 years they will be moved to archive storage and no longer be in the database.
My question is will option 1 or option 2 provide me the best performance? And will option 2 hit the database with 2 queries every single time I request a Travel report? So If I returned a user 25 Travel Reports i'M going to hit the database 25^2 times to calculate the status, is there any way in codefirst EF to stop that?
I don't know if EF is going to hit the database 3 times or not, but if it is the let clause would resolve it so it would only hit it once.
var travelReports = (from s in db.TravelReports.Include("ApprovalStatuses")
let status = s.Status
where (status == TravelReportStatus.PendingApproval ||
status == TravelReportStatus.Processed ||
status == TravelReportStatus.Denied) &&
reports.IsDeleted == false
select reports).ToList();
Re-creating the let clause using the method syntax instead of the query syntax is much more difficult to do. You have to create a anonymous type to act as a container for your value. For this simple of a query it is not too hard but with more complex queries it may just be easier to use the query syntax.
var travelReports = db.TravelReports.Include("ApprovalStatuses")
.Select(s => new {Query = s, Status = s.Status}) //Project to a new object that has the status pre-calculated
.Where(s => s.Status == TravelReportStatus.PendingApproval || s.Status == TravelReportStatus.Processed || s.Status == TravelReportStatus.Denied)
.Select(s => s.Query) //flatten the object back out.
.Where(s => s.IsDeleted == false)
.ToList();
P.S. To find out what query EF is going to generate just do .ToString() on the IQueryable object and it will return the SQL it will be sending to the server.
var travelReportsQuery = db.TravelReports.Include("ApprovalStatuses")
.Where(s => s.Status == TravelReportStatus.PendingApproval || s.Status == TravelReportStatus.Processed || s.Status == TravelReportStatus.Denied)
.Where(s => s.IsDeleted == false)
var travelReportsQueryText = travelReportsQuery.ToString();
var travelReports = travelReportsQuery.ToList();

Should I keep adding conditions to my LINQ query or add a condition upfront and use two different queries?

I have the following LINQ Query:
var contents = _contentsRepository.GetAll()
.Where(a => a.SubjectId == subjectId &&
a.ContentTypeId == contentTypeId &&
a.ContentStatusId == contentStatusId )
.ToList();
I would like this select to proceed normally unless the contentStatusId == 99. If that's
the case then I want it to retrieve a row from the database with ANY contentStatusId.
Would it be best to do a check of contentStatusId first and then break this down into
two LINQ selects or is there a way I could modify my LINQ query?
Note that I am using SQL Server 2012 and my repository:
public virtual IQueryable GetAll() { return DbSet; }
I believe you can modify your query by adding a contentStatusId == 99 component to your predicate that will short-circuit the evaluation of a.ContentStatusId == contentStatusId like so:
var contents = _contentsRepository.GetAll()
.Where(a => a.SubjectId == subjectId &&
a.ContentTypeId == contentTypeId &&
(contentStatusId == 99 ||
a.ContentStatusId == contentStatusId))
.ToList();
In the normal case everything will work just like before.
In the case when contentStatusId equals 99, there will be overhead of evaluating contentStatusId == 99 for every row, although I think depending on repository you're querying this part could be inlined as a true. You should see for yourself how this impacts performance in your setup.
Try this
var contents = _contentsRepository.GetAll()
.Where(a => contentStatusId == 99 ? (a.SubjectId == subjectId &&
a.ContentTypeId == contentTypeId &&
a.ContentStatusId == contentStatusId) : (a.SubjectId == subjectId &&
a.ContentTypeId == contentTypeId) )
.ToList();
var contents = _contentsRepository.GetAll()
.Where(a =>
{
return a.ContentTypeId == 99 ||
(a.SubjectId == subjectId &&
a.ContentTypeId == contentTypeId &&
a.ContentStatusId == contentStatusId)
}

Categories

Resources