Get max + min in one line with linq - c#

How can I change this method so it also returns Max(t.StartTime) and Min(t.StartTime) with only using it in one line as below?
public IQueryable<Timetable> GetTimetables()
{
return from t in _entities.Timetables
select t;
}
/M

Off the top of my head (read: untested) and presuming StartTime is non-nullable:
public class TimetableWithMaxMin
{
public Timetable Timetable { get; set; }
public DateTime Max { get; set; }
public DateTime Min { get; set; }
}
public IQueryable<TimetableWithMaxMin> GetTimetables()
{
return from t in _entities.Timetables
select new TimetableWithMaxMin
{
Timetable = t,
Max = _entities.Timetables.Max(t => t.StartTime),
Min = _entities.Timetables.Min(t => t.StartTime)
};
}
This gets considerably more complicated when StartTime is nullable.

I don't know if this is applicable to Entity framework but with linq it's something like this
public IQueryable<Timetable> GetTimetables()
{
return from t in _entities.Timetables
select new {maxt = Max(t.StartTime), mint = Min(t.StartTime)};
}

Related

LINQ query grouping without duplication of data

So I want to display an output that is group by two fields: SubsidiaryCode and AssetCreatedDate. My problem is it displays the grouping values redundantly.
I suspect it duplicates because of my Detail class.
What I want is:
But it displays like this:
LINQ query:
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public IList<AssetListTemplate> List = new List<AssetListTemplate>();
public IList<AssetListTemplate> GetList()
{
using (var ctx = LinqExtensions.GetDataContext<NXpert.FixedAsset.DataAccess.FixedAssetDataContext>("AccountingDB"))
{
var list = (from x in ctx.DataContext.AssetRegistryEntities
where x.SubsidiaryCode2 != "" && x.SubsidiaryCode2.ToUpper().Contains("y-") && x.AssetCreatedDate>=FromDate && x.AssetCreatedDate <= ToDate
group new { x.SubsidiaryCode2, x.AssetCreatedDate,x.AssetCategoryID } by x into groupedList
select new AssetListTemplate
{
IsSelected = false,
SubsidiaryCode = groupedList.Key.SubsidiaryCode2,
AssetCreatedDate = groupedList.Key.AssetCreatedDate,
AssetCategory = groupedList.Key.AssetCategoryID
}
).OrderBy(x => x.SubsidiaryCode).ThenBy(y => y.AssetCreatedDate).ToList();
List = list;
foreach (var item in List)
{
var details = (from x in ctx.DataContext.AssetRegistryEntities
join y in ctx.DataContext.AssetCategoryEntities on x.AssetCategoryID equals y.AssetCategoryID
join z in ctx.DataContext.FixedAssetOtherInfoEntities on x.AssetCode equals z.AssetCode
where x.SubsidiaryCode2 == item.SubsidiaryCode
select new Details
{
AssetCode = x.AssetCode,
AssetCodeDesc = y.AssetCategoryDesc,
AssetDesc = x.AssetCodeDesc,
DepInCharge = z.DepartmentInCharge,
SerialNo = x.SerialNumber,
ModelNo = x.ModelNumber
}).ToList();
item.Details = details;
}
return List;
}
}
}
public class AssetListTemplate
{
public bool IsSelected { get; set; }
public string SubsidiaryCode { get; set; }
public DateTime? AssetCreatedDate { get; set; }
public string AssetCategory { get; set; }
public List<Details> Details { get; set; }
}
public class Details {
public string AssetCode { get; set; }
public string AssetCodeDesc { get; set; }
public string AssetDesc { get; set; }
public string DepInCharge { get; set; }
public string SerialNo { get; set; }
public string ModelNo { get; set; }
}
SQL Query:
SELECT Are_SubsidiaryCode2[SubsidiaryCode],Are_AssetCreatedDate[AssetCreatedDate],Are_AssetCategoryID[AssetCategory]
FROM E_AssetRegistry
WHERE Are_SubsidiaryCode2<>''
AND Are_SubsidiaryCode2 LIKE '%Y-%'
GROUP BY Are_SubsidiaryCode2
,Are_AssetCreatedDate
,Are_AssetCategoryID
ORDER BY AssetCreatedDate ASC
You don't seem to be using the grouping for any aggregate function , so you could make life simpler by just using distinct:
from x in ctx.DataContext.AssetRegistryEntities
where x.SubsidiaryCode2.Contains("y-") && x.AssetCreatedDate>=FromDate && x.AssetCreatedDate <= ToDate
select new AssetListTemplate
{
IsSelected = false,
SubsidiaryCode = groupedList.Key.SubsidiaryCode2,
AssetCreatedDate = groupedList.Key.AssetCreatedDate.Value.Date,
AssetCategory = groupedList.Key.AssetCategoryID
}
).Distinct().OrderBy(x => x.SubsidiaryCode).ThenBy(y => y.AssetCreatedDate).ToList();
Side note, you don't need to assign a list to a clas variable and also return it; I'd recommend just to return it. If you're looking to cache the results, make the class level var private, assign it and return it first time and just return it the second time (use the null-ness of the class level var to determine if the query has been run)
Expanding on the comment:
You don't need to store your data in a public property and also return it. Don't do this:
public class Whatever{
public string Name {get;set;}
public string GetName(){
var name = "John";
Name = name;
return name;
}
Typically we would either return it:
public class Whatever{
public string GetName(){
var name = MySlowDatabaseCallToCalculateAName();
return name;
}
//use it like:
var w = new Whatever();
var name = w.GetName();
Or we would store it:
public class Whatever{
public string Name {get;set;}
public void PopulateName(){
Name = MySlowDatabaseCallToCalculateAName();
}
//use it like
var w = new Whatever();
w.PopulateName();
var name = w.Name;
We might have something like a mix of the two if we were providing some sort of cache, like if the query is really slow and the data doesn't change often, but it is used a lot:
public class Whatever{
private string _name;
private DateTime _nameGeneratedAt = DateTime.MinValue;
public string GetName(){
//if it was more than a day since we generated the name, generate a new one
if(_nameGeneratedAt < DateTime.UtcNow.AddDays(-1)){
_name = MySlowDatabaseCallToCalculateAName();
_nameGeneratedAt = DateTime.UtcNow; //and don't do it again for at least a day
}
return _name;
}
This would mean that we only have to do the slow thing once a day, but generally in a method like "Get me a name/asset list/whatever" we wouldn't set a public property as well as return the thing; it's confusing for callers which one to use - they want the name; should they access the Name property or call GetName? If the property was called "MostRecentlyGeneratedName" and the method called "GenerateLatestName" it would make more sense - the caller can know they might call Generate..( first, and then they could use MostRecently.. - it's like a caching; the calling class can decide whether to get the latest, or reuse a recently generated one (but it does introduce the small headache of what happens if some other operation does a generate in the middle of the first operation using the property..)..
..but we probably wouldn't do this; instead we'd just provide the Generate..( method and if the caller wants to cache it and reuse the result, it can

Combine two lists and match values

Inside the "Distributions" variable is a key called "Deadline" which contains a date.
I would like to add "RealDeadline = i.Deadline,". All the other lines works fine, but I just cant find a way to add the last thing.
The match has to be made on AssignmentId which is the key for the whole combine. basically if the HandInData.Where(...) could just add the value of "Deadline" from "Distributions", that would do the trick..
var HandInData = db.Handins.ToList();
var Distributions = db.Distributes.ToList();
var AssignNames = HandInData.Where(a => Distributions.Any(x => x.AssignmentId == a.AssignmentId));
var StudentsHandedInDataFeed = AssignNames.Select(i => new {
*RealDeadline = i.Deadline, (this is not working..)*
Help = i.NeedHelp,
Done = i.Done,
AssName = i.Assignment.AssignmentName,
Student = i.Student.StudentName,
DeadlineInTimeformat = i.Assignment.AssignmentDeadline,
HandedInInTimeformat = i.HandedInDate,
Deadline = i.Assignment.AssignmentDeadline.ToString(),
HandedIn = i.HandedInDate.ToString()
});
public class Handin {
public int HandinId { get; set; }
public int StudentId { get; set; }
public int AssignmentId { get; set; }
public bool? Done { get; set; }
public bool? NeedHelp { get; set; }
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime? HandedInDate { get; set; }
public virtual Student Student { get; set; }
public virtual Assignment Assignment { get; set; }
}
You need to join the two lists. You can do it with LINQ syntax like this:
var StudentsHandedInDatFeed =
from h in HandInData
join d in Distributions on h.AssignmentId equals d.AssignmentId
select new {
RealDeadline = d.Deadline,
Help = h.NeedHelp,
// etc
};
The join will only include values from HandInData where there is a matching value in Distributions, so this takes care of your Where(a => Distributions.Any(... code as well.

Sequence contains more than one element when using Contains in LINQ query

The following code is throwing an InvalidOperationException: Sequence contains more than one element. The errors occurs whether I have the Include statement or not.
long[] customerIDs; //Method parameter. Has valid values
var results = from x in DB.CycleCounts
//.Include(y => y.CustomerInventoryItem)
select x;
if (customerIDs != null && customerIDs.Length > 0)
{
results = from x in results
where customerIDs.Contains(x.CustomerInventoryItem.CustomerID)
select x;
}
var cycleCounts = await results.ToListAsync(); //throws InvalidOperationException
I am using ASP5 RC1 (Core), and Entity Framework 7
I not sure how look your models, but I think that they looks like something below (relationship between CycleCounts and CustomerInventoryItems as many-to-one as I expect):
Models:
[Table("CustomerInventorys")]
public class CustomerInventory
{
public long ID { get; set; }
public long CustomerID { get; set; }
public virtual ICollection<CycleCount> CycleCounts { get; set; }
}
[Table("CycleCounts")]
public class CycleCount
{
public long ID { get; set; }
public long CustomerInventoryItemID { get; set; }
public virtual CustomerInventory CustomerInventoryItem { get; set; }
}
So if my suggestions are correct I recommend you to rewrite you code like this:
IQueryable<CycleCount> results = null;
if (customerIDs != null && customerIDs.Length > 0)
{
results = from invent in DB.CustomerInventorys
where customerIDs.Contains(invent.CustomerID)
join cycle in DB.CycleCounts on invent.ID equals cycle.CustomerInventoryItemID
select cycle;
}
else
results = DB.CycleCounts;
var cycleCounts = await results.ToListAsync();

Linq Grouping and averages

I have two objects one is a car object and the other object I use to log how many car objects are on shift and record what properties these cars have.
public class Car
{
public int Id { get; set; }
public bool OnShift { get; set; }
public bool HasExtraBaggageSpace { get; set; }
}
public class Log
{
public DateTime TimeStamp { get; set; }
public int CarId { get; set; }
public bool HasExtraBaggageSpace { get; set; }
}
Every five minutes the app selects all the cars on shift and writes the information to a log object and inserts them into a List Logs.
After three weeks of logging I would now like to return a number which reflects the average of the last three weeks . Example:
How many cars with HasExtraBaggageSpace can I expect on a thursday at 14:00.
public class myApp
{
public class AverageReturnArgs
{
public int Hour { get; set; }
public int Minute { get; set; }
public int Count { get; set; }
}
public AverageReturnArgs GetAverage(List<Log> logs, DateTime TimeReq)
{
int hour = TimeReq.Hour;
int min = TimeReq.Minute;
var average = logs.GroupBy(grpByHourMin => new
{
hour = grpByHourMin.TimeStamp.Hour,
min = grpByHourMin.TimeStamp.Minute
}).Select(av => new AverageReturnArgs()
{
Hour = av.Key.hour,
Minute = av.Key.min,
Count = av.Average(x => x.HasExtraBaggageSpace)
});
}
}
This is producing a compiler error.
Count = av.Average(x => x.HasExtraBaggageSpace)
Any ideas how I could accomplish this?
How would you calculate the average of boolean values ?
I think the Count aggregate should be what you are looking for:
Count = av.Count(x => x.HasExtraBaggageSpace)
EDIT If you mean to calculate the percentage of cars having ExtraBaggageSpace you may try something like this :
Count = av.Average(x => x.HasExtraBaggageSpace ? 1 : 0)
With use of the ternary operator this expression convert your boolean value to an integer and calculate the average (that will be a Double).
EDIT 2
Here is what your line should look like.
Count should be made of type Double.
Count = av.Average(x => av.Count(y=>y.HasExtraBaggageSpace))
EDIT 3
Ok the logic was all wrong :
public AverageReturnArgs GetAverage(List<Log> logs, DateTime TimeReq)
{
int hour = TimeReq.Hour;
int min = TimeReq.Minute;
var average = logs
.Where(log => log.TimeStamp.Hour == hour && log.TimeStamp.Minute == min)
.GroupBy(grp => grp.TimeStamp)
.Select(av => new AverageReturnArgs()
{
Hour = hour,
Minute = min,
Count = av.Average(x => av.Count(y=>y.HasExtraBaggageSpace))
});
}

How to set values of entities

I have module which is not mapped to database( sql server) and is only use to generate report.
public class Report
{
public int USERID { get; set; }
public DateTime DateToCal { get; set; }
public string Name { get; set; }
public string Position { get; set; }
public TimeSpan? Intime { get; set; }
public TimeSpan? OutTime { get; set; }
}
I generate a query and fill some properties(USERID, DateToCal, Name, Position, Intime) of Report and some properties of Report remains null ( as OutTime is null)
var query = .....;
Now what I want iterate on items of query( of type Report) and set value for null properties OutTime as
foreach(var items in query)
{
var outtime= from x in con.CHECKINOUTs
where x.USERID == items.USERID && EntityFunctions.TruncateTime(x.CHECKTIME) == EntityFunctions.TruncateTime(items.DateToCal && x.CHECKTYPE == "O"
select x.CHECKTIME
.Single();
items.OutTime= outtime.TimeOfDay;
}
Now problem is, on mousehover to items.OutTime with in foreach there appear value but if I out from foreach and mousehover to query there is still OutTime is null. There not appear value what I set. Is this is possible to set value of entities such way. Or what is my problem?
Thank you.
Save query results locally before iterating over them:
var query = ....ToList();
Looks like in your case query executed two times - first time when you are updating OutTime property, and second time when you are iterating over query items (either looking in debugger or displaying in UI). So, when query executed second time, you see completely new set of objects as query result (which have original null values of OutTime).
BTW Consider to use single JOIN query instead of making separate outtime query for each item in your main query.
Try code something like this:
public class Report
{
public int USERID { get; set; }
public DateTime DateToCal { get; set; }
public string Name { get; set; }
public string Position { get; set; }
private TimeSpan? _intime;
public TimeSpan Intime {
get { return _intime ?? new TimeSpan(0); }
set { _intime = value; }
}
private TimeSpan? _outTime;
public TimeSpan OutTime
{
get { return _outTime ?? new TimeSpan(0); }
set { _outTime = value; }
}
}

Categories