I have a function that uses LINQ to get data from the database and then I call that function in another function to sum all the individual properties using .Sum() on each individual property. I was wondering if there is an efficient way to sum all the properties at once rather than calling .Sum() on each individual property. I think the way I am doing as of right now, is very slow (although untested).
public OminitureStats GetAvgOmnitureData(int? fnsId, int dateRange)
{
IQueryable<OminitureStats> query = GetOmnitureDataAsQueryable(fnsId, dateRange);
int pageViews = query.Sum(q => q.PageViews);
int monthlyUniqueVisitors = query.Sum(q => q.MonthlyUniqueVisitors);
int visits = query.Sum(q => q.Visits);
double pagesPerVisit = (double)query.Sum(q => q.PagesPerVisit);
double bounceRate = (double)query.Sum(q => q.BounceRate);
return new OminitureStats(pageViews, monthlyUniqueVisitors, visits, bounceRate, pagesPerVisit);
}
private IQueryable<OminitureStats> GetOmnitureDataAsQueryable(int? fnsId, int dateRange)
{
var yesterday = DateTime.Today.AddDays(-1);
var nDays = yesterday.AddDays(-dateRange);
if (fnsId.HasValue)
{
IQueryable<OminitureStats> query = from o in lhDB.omniture_stats
where o.fns_id == fnsId
&& o.date <= yesterday
&& o.date > nDays
select new OminitureStats (
o.page_views.GetValueOrDefault(),
o.monthly_unique.GetValueOrDefault(),
o.visits.GetValueOrDefault(),
(double)o.bounce_rate.GetValueOrDefault()
);
return query;
}
return null;
}
public class OminitureStats
{
public OminitureStats(int PageViews, int MonthlyUniqueVisitors, int Visits, double BounceRate)
{
this.PageViews = PageViews;
this.MonthlyUniqueVisitors = MonthlyUniqueVisitors;
this.Visits = Visits;
this.BounceRate = BounceRate;
this.PagesPerVisit = Math.Round((double)(PageViews / Visits), 1);
}
public OminitureStats(int PageViews, int MonthlyUniqueVisitors, int Visits, double BounceRate, double PagesPerVisit)
{
this.PageViews = PageViews;
this.MonthlyUniqueVisitors = MonthlyUniqueVisitors;
this.Visits = Visits;
this.BounceRate = BounceRate;
this.PagesPerVisit = PagesPerVisit;
}
public int PageViews { get; set; }
public int MonthlyUniqueVisitors { get; set; }
public int Visits { get; set; }
public double PagesPerVisit { get; set; }
public double BounceRate { get; set; }
}
IIRC you can do all the sums in one go (as long as the query is translated to SQL) with
var sums = query.GroupBy(q => 1)
.Select(g => new
{
PageViews = g.Sum(q => q.PageViews),
Visits = g.Sum(q => q.Visits),
// etc etc
})
.Single();
This will give you one object which contains all the sums as separate properties.
I found out why it was throwing the NotSupportedException. I learned that Linq to Entity does not support constructors with parameters, So deleted the constructors and made changes in my query. I am a novice C# programmer, so let me know if my solution could be improved, but as of right now it is working fine.
public class OminitureStats
{
public int PageViews { get; set; }
public int MonthlyUniqueVisitors { get; set; }
public int Visits { get; set; }
public double PagesPerVisit { get; set; }
public double BounceRate { get; set; }
}
private IQueryable<OminitureStats> GetOmnitureDataAsQueryable(int? fnsId, int dateRange)
{
var yesterday = DateTime.Today.AddDays(-1);
var nDays = yesterday.AddDays(-dateRange);
if (fnsId.HasValue)
{
IQueryable<OminitureStats> query = from o in lhDB.omniture_stats
where o.fns_id == fnsId
&& o.date <= yesterday
&& o.date > nDays
select new OminitureStats() {
o.page_views.GetValueOrDefault(),
o.monthly_unique.GetValueOrDefault(),
o.visits.GetValueOrDefault(),
(double)o.bounce_rate.GetValueOrDefault()
};
return query;
}
return null;
}
Related
SELECT
CreationUtcTime, Speed,
CONVERT(varchar, (CreationUtcTime - LAG(CreationUtcTime) OVER (ORDER BY CreationUtcTime)), 108) AS diff
FROM
assetstatusrecords
WHERE
Speed <> 0.00
ORDER BY
CreationUtcTime
I want this SQL query to be converted to LINQ query without using LINQTODB functions and I want exact difference including hours, days, seconds, minutes such that I want to sum the time at later stage.
What I have tried is below:
var records = _context.AssetStatusRecords
.OrderByDescending(s => s.CreationUtcTime)
.Where(s => s.AssetId.Equals(asset.Id)
&& s.CreationUtcTime >= from
&& s.CreationUtcTime <= to
&& s.Speed != 0)
.ToList();
var query = from rec1 in records
from rec2 in records.Where(r => rec1.SequentialId > r.SequentialId).DefaultIfEmpty()
group new { rec1, rec2 } by new { rec1.SequentialId, rec1.CreationUtcTime, rec1.Speed } into g
orderby g.Key.SequentialId
select new
{
g.Key.CreationUtcTime,
g.Key.Speed,
Diff = EntityFunctions.DiffDays(g.Max(p => p.rec2.CreationUtcTime), g.Key.CreationUtcTime)
};
Model class for LINQ
class AssetStatusRecord : Entity
{
protected AssetStatusRecord()
{
}
public AssetStatusRecord(CoordinatesValue coordinates, double speed,
LengthValue distanceTravelled, Guid sensorId, Guid? assetId,
int? heading, Guid readingId, DateTime? sensorDateTime)
{
Coordinates = coordinates;
Speed = speed;
DistanceTravelled = distanceTravelled;
SensorId = sensorId;
AssetId = assetId;
Heading = heading;
ReadingId = readingId;
SensorDateTime = sensorDateTime;
}
public CoordinatesValue Coordinates { get; private set; }
public double Speed { get; private set; }
public LengthValue DistanceTravelled { get; private set; }
public Guid SensorId { get; private set; }
public Guid? AssetId { get; private set; }
public int? Heading { get; private set; }
public Guid ReadingId { get; private set; }
public DateTime? SensorDateTime { get; private set; }
}
And the Entity class is as follows:
public class Entity : IEntity
{
public Entity();
public Guid Id { get; protected set; }
public long SequentialId { get; protected set; }
public DateTime CreationUtcTime { get; protected set; }
public DateTime CreationLocalTime { get; protected set; }
}
And this is the interface IEntity:
public interface IEntity
{
Guid Id { get; }
long SequentialId { get; }
DateTime CreationUtcTime { get; }
}
Try the following query:
var records = _context.AssetStatusRecords
.Where(s => s.AssetId == asset.Id
&& s.CreationUtcTime >= from
&& s.CreationUtcTime <= to
&& s.Speed != 0);
var query =
from current in records
from prev in records
.Where(prev => current.CreationUtcTime <= prev.CreationUtcTime && prev.SequentialId < current.SequentialId)
.OrderByDescending(prev => prev.CreationUtcTime)
.Take(1)
.DefaultIfEmpty()
orderby current.CreationUtcTime
select new
{
current.CreationUtcTime,
current.Speed,
Diff = EntityFunctions.DiffDays(current.CreationUtcTime, prev.CreationUtcTime)
};
I have the following objects:
public class TestResult
{
public string SectionName { get; set; }
public int Score { get; set; }
public int MaxSectionScore { get; set; }
public bool IsPartialScore { get; set; }
public string Name { get; set; }
public int NumberOfAttempts { get; set; }
}
public class TestResultGroup
{
public TestResultGroup()
{
Results = new List<TestResult>();
Sections = new List<string>();
}
public List<TestResult> Results { get; set; }
public List<string> Sections { get; set; }
public string Name { get; set; }
public int Rank { get; set; }
}
So, a TestResultGroup can have any number of results of type TestResult. These test results only differ by their SectionName.
I have a List<TestResultGroup> which I need to sort into descending order based on a score in the Results property, but only when Results has an item whos SectionName = "MeanScore" (if it doesnt have this section we can assume a score of -1). How would I go about ordering the list? Ideally I would also like to apply the result of this ordering to the Rank property.
Many Thanks
List<TestResultGroup> groups = ...
// group test result groups by the same score and sort
var sameScoreGroups = groups.GroupBy(
gr =>
{
var meanResult = gr.Results.FirstOrDefault(res => res.SectionName == "MeanScore");
return meanResult != null ? meanResult.Score : -1;
})
.OrderByDescending(gr => gr.Key);
int rank = 1;
foreach (var sameScoreGroup in sameScoreGroups)
{
foreach (var group in sameScoreGroup)
{
group.Rank = rank;
}
rank++;
}
// to obtain sorted groups:
var sortedGroups = groups.OrderByDescending(gr => gr.Rank).ToArray();
Or even write one expression with a side effect:
List<TestResultGroup> groups = ...
int rank = 1;
var sortedGroups = groups
.GroupBy(
gr =>
{
var meanResult = gr.Results.FirstOrDefault(res => res.SectionName == "MeanScore");
return meanResult != null ? meanResult.Score : -1;
})
.OrderByDescending(grouping => grouping.Key)
.SelectMany(grouping =>
{
int groupRank = rank++;
foreach (var group in grouping)
{
group.Rank = groupRank;
}
return grouping;
})
.ToArray(); // or ToList
I have a dictionary, salaryFitmentDictionary which I would like to query (linq or lambda) based on example: where employeedId = 1 and EarningDeductionId = 145 and get the value of the balance, EDBalance.
How would I achieve this?
var balance = salaryFitmentDictionary.Where...
Dictionary<int, IEnumerable<SalaryFitmentInfoMonth>> salaryFitmentDictionary = new Dictionary<int, IEnumerable<SalaryFitmentInfoMonth>>();
employeeIdList.ToList().ForEach(employeedId =>
{
var perEmployeeFitments = from pf in _db.PayFitments.AsEnumerable()
join ed in _db.EarningDeductions.AsEnumerable()
on pf.EarningDeductionId equals ed.EarningDeductionId
where pf.EmployeeId == employeedId
select new SalaryFitmentInfoMonth
{
EDId = pf.EarningDeductionId,
EDAmount = pf.Amount,
EDBalance = pf.Balance.GetValueOrDefault(),
EDType = ed.EDType,
IsTaxable = ed.IsTaxable,
IsBenefit = ed.IsBenefit,
IsLoan = ed.IsLoan,
IsAdvance = ed.IsAdvance,
Limit = ed.TaxIfMoreThan.GetValueOrDefault()
};
salaryFitmentDictionary.Add(employeedId, perEmployeeFitments);
});
public struct SalaryFitmentInfoMonth
{
public int EDId { get; set; }
public decimal EDAmount { get; set; }
public decimal? EDBalance { get; set; }
public EarnDeduct EDType { get; set; }
public bool IsTaxable { get; set; }
public bool IsBenefit { get; set; }
public bool IsLoan { get; set; }
public bool IsAdvance { get; set; }
public decimal? Limit { get; set; }
}
IEnumerable<SalaryFitmentInfoMonth> salaries = salaryFitmentDictionary[1];
SalaryFitmentInfoMonth salary = salaries.FirstOrDefault(s => s.EDId == 45);
You should handle the case that salaryFitmentDictionary doesn't contain one with this ID. So you could use TryGetValue instead. If no salary has this EDId FirstOrDefault returns null.
So here's the safer version:
IEnumerable<SalaryFitmentInfoMonth> salaries;
if(salaryFitmentDictionary.TryGetValue(1, out salaries))
{
SalaryFitmentInfoMonth salary = salaries.FirstOrDefault(s => s.EDId == 45);
if(salary != null)
{
// do something ...
}
}
If you expect more than one match you could use Enumerable.Where instead of FirstOrDefault.
You could use SelectMany method in LINQ method syntax:
Int32 id = 1;
Int32 edId = 147;
var result = salaryFitmentDictionary.
Where((pair) => pair.Key == id ).
SelectMany((pair) =>
pair.Value.Where((perEmployeeFitment) => perEmployeeFitment.EDId == edId)).
Select(perEmployeeFitment => perEmployeeFitment.EDBalance).
Single();
Or in query syntax:
Int32 id = 1;
Int32 edId = 147;
var result = (from pair in salaryFitmentDictionary
from perEmployeeFitment in pair.Value
where pair.Key == id
where perEmployeeFitment.EDId == edId
select perEmployeeFitment.EDBalance).Single();
I am trying to merge 2 classes into a third on the date properties, for binding to a graph I have already setup.
public class Last30DaysHours
{
public DateTime Date { get; set; }
public float Hours { get; set; }
public float LostHours { get; set; }
}
public class MachineHours
{
public DateTime Date { get; set; }
public float Hours { get; set; }
}
into
public class GraphLast30Days
{
public DateTime Date { get; set; }
public float Hours { get; set; }
public float LostHours { get; set; }
public float SelectedMachine { get; set; }
}
So far I have this linq statement which almost compiles.
The error with the current statment is "'x' does not exist in the current context".
I know what this means but I don't know how to make it accessable in the statement.
IEnumerable<GraphLast30Days> last30DaysMachineHoursSelect = _last30DaysMachineHours
.Select(p => (_last30Days
.Where(x => x.Date == p.Date) <=
(new GraphLast30Days {
Date = x.Date,
Hours = x.Hours,
LostHours = x.LostHours,
SelectedMachine = p.Hours
})));
My question is how do I make x accessable by the second half of the statement or what is a better statement to achieve the same results?
Thanks for the help.
You'd wont to use join to join your collections by date (not sure I got the correct proeprties, but you got the idea):
IEnumerable<GraphLast30Days> last30DaysMachineHoursSelect =
from machineHours in _last30DaysMachineHours
join last30 in _last30Days on machineHours.Date equals last30.Date
select
new GraphLast30Days {
Date = machineHours.Date,
Hours = machineHours.Hours,
LostHours = last30.LostHours,
SelectedMachine = machineHours.Hours
};
Or with alternative syntax:
var result = _last30DaysMachineHours.Join(_last30Days, graph => graph.Date, last30 => last30.Date,
(graph, last30) => new GraphLast30Days
{
Date = graph.Date,
Hours = graph.Hours,
LostHours = last30.LostHours,
SelectedMachine = graph.Hours
});
In case you don't wont to filter out missing values you'll need to do left join:
IEnumerable<GraphLast30Days> last30DaysMachineHoursSelect =
from last30 in _last30Days
from machineHours in _last30DaysMachineHours.Where (h => h.Date == last30.Date).DefaultIfEmpty()
select
new GraphLast30Days {
Date = last30.Date,
Hours = last30.Hours,
LostHours = last30.LostHours,
SelectedMachine = machineHours == null ? 0 : machineHours.Hours
}
I have a query that works fine when using an anonymous type but as soon as I try to un-anonymize it it fails to select all values into the class.
here is the linq i'm using (in combination with Subsonic 3):
var producten = (from p in Premy.All()
join pr in Producten.All() on p.dekking equals pr.ID
where p.kilometragemax >= 10000 &&
p.CCmin < 3000 &&
p.CCmax >= 3000 &&
p.leeftijdmax >= DateTime.Today.Subtract(car.datumEersteToelating).TotalDays / 365
group p by new { pr.ID, pr.Naam, pr.ShortDesc, pr.LongDesc } into d
select new
{
ID = d.Key.ID,
Dekking = d.Key.Naam,
ShortDesc = d.Key.ShortDesc,
LongDesc = d.Key.LongDesc,
PrijsAlgemeen = d.Min(x => x.premie),
PrijsAlgemeenMaand = d.Min(x => x.premie),
PrijsMerkdealerMaand = d.Min(x => x.premie),
PrijsMerkdealer = d.Min(x => x.premie)
}).ToList();
When I change it to:
List<QuotePremies> producten = (from p in Premy.All()
join pr in Producten.All() on p.dekking equals pr.ID
where p.kilometragemax >= 10000 &&
p.CCmin < 3000 &&
p.CCmax >= 3000 &&
p.leeftijdmax >= DateTime.Today.Subtract(car.datumEersteToelating).TotalDays / 365
group p by new { pr.ID, pr.Naam, pr.ShortDesc, pr.LongDesc } into d
select new QuotePremies
{
ID = d.Key.ID,
Dekking = d.Key.Naam,
ShortDesc = d.Key.ShortDesc,
LongDesc = d.Key.LongDesc,
PrijsAlgemeen = d.Min(x => x.premie),
PrijsAlgemeenMaand = d.Min(x => x.premie),
PrijsMerkdealerMaand = d.Min(x => x.premie),
PrijsMerkdealer = d.Min(x => x.premie)
}).ToList();
in combination with this class:
public class QuotePremies
{
public byte ID { get; set; }
public string Dekking { get; set; }
public string ShortDesc { get; set; }
public string LongDesc { get; set; }
public decimal PrijsAlgemeen { get; set; }
public decimal PrijsAlgemeenMaand { get; set; }
public decimal PrijsMerkdealer { get; set; }
public decimal PrijsMerkdealerMaand { get; set; }
}
it doesn't give me an error but all values in the class are 0 except for QuotePremies.ID, QuotePremies.ShortDesc and QuotePremies.LongDesc. No clue why that happens.
See if using conversion helps
PrijsAlgemeen = Convert.ToDecimal(d.Min(x => x.premie))
I believe the problem has to do with casting. Why not write and extension method for IEnumberable which would take this query result and return a collection of List. It could look something like this:
public static class Extensions
{
// extends IEnumerable to allow conversion to a custom type
public static TCollection ToMyCustomCollection<TCollection, T>(this IEnumerable<T> ienum)
where TCollection : IList<T>, new()
{
// create our new custom type to populate and return
TCollection collection = new TCollection();
// iterate over the enumeration
foreach (var item in ienum)
{
// add to our collection
collection.Add((T)item);
}
return collection;
}
}
Thanks to kek444 for helping me with a similar problem