Using linq to find MIN(date) within linq query - c#

I am using linq to extract data. This data contains a date and some other values. The thing i that these dates can occur more then once, because the dates can have the same value but a different timestamp. I want to extract the anonymous type with the earliest timestamp. How can i do this in linq ?
this is my code:
var result = (from a in UnitOfWork.ActivityLessonParticipantService.Query
.Where(a => a.ActivityLesson.Activity.Id == activityId)
.Where(a => a.ActivityLesson.From >= startDate && (a.ActivityLesson.To == startDate || a.ActivityLesson.To <= endDate)).OrderBy(d => d.ActivityLesson.From)
where !a.ActivityLesson.IsDeleted && !a.ActivityLesson.Activity.IsDeleted && a.Appeared == true
select new
{
CPR = a.User.UserName,
FullName = a.User.FullName,
ActivityFromDate = a.ActivityLesson.From,
}).OrderBy(c => c.CPR).ToList();
thanks

You can GroupBy the Date property of DateTime and then order this group by DateTime and use First to pick only the first record/object:
var query = from a in UnitOfWork.ActivityLessonParticipantService.Query
where a.ActivityLesson.Activity.Id == activityId
&& a.ActivityLesson.From >= startDate
&& (a.ActivityLesson.To == startDate || a.ActivityLesson.To <= endDate)
&& !a.ActivityLesson.IsDeleted
&& !a.ActivityLesson.Activity.IsDeleted
&& a.Appeared
select a;
var firstByDate = query.GroupBy(a => a.ActivityLesson.From.Date)
.Select(grp => grp.OrderBy(a => a.ActivityLesson.From).First())
.OrderBy(a => a.User.UserName)
.Select(a => new
{
CPR = a.User.UserName,
FullName = a.User.FullName,
ActivityFromDate = a.ActivityLesson.From,
}).ToList();
Due to LINQ's deferred execution this is actually a single query that gets executed at the final ToList. I'm mixing query and method syntax because i prefer method syntax when it comes to GroupBy but it's a matter of taste.

Related

C# db query where conditions are met, orderby date and then get the first result

While evaluating some queries we found some possible optimization. The ideia is shown below but I currently don't know how to solve this.
Current query:
public static List<Object> SampleQuerySales(int store_id)
{
var query = (from clients in db.table1.Where(p => p.store_id == store_id)
from sales in db.table2.Where(q => q.customer_id == clients.customer_id))
select new Object {
...
}).ToList();
return query;
}
This returns all sales made, but its required only the latest sale (OperationDate) from a datetime reference. As obvious this became a bottleneck.
My ideia was to make it similar to query below, which is incorrect (doesn't compile). How can I achieve this dataset?
var query = (from clients in db.table1.Where(p => p.store_id == store_id)
from sales in db.table2.Where(q => q.customer_id == clients.customer_id
&& q.OperationDate <= dateReference)
.OrderByDescending(s => s.OperationDate).FirstOrDefault() //error
select new Object {
...
}).Tolist();
Since you only want one value from table2, use let instead of from:
var query = (from client in db.table1.Where(p => p.store_id == store_id)
let mostRecentSaleAfterDateReference = db.table2
.Where(q => q.customer_id == client.customer_id
&& q.OperationDate <= dateReference)
.OrderByDescending(s => s.OperationDate)
.FirstOrDefault()
select new Object {
...
}).Tolist();

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.

converting SQL Group By to EF LINQ Lambda

I have this query
SELECT
DISTINCT TOP 10 COUNT([ServicesTracking].[dbo].[SearchLogs].[recordCount]) AS [RecordCount],
[ServicesTracking].[dbo].[SearchLogs].[searchValue] AS [SearchValue] FROM [ServicesTracking].[dbo].[SearchLogs]
WHERE (([ServicesTracking].[dbo].[SearchLogs].[searchType] = 'something'
AND [ServicesTracking].[dbo].[SearchLogs].[searchValue] <> ''
AND [ServicesTracking].[dbo].[SearchLogs].[recordCount] > 0
AND [ServicesTracking].[dbo].[SearchLogs].[appDomain] = 'localhost'))
GROUP BY [ServicesTracking].[dbo].[SearchLogs].[searchValue]
ORDER BY RecordCount DESC
And I am trying to convert it into EF LINQ Lambda. This is what I came up with. EDIT: Fixed a bug with my successive queries.
IQueryable<SearchLog> query = _context.SearchLogs
.Where(sl => sl.appDomain == AppDomain)
.Where(sl => sl.searchType == SearchType)
.Where(sl => sl.searchValue != string.Empty);
// Are we looking for terms that brought back results?
if (_greaterThanZero) query = query.Where(sl => sl.recordCount > 0);
else query = query.Where(sl => sl.recordCount == 0);
// Date range being used?
if (StartDate != DateTime.MinValue) query = query.Where(sl => sl.createDate > DateUtilities.GetStartOfDay(StartDate));
if (EndDate != DateTime.MinValue) query = query.Where(sl => sl.createDate < DateUtilities.GetEndOfDay(EndDate));
List<SearchResultSet> results = query
.GroupBy(sl => sl.searchValue)
.Select(sl => new SearchResultSet
{
SearchValue = sl.Key,
RecordCount = sl.Select(r => r.recordCount)Distinct().Count()
})
.OrderByDescending(sl => sl.RecordCount)
.Take(10)
.ToList();
foreach (SearchResultSet result in results)
result.SearchValue = HttpContext.Current.Server.UrlDecode(result.SearchValue);
return results;
It's not returning the same results. I'm fairly certain I have something mixed up in the GroupBy or Select statements. Any ideas?
Just
RecordCount = sl.Select(r => r.recordCount).Count()
You don't need the Distinct(), and it is not the same as the DISTINCT in the SQL Query, which does nothing, since each row has a distinct searchValue after GROUP BY searchValue.
But why
COUNT([ServicesTracking].[dbo].[SearchLogs].[recordCount]) AS [RecordCount]
? You are counting the rows with non-null recordCount. Should this be SUM()?

Grouping by day in LINQ without TruncateTime()

I have the following LINQ query for a MySQL database in a C# Razor MVC project.
private Dictionary<DateTime?, int> getOrderQuantityDict(DateTime start, DateTime end, int siteCode)
{
return (from o in thisDataEntities.this_table
where o.created_at >= start
&& o.created_at <= end
&& o.store_id == siteCode
select new { OrderDate = o.created_at, Id = o.entity_id})
.GroupBy(q => q.OrderDate)
.ToDictionary(q => q.Key, q => q.Count());
}
I need to group by day. Right now q.OrderDate has hours, minutes, and seconds. I need to ignore those when grouping.
The tricky part: I need to do this without TruncateTime(). When our host moved our DB, we lost the ability to use TruncateTime() for some reason. Our host has been less than helpful on this issue, and I'm hoping a workaround is possible.
Haven't tested it but the following may help you:
return (from o in thisDataEntities.this_table
where o.created_at >= start
&& o.created_at <= end
&& o.store_id == siteCode
select new { OrderDate = o.created_at, Id = o.entity_id})
.AsEnumerable() //Once this is executed, the database will return the result of the query and any other statement after this will be ran locally so TruncateTime will not be an issue
.GroupBy(q => q.OrderDate)
.ToDictionary(q => q.Key, q => q.Count());
You can convert date to the string and make grouping based on the string representation of date.
return
thisDataEntities.this_table
.Where(o => o.created_at >= start)
.Where(o => o.created_at <= end)
.Where(o => o.store_id == siteCode)
.Select(o => new
{
OrderDate = o.created_at,
Id = o.entity_id,
OrderDateFormatted =
SqlFunctions.DateName("yyyy", o.created_at) + "-" +
SqlFunctions.DateName("mm", o.created_at) + "-" +
SqlFunctions.DateName("dd", o.created_at)
})
.GroupBy(n => n.OrderDateFormatted) // format "2017-10-03"
.ToDictionary(g => g.First().OrderDate, g => g.Count());
With approach above execution should happened on database side. Of course only in case GroupBy supported.

Lambda convert to LINQ

I don't know anything about lambda, and I can't even read a complicated lambda expression. I have this lambda code below that I want to convert into LINQ, but I don't know how.
var train = db.sample1
.Join(db.sample2, a => a.CertificateId, b => b.CertificateId, (a, b) => new { a, b })
.Where(x => x.a.Year.Value.Year == year && x.a.TrainingTypeId.Value == trainingTypeId && x.a.IsApproved.Value && x.b.EndDate >= DateTime.Now)
.Select(z => z.a).Distinct();
What I have tried so far and got stuck on:
var train = (from c in db.sample1
join in ts sample2 where a.CertificateId equals b.CertificateId
......
Lambda LINQ is still a link expression. However, the statement should look something like this:
var train2 = (from c in db.sample1
join t in db.sample2
on c.CertificateId equals t.CertificateId
where c.Year.Value.Year == year && c.TrainingTypeId.Value == trainingTypeId
&& c.IsApproved.Value && t.EndDate >= DateTime.Now
select c).Distinct();

Categories

Resources