Outputting anonymous types from Linq GroupJoin - c#

I'm trying to output an anonymous type from a GroupJoin in LINQ but one query works and another one doesn't.
The only difference is in the first query the starting service is an in memory IGrouping (a member from a previous call to GroupBy) instead of a direct database query.
The error I'm getting is
Error 8 The type arguments for method 'System.Linq.Enumerable.GroupJoin(System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerable, System.Func, System.Func, System.Func,TResult>)' cannot be inferred from the usage. Try specifying the type arguments explicitly. C:\Users\wbutt\Documents\Visual Studio 2013\Projects\ServiceGraphMonitor\Controllers\ServiceQueueController.cs 148 30 ServiceGraphMonitor
This is the query that doesn't work:
var result = index.GroupJoin(service.GroupBy(
s => new
{
h = s.orig_que_dt.Hour,
s = s.orig_que_dt.Minute / 15
}
), i => i, s => s.Key,
(i, s) => new
{
x = new DateTime(start_date.Year, start_date.Month, start_date.Day, i.h, i.m * 15, 0),
y = s.Count()
}
);
This one works:
var result = index.GroupJoin(db.t_svc_task_sta.Where(ts =>
ts.t_svc_task.orig_que_dt > start_date &&
ts.t_svc_task.orig_que_dt < end_date
).GroupBy(ts => new
{
h = ts.t_svc_task.orig_que_dt.Hour,
m = ts.t_svc_task.orig_que_dt.Minute / 15
}
), i => i, ts => ts.Key,
(i, ts) => new // DTO.ServiceReport()
{
time = new DateTime(start_date.Year, start_date.Month, start_date.Day, i.h, i.m * 15, 0).ToUniversalTime(),
queue_time = ts.Sum(t => t.Where(x => x.svc_task_sta_cd == 1 && x.sta_end_dt <= DateTime.Now).DefaultIfEmpty(new t_svc_task_sta() {sta_start_dt = DateTime.Today, sta_end_dt = DateTime.Today }).Average(x => (x.sta_end_dt - x.sta_start_dt).TotalSeconds)),
exec_time = ts.Sum(t => t.Where(x => x.svc_task_sta_cd == 2 && x.sta_end_dt <= DateTime.Now).DefaultIfEmpty(new t_svc_task_sta() {sta_start_dt = DateTime.Today, sta_end_dt = DateTime.Today }).Average(x => (x.sta_end_dt - x.sta_start_dt).TotalSeconds)),
volume = ts.Sum(t => t.Count(x => x.svc_task_sta_cd == 1))
}
);

Related

The proper place to add try-catch C#

I want to use try-catch to check being divided by 0 in
((double)o.Close - (double)subList.Skip(idx - t + 1).First().Close) /
(double)subList.Skip(idx - t + 1).First().Close
...but I don't know where to add try-catch. I try to add including the whole var newList =, but it is not allowed, since variable newList no longer exists later. So where is the proper position to add try-catch?
var newList = data.GroupBy(o => o.Date).Where(o => o.Key <= Beginday).OrderByDescending(o => o.Key).Take(Y).SelectMany(o => o).GroupBy(x => new { x.Symbol })
.Select
(
x =>
{
var subList = x.OrderBy(y => y.Date).ToList();
return subList.Select((y, idx) =>
{
return new
{
Symbol = y.Symbol,
Close = y.Close,
Date = y.Date,
Vol = (idx < t - 1) ? 0 : new DescriptiveStatistics(subList.Skip(idx - t + 1).Take(t)
.Select(o =>
((double)o.Close - (double)subList.Skip(idx - t + 1).First().Close) / (double)subList.Skip(idx - t + 1).First().Close).ToList()).StandardDeviation,
};
});
}
)
.SelectMany(x => x)
.ToList();
depends on what you're going to do in your catch - if you want newList to have a valid value even if the divisor is 0 then you should probably have your 0 check integrated into the main code flow rather than using exceptional flows.
However, to directly answer your question as written, explicity declare newList to a default value first, then reassign within a try/catch block (place the entirety of the code you've written inside the try, with the explicit default declaration before the try). Then newList will exist when you need to use it later.
If you want to catch everything then
try
{
var newList = data.GroupBy(o => o.Date).Where(o => o.Key <= Beginday).OrderByDescending(o => o.Key).Take(Y).SelectMany(o => o).GroupBy(x => new { x.Symbol })
.Select
(
x =>
{
var subList = x.OrderBy(y => y.Date).ToList();
return subList.Select((y, idx) =>
{
return new
{
Symbol = y.Symbol,
Close = y.Close,
Date = y.Date,
Vol = (idx < t - 1) ? 0 : new DescriptiveStatistics(subList.Skip(idx - t + 1).Take(t)
.Select(o =>
((double)o.Close - (double)subList.Skip(idx - t + 1).First().Close) / (double)subList.Skip(idx - t + 1).First().Close).ToList()).StandardDeviation,
};
});
}
)
.SelectMany(x => x)
.ToList();
}
catch(Exception ex)
{
//your error
}
You can certainly do this:
var newList = new[]
{
new { Symbol = "", Close = 0.0, Date = DateTime.Now, Vol = 0 }
}.Take(0).ToList();
try
{
var newList = data.GroupBy(o => o.Date).Where(o => o.Key <= Beginday).OrderByDescending(o => o.Key).Take(Y).SelectMany(o => o).GroupBy(x => new { x.Symbol })
.Select
(
x =>
{
var subList = x.OrderBy(y => y.Date).ToList();
return subList.Select((y, idx) =>
{
return new
{
Symbol = y.Symbol,
Close = y.Close,
Date = y.Date,
Vol = (idx < t - 1) ? 0 : new DescriptiveStatistics(subList.Skip(idx - t + 1).Take(t)
.Select(o =>
((double)o.Close - (double)subList.Skip(idx - t + 1).First().Close) / (double)subList.Skip(idx - t + 1).First().Close).ToList()).StandardDeviation,
};
});
}
)
.SelectMany(x => x)
.ToList();
}
catch (Exception ex)
{
}
So long as the names and types are the same in the two anonymous types then this works fine.

How to convert IEnumerable<CustomType> to List, applying grouping logic, and convert back to IEnumerable<CustomType>?

I want to take a list of items from a table, apply grouping logic(grouping by assignment type, start and end time), and then convert back to starting type.
IEnumerable<Assignment> assignments = UnitOfWork.AssignmentRepository.Get(filter: a => a.Start.CompareTo(start) >= 0
&& a.End.CompareTo(end) <= 0,
orderBy: o => o.OrderBy(a => a.Start));
List<Assignment> pre_alerts = new List<Assignment>();
foreach (Assignment a in assignments)
{
foreach (AssignmentType t in assignmentTypes)
{ // determine if the assignment's type has a minimum required
if (a.AssignmentTypeID == t.AssignmentTypeID && t.Minimum>0)
{
// get the minimum assignments requierd
int min_required = t.Minimum;
DateTime s = a.Start;
DateTime e = a.End;
AssignmentType type = a.AssignmentType;
IEnumerable<Assignment> minimum_assignments = UnitOfWork.AssignmentRepository.Get(filter: m => m.Start.CompareTo(s) >= 0 && m.End.CompareTo(e) <= 0 && m.AssignmentTypeID == a.AssignmentTypeID, orderBy: o => o.OrderBy(m => m.Start));
int min_actual = minimum_assignments.Count();
if (min_actual < min_required)
{
// add a single alert for time range and assignment type
pre_alerts.Add(a);
}
}
}
}
var alerts = pre_alerts.GroupBy(x => new Assignment{ x.AssignmentTypeID, x.Start, x.End }).Select(k => k);
how do I convert alerts to IEnumerable<Assignment> ?
When using Select you are already returning it as an IEnumerable. If you want it to be of your type then:
var alerts = pre_alerts.GroupBy(x => new Assignment{ x.AssignmentTypeID, x.Start, x.End }).Select(k => k.Key);
The result of a GroupBy is an IGrouping so by Selecting just k you are returning an IEnumerable<IGrouping<Assignment...>>
But it feels to me that if that is the case then there is no reason to use the GroupBy but just Select to create the assignments and then distinct

C# LINQ to create a temporary variable with dynamic index

var newList = data.GroupBy(x => new { x.Symbol })
.Select
(
x =>
{
var subList = x.OrderBy(y => y.Date).ToList();
return subList.Select((y, idx) => new
{
Symbol = y.Symbol,
Close = y.Close,
Date = y.Date,
/*p = (idx < 1) ? null : subList.Skip(idx - 1)
.Take(2).Select(o => o.Close).ToList()*/,
Vol = (idx < 1) ? 0 : new DescriptiveStatistics
(subList.Skip(idx - 1).Take(2).Select(o => (double)o.Close/(double)
subList.ElementAt(idx - 1).Close).ToList()).StandardDeviation,
});
}
)
.SelectMany(x => x)
.ToList();
I want create a variable p = (idx < 1) ? null : subList.Skip(idx - 1).Take(2).Select(o => o.Close).ToList() with the same index idx of Vol but do not appear in the return just a temporary variable(but have to use the synchronous idx as Vol). So where to write down this p or how to change the syntax
You can hold a temp variable, just like you have done with subList although I dont have a test structure to build against something like this below should work.
var newList = data.GroupBy(x => new { x.Symbol })
.Select
(
x =>
{
var subList = x.OrderBy(y => y.Date).ToList();
return subList.Select((y, idx) => { //return is a function not an object
var p = (idx < 1) ? null : subList.Skip(idx - 1).Take(2).Select(o => o.Close).ToList(); //your p
return new //this return returns the object definition
{
Symbol = y.Symbol,
Close = y.Close,
Date = y.Date,
Vol = p == null ? 0 : new DescriptiveStatistics(subList.Skip(idx - 1).Take(2).Select(o => (double)o.Close / (double)subList.ElementAt(idx - 1).Close).ToList()).StandardDeviation,
};
});
}
)
.SelectMany(x => x)
.ToList();

Preserve order with linq after groupby and selectmany

Is there a way to preserve the order after this linq expression?
var results =
DateList
.GroupBy(x => x.Date.Subtract(firstDay).Days / 7 + 1)
.SelectMany(gx => gx, (gx, x) => new {Week = gx.Key,DateTime =x,Count = gx.Count(),});
I found this Preserving order with LINQ , but I'm not sure if its the GroupBy or SelectMany casing the issues
Yes, if you first select your DateList and combine it with an index, using an overload of .Select that uses a delegate with a second (int) parameter that is called with the index of the items from the sequence :
DateList
.Select((dateTime, idx) => new {dateTime, idx})
.GroupBy(x => x.dateTime.Date.Subtract(firstDay).Days / 7 + 1)
...and persist the value through the linq chain
.SelectMany(gx => gx, (gx, x) => new {Week = gx.Key,
DateTime = x.dateTime,
Count = gx.Count(),
x.idx})
...then use it to re-order the output
.OrderBy(x => x.idx)
...and strip it from your final selection
.Select(x => new {x.Week, x.DateTime, x.Count});
then you can maintain the same order as the original list.
Solution of #spender is good, but can it be done without OrderBy? It can, because we can use the index for direct indexing into array, but it would not be one linq query:
var resultsTmp =
DateList.Select((d, i) => new { d, i })
.GroupBy(x => x.d.Date.Subtract(firstDay).Days / 7 + 1)
.SelectMany(gx => gx, (gx, x) => new { Week = gx.Key, DateTime = x.d, Count = gx.Count(), x.i })
.ToArray();
var resultsTmp2 = resultsTmp.ToArray();
foreach (var r in resultsTmp) { resultsTmp2[r.i] = r; };
var results = resultsTmp2.Select(r => new { r.Week, r.DateTime, r.Count });
It looks a bit complex. I would probably do something more straightforward like:
var DateList2 = DateList.Select(d => new { DateTime = d, Week = d.Subtract(firstDay).Days / 7 + 1 }).ToArray();
var weeks = DateList2.GroupBy(d => d.Week).ToDictionary(k => k.Key, v => v.Count());
var results = DateList2.Select(d2 => new { d2.Week, d2.DateTime, Count = weeks[d2.Week] });

I want to print Count using Groupby and Left join in linq

I having two list or table as per below:
Query:
var q = db.tbl_User_to_CustomerMast
.Where(i => i.fk_Membership_ID == m.MembershipID)
.Join(
db.tbl_CustomerMast,
u => u.fk_Customer_ID,
c => c.CustomerID,
(u, c) => new { UserCustomer = u, Customer = c })
.Where(i => i.UserCustomer.fk_Store_ID == shopid).ToList();
Output:
List A:
User_Customer_ID Name
===================================
1 XYZ
2 ABC
Query:
var rewards = q.Join(
db.tbl_RewardAwardMast,
i => i.UserCustomer.User_Customer_ID,
j => j.fk_Customer_UserID,
(i, j) => new { Customer = i, Reward = j })
.Where(i => i.Reward.RewardDate >= i.Customer.UserCustomer.Membership_Start)
.GroupBy(i => i.Reward.fk_Customer_UserID)
.Select(i => new { CustomerID = i.Key, RewardCount = i.Count()})
.ToList();
Output:
List B:
User_Customer_ID RewardCount
===================================
1 5
Here is final Output Table
User_Customer_ID Name RewardCount
===============================================
1 XYZ 5
2 ABC 0
If I want to check that which user_customer_ID has less than 5 Reward Count, How I will Check:
Query:
var final = q.GroupJoin(
rewards,
i => i.UserCustomer.User_Customer_ID,
j => j.CustomerID,
(i, j) => new { Customer = i, Reward = j.DefaultIfEmpty() })
.Select(i => new { Count = i.Reward, id = i.Customer.UserCustomer.User_Customer_ID })
.ToList();
var final1 = final.Where(i => i.Count < m.MembershipMinVisit.Value).ToList();
Error:
Operator '<' cannot be applied to operands of type 'System.Collections.Generic.IEnumerable' and 'int'
You don't need a group join here as for each customer you need a single result (reward). Also because you need only customers with rewards < 5, an inner join using that condition wil give you what you want:
var final = q.Join( // Join instead of GroupJoin
rewards.Where(r => r.RewardCount < 5), // filter out rewards >= 5
i => i.UserCustomer.User_Customer_ID,
j => j.CustomerID,
(i, j) => new { Customer = i, Reward = j })
.Select(i => new {
Reward = i.Reward, // 'Count' is a bad name
// it is still the reward object
id = i.Customer.UserCustomer.User_Customer_ID
})
.ToList();
In your original query, Count (bad name) is a collection (IEnumerable) of awards, that's why you get that error. To fix it, you have to check that the single returned reward is not null (to filter out users without rewards at all, because you use a left join) and that it has a RewardCount less that 5:
var final1 = final.Where(i => i.Count.Single() != null &&
i.Count.Single().RewardCount < 5)
.ToList();

Categories

Resources