Filtering a group by linq query - c#

var testingAll = (from ac in metaData.AcTable
where ac.Call >= DateTime.Now.AddMonths(-2) && ac.Call <= DateTime.Now
group adminCall by ac.LanguageCode into acc
select new { lang = acc.Key, count = acc.Count() }).ToDictionary(x => x.lang, y => y.count).OrderByDescending(x => x.Key);
Can I have filter again after the datetime ?
Something like this:
var Today = testingAll.Where( /*x => x.Call >= DateTime.Now.AddDays(-2)*/)

I think you want something like
var testingAll = (from ac in metaData.AcTable
where ac.Call >= DateTime.Now.AddMonths(-2) && ac.Call <= DateTime.Now
group adminCall by adminCall.LanguageCode into ac
select ac
this should give back a collection where you can then query a number of times.

The short answer is that you can't do this. Think of it this way, the problem is effectively the same as me giving you the average age of children in a class and then you taking that number and trying to work out the average age of the boys only - it's not possible without the source data.
Now you could maybe do this by building an expression and spending a lot of effort there, but it would still have to re-query the database.
If you really want to abstract it away slightly, then you could create a function that takes the where predicate as a parameter:
public IEnumerable<KeyValuePair<string, int>> GetFilteredCalls(
Expression<Func<Call, bool>> predicate)
{
return calls
.Where(predicate)
.GroupBy(c => c.LanguageCode)
.Select(g => new { Lang = g.Key, Count = g.Count() })
.ToDictionary(x => x.Lang, y => y.Count)
.OrderByDescending(x => x.Key);
}
And you use it like this:
var calls = GetFilteredCalls(c => c.Call >= DateTime.Now.AddMonths(-2)
&& c.Call <= DateTime.Now);
var moreCalls = GetFilteredCalls(c => c.Call >= DateTime.Now.AddDays(-2)
&& c.Call <= DateTime.Now);

Related

Getting the DENSE_RANK() in C# over multiple splits of data

I have a dataset that is built gradually in parts, and as each part is done, I'm associating the entries with their DENSE_RANK() with the following code (source: implement dense rank with linq):
aQueryable.GroupBy(x => x)
.Where(g => g.Any())
.OrderBy(g => g.Key.SortOrder1)
.ThenBy(g => g.Key.SortOrder2)
.ThenBy(g => g.Key.SortOrder3)
.Select((g, i) =>
{
++i;
foreach (var x in g)
{
x.DenseRank = i;
}
return g;
}).Select(g => g.Key)
SQL equivalent: DENSE_RANK() OVER ( ORDER BY SortOrder1, SortOrder2, SortOrder3 )
However, the DenseRank that I'm computing here doesn't match the DENSE_RANK() I get in SQL once the entire dataset is written. I suspect this is because I'm computing my DENSE_RANK() on a subset of the full dataset.
Is there any way I can compute the same DENSE_RANK() as SQL without waiting for my entire dataset to finish populating first?
If you wanted to stream the results through an IEnumerable, you can OrderBy on the database and then write your own select, checking for rank:
var lastSort1 = default(int);
var lastSort2 = default(int);
var lastSort3 = default(int);
var firstRun = true;
var rank = 1;
iqueryable
.OrderBy(i => i.SortOrder1)
.ThenBy(i => i.SortOrder2)
.ThenBy(i => i.SortOrder3)
.AsEnumerable()
.Select(i =>
{
if (!firstRun && (lastSort1 != i.SortOrder1 || lastSort2 != i.SortOrder2 || lastSort3 != i.SortOrder3))
{
rank++;
}
firstRun = false;
lastSort1 = i.SortOrder1;
lastSort2 = i.SortOrder2;
lastSort3 = i.SortOrder3;
i.DenseRank = rank;
return i;
});
As soon as the first data comes through, you'll start receiving items through the IEnumerable, but eventually it will all be materialized.

Adding Summed columns together using Linq

I am wondering if there is an easier way of adding together summed columns inside a group by query using Linq. I am wanting to check wether column1 + column2 is greater than 2250, and if so do something...
Below is a snippet of code im using, a much slimmed down version for use here
from contact in _db.Worksheets
join person in _db.MyTable on contact.Email equals
person.EmailAddress
orderby contact.ShiftDate ascending
select new
{
EmployeeNumber = person.EmployeeNumber,
Overtime1= contact.Overtime1,
Overtime2= contact.Overtime2,
ShiftDate = contact.ShiftDate,
} into t1
group t1 by t1.EmployeeNumber into pg
select (new
{
OvertimeTotal = pg.Sum(x => x.ShiftDate >= vStart1 && x.ShiftDate <= vEnd1 ? x.Overtime1 : 0)
+ pg.Sum(x => x.ShiftDate >= vStart1 && x.ShiftDate <= vEnd1 ? x.Overtime2 : 0) > 2250 (....then do something)
I was wondering if you could do something like the below. (Which I have tried and it doesnt work)
I am using entity framework too, so realise there may be complications converting this type of query
OvertimeTotal = pg.Sum(x => x.ShiftDate >= vStart1 && x.ShiftDate <= vEnd1 ? x.Overtime1 + x.Overtime2 : 0)
It would help if you specify what you want when there is overtime. The following will return the employee numbers with overtime.
The approach is a little different from yours, as it filters the worksheet records before joining with the person records for optimization reasons. It then filters to just the employees that have overtime.
I'm assuming an employee has overtime if overtime1 + overtime2 of all their shifts in the specified date range adds up to more than 2250.
var employeesWithOvertime = _db.Worksheets
.Where(w => w.ShiftDate >= vStart1 && w.ShiftDate <= vEnd1)
.Join(_db.MyTable, w => w.Email, p => p.EmailAddress, (w, p) => new
{
EmployeeNumber = p.EmployeeNumber,
Overtime1 = w.Overtime1,
Overtime2 = w.Overtime2
})
.GroupBy(x => x.EmployeeNumber)
.Select(g => new
{
EmployeeNumber = g.Key,
OvertimeTotal = g.Sum(x => x.Overtime1 + x.Overtime2)
})
.Where(x => x.OvertimeTotal > 2250);

Performing multiple Linq queries against the same Linq result

I have created a dashboard that all data displayed on it shares 4 common elements (startDate,endDate,CompanyID,StoreID) that are used as Where clauses in a Linq statement. The result of that statement is then queried in a variety of ways to group and sort the data and used in charts, lists etc. Here is a short snippit to show the duplication that is currently going on:
var dashboardEntity = new BlueStreakSalesDWEntities();
//Get Total Sales
ViewBag.companySalesTotal = dashboardEntity.FactSales.Where(d => d.DateKey >= startDate)
.Where(d => d.DateKey <= endDate)
.Where(c => c.CompanyID == companyID)
.Sum(a => a.Amount);
//get list of all items sold
var companyStoreTotalItem = dashboardEntity.FactSales.Where(d => d.DateKey >= startDate)
.Where(d => d.DateKey <= endDate)
.Where(c => c.CompanyID == companyID).GroupBy(m => new { m.Description })
.Select(g => new DescriptionAmountModel { Amount = g.Sum(a => a.Amount).Value, Description = g.Key.Description })
.OrderByDescending(x => x.Amount);
I have like 15 of these calls on the dashboard and it can get very slow at times from what I imagine are multiple calls when in reality the database only needs to be queried once then that result needs to be queried for different results.
How can I do this?
Any help would be greatly appreciated
In your current solution each query executes separatly, on the same data. You can first execute the shared parts of the queries and bring the results from database. In your examples it is these where conditions
//Executes in database
var entities = dashboardEntity.FactSales.Where(d => d.DateKey >= startDate)
.Where(d => d.DateKey <= endDate)
.Where(c => c.CompanyID == companyID)
.ToList();
Now that this data is filtered to only what you want you can in memory do the rest of the aggregations:
//Happens in the List<T> in memory
ViewBag.companySalesTotal = entities.Sum(a => a.Amount);
var companyStoreTotalItem = entities.GroupBy(m => new { m.Description })
.Select(g => new DescriptionAmountModel { Amount = g.Sum(a => a.Amount).Value, Description = g.Key.Description })
.OrderByDescending(x => x.Amount);
This way you can make efficient. This make the query execute single time in database and rest of the part happen on the pullout in memory data
var result = dashboardEntity.FactSales.Where(d => d.DateKey >= startDate && d => d.DateKey <= endDate && d.CompanyID == companyID).ToList();
ViewBag.companySalesTotal = result.Sum(a => a.Amount);
//then get list of all items sold from in memory data
var companyStoreTotalItem = result.GroupBy(m => new { m.Description }).Select(g => new DescriptionAmountModel { Amount = g.Sum(a => a.Amount).Value, Description = g.Key.Description }).OrderByDescending(x => x.Amount);

LINQ to Entities does not recognize the method

This is my Code where I am fetching data.
var list = (from u in _dbContext.Users
where u.IsActive
&& u.IsVisible
&& u.IsPuller.HasValue
&& u.IsPuller.Value
select new PartsPullerUsers
{
AvatarCroppedAbsolutePath = u.AvatarCroppedAbsolutePath,
Bio = u.Bio,
CreateDateTime = u.CreationDate,
Id = u.Id,
ModifieDateTime = u.LastModificationDate,
ReviewCount = u.ReviewsReceived.Count(review => review.IsActive && review.IsVisible),
UserName = u.UserName,
Locations = (from ul in _dbContext.UserLocationRelationships
join l in _dbContext.Locations on ul.LocationId equals l.Id
where ul.IsActive && ul.UserId == u.Id
select new PartsPullerLocation
{
LocationId = ul.LocationId,
Name = ul.Location.Name
}),
Rating = u.GetPullerRating()
});
Now Here is my Extension.
public static int GetPullerRating(this User source)
{
var reviewCount = source.ReviewsReceived.Count(r => r.IsActive && r.IsVisible);
if (reviewCount == 0)
return 0;
var totalSum = source.ReviewsReceived.Where(r => r.IsActive && r.IsVisible).Sum(r => r.Rating);
var averageRating = totalSum / reviewCount;
return averageRating;
}
I have check this Post LINQ to Entities does not recognize the method
And I come to know I need to use
public System.Linq.Expressions.Expression<Func<Row52.Data.Entities.User, int>> GetPullerRatingtest
But how ?
Thanks
You can use conditionals inside LINQ to Entity queries:
AverageRating = u.ReviewsReceived.Count(r => r.IsActive && r.IsVisible) > 0 ?
u.ReviewsReceived.Where(r => r.IsActive && r.IsVisible).Sum(r => r.Rating) /
u.ReviewsReceived.Count(r => r.IsActive && r.IsVisible)
: 0
This will be calculated by the server, and returned as part of your list. Although with 10 million rows like you said, I would do some serious filtering before executing this.
Code within LINQ (to Entities) query is executed within database, so you can't put random C# code there. So you should either use user.GetPullerRating() after it is retrieved or create a property if you don't want to do the calculation every time.
You can also do:
foreach (var u in list)
u.Rating = u.GetPullerRating()
By the way, why is it extension method.

Can you dynamically cast during runtime inside a linq statement?

Hello is there a way to dynamically cast inside the following code so I do not need a bunch of if statements with nearly identical code?
List<DateTime> dateTimeList = null;
if(_dataSeriesList[0].GetType().Name == "Class1")
{
dateTimeList =
_dataSeriesList.ConvertAll(x => (Class1) x)
.Where(d => d.Time >= min && d.Time <= max)
.OrderBy(t => t.Time)
.Select(d => d.Time)
.ToList();
}
else if(_dataSeriesList[0].GetType().Name == "Class2")
{
dateTimeList =
_dataSeriesList.ConvertAll(x => (Class2) x)
.Where(d => d.Time >= min && d.Time <= max)
.OrderBy(t => t.Time)
.Select(d => d.Time)
.ToList();
}
.
.
and so on
I tried using the following code:
public static T Cast<T>(object o)
{
return (T)o;
}
Type t2 = _dataSeriesList[0].GetType();
dateTimeList =
_dataSeriesList.ConvertAll(x => castMethod.Invoke(null, new object[] { x }))
.Where(d => d.Time >= min && d.Time <= max)
.OrderBy(t => t.Time)
.Select(d => d.Time)
.ToList();
But then the linq statement will not compile.
If they don't share a common base type you can do it like this:
dateTimeList =
_dataSeriesList.Select(x => {
if (x.GetType().Name == "Class1")
return ((Class1)x).Time;
else
return ((Class2)x).Time;
})
.Where(d => d >= min && d <= max)
.OrderBy(t => t)
.Select(d => d)
.ToList();
Since all you are doing is working with the time element you just select out the item of interest according to your rules and then use it in a common way.
Of course if what you are working with is more complicated you select that information instead.
This is fairly standard in Linq -- make a new type on the fly that you need in order to do your work.
((as has been pointed out in the comments "on the fly" is actually determined at compile time and not dynamic in the typical use of the word))

Categories

Resources