List.GroupBy<> error using LInq - linq

I'm trying to group a generic List<> in C#. The code compiles, but the application (Silverlight) throws the following error (CharOpps is the class of objects in the list I'm trying to group):
Unhandled Error in Silverlight Application Unable to cast object of type 'Grouping[System.DateTime,Invoc_SalesDashboard.ChartOpps]' to type 'Invoc_SalesDashboard.ChartOpps'.
Here's the code:
var newtemplist = list.GroupBy(opp =>
new DateTime(opp.EstimatedCloseDate.Year, opp.EstimatedCloseDate.Month, 1)).OrderBy(opp => opp.Key);
I've also tried:
var newtemplist =
from opp in list
orderby opp.EstimatedCloseDate
group opp by new { opp.EstimatedCloseYear, opp.EstimatedCloseMonth };
ChartOpps have a revenue value, and the EstimatedCloseDate value. What I'm hoping to end up with is a list of ChartOpps with the revenue aggregated in the year/month groupings.
foreach (ChartOpps c in newtemplist)
{
ErrorBox.Text += "o";
}

You're not showing us what you're doing with the result newtemplist. The runtime error message indicates that you are taking a group and trying to treat it as an instance of ChartOpps which is clearly impossible. Show us that code and we can help you fix it.
Edit:
Okay, now the problem is clear. To enumerate the results of the grouping, you need to proceed as follows:
foreach(var group in newtemplist) {
foreach(ChartOpps c in group) {
// do something with c here
}
}
The result of newtemplist is a sequence of sequences, each sequence having all of its elements having the same value of new DateTime(opp.EstimatedCloseDate.Year, opp.EstimatedCloseDate.Month, 1). Therefore, to enumerate this sequence of sequences, you first have to enumerate the groups, and then within each group enumerate the instances of ChartOpps.

Not knowing anything about your class structure, here is a basic attempt:
List<CharOpps> list = GetList();
var newtemplist =
from opp in list
group opp by opp.EstimatedCloseYear into g
select new { g = g.Key, CharOpps = g };

If you take the var out of the picture, it all becomes clear.
IEnumerable<IGrouping<DateTime, ChartOpps>> newtemplist = list
.GroupBy(opp => new DateTime(
opp.EstimatedCloseDate.Year,
opp.EstimatedCloseDate.Month,
1))
.OrderBy(opp => opp.Key);
foreach (ChartOpps c in newtemplist)
{
ErrorBox.Text += "o";
}
The error occurs in the assignment of the first element of newtemplist to c. c is allowed to reference ChartOpps instances. The first element of newtemplist is a IGrouping<DateTime, ChartOpps>, not a ChartOpps. The implicit cast in the foreach fails and you get a runtime exception.
Try instead:
foreach(IGrouping<DateTime, ChartOpps> g in newtemplist)
{
foreach (ChartOpps c in g)
{
ErrorBox.Text += "o";
}
}

Related

Building LINQ Expression Getting Ignored

I'm trying to build a linq query coming from a table grid from the client side, so im expecting page offset, page start, order and the traditional paging parameters. I have the following code:
[Route("api/settings/logs")]
public Rest.DatatablesResponse GetLogs(int draw, int start, int length) {
var query_string = Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);
var search = query_string["search.value"];
int order_column = int.Parse(query_string["order[0].column"]);
var order_direction = query_string["order[0].dir"];
var count = db.Logs.Count(q => q.Mode == 2);
var logs = (from l in db.Logs
where l.Mode == 2
select new {
id = l.ID,
mode = l.Mode,
phase_id = l.Phase.ID,
created = l.Created,
user = l.User.Name,
blender_name = l.Blender.Name,
oil_name = l.Oil,
oil_quantity = l.OilQuantity,
production_cycle_name = l.ProductionCycle.Name
});
if (order_direction == "asc") {
if (order_column == 0) logs.OrderBy(q => q.created);
else if (order_column == 2) logs.OrderBy(q => q.production_cycle_name);
} else {
if (order_column == 0) logs.OrderByDescending(q => q.created);
else if (order_column == 2) logs.OrderByDescending(q => q.production_cycle_name);
};
if (!string.IsNullOrEmpty(search)) {
logs.Where(q => q.blender_name.Contains(search) ||
q.oil_name.Contains(search) ||
SqlFunctions.StringConvert((decimal)q.id).Contains(search));
}
logs.Skip(start).Take(length);
DateTime dtDateTime = new DateTime(1970,1,1,0,0,0,0,System.DateTimeKind.Utc);
var steps = from l in logs.ToList()
select new {
id = l.id,
message = StringHelpers.FormatWith(_tpl_message[l.phase_id.ToString() + l.mode.ToString() ], l) ,
created = dtDateTime.AddSeconds(l.created).ToString("h:mmtt - MMMM d, yyyy"),
production_cycle_name = l.production_cycle_name
};
return new Rest.DatatablesResponse {
draw = draw,
recordsTotal = count,
recordsFiltered = count,
data = steps.ToArray()
};
}
My problem is the skip and take and orderby expressions are getting ignored for some reason, and this is the SQL code generated just before converting my linq expressions to a list. From my understanding, the query should not be executed or evaluated until my logs.ToList() call, so the ordering and take/skip should be taken into account, but it's not:
{SELECT
[Extent1].[ID] AS [ID],
[Extent1].[Mode] AS [Mode],
[Extent1].[Phase_ID] AS [Phase_ID],
[Extent1].[Created] AS [Created],
[Extent2].[Name] AS [Name],
[Extent3].[Name] AS [Name1],
[Extent1].[Oil] AS [Oil],
[Extent1].[OilQuantity] AS [OilQuantity],
[Extent4].[Name] AS [Name2]
FROM [dbo].[Steps] AS [Extent1]
LEFT OUTER JOIN [dbo].[Users] AS [Extent2] ON [Extent1].[User_Id] = [Extent2].[Id]
LEFT OUTER JOIN [dbo].[Blenders] AS [Extent3] ON [Extent1].[Blender_ID] = [Extent3].[ID]
LEFT OUTER JOIN [dbo].[ProductionCycles] AS [Extent4] ON [Extent1].[ProductionCycle_ID] = [Extent4].[ID]
WHERE 2 = [Extent1].[Mode]}
Irrelevant P.S. I'm using the not so clever ifs for building the order expression instead of using DynamicLINQ since i have only two sortable columns.
logs.Skip(start).Take(length);
Creates a IQueryable<T> where T is the same anonymous type of which logs is an IQueryable<T> but with start items skipped. Then from that it creates a similarly typed IQueryable<T> where lenght items are the most that will be taken.
Then it throws that away and lets it be garbage collected. (Or ideally the compiler or jitter steps will realise it's thrown away and cut out the whole thing).
Then logs.ToList() goes back to the logs you still have and creates a list from it.
You should replace the Skip and Take line with:
logs = logs.Skip(start).Take(length);
So that you are actually making use of this skipping and taking.
I'm using the not so clever ifs for building the order expression instead of using DynamicLINQ since i have only two sortable columns.
There's nothing particularly not-clever about that, except that you make the same mistake; apply the OrderBy and then throwing away the result instead of using it. Likewise with the Where. You need logs = logs.OrderBy(...) etc.
I'd also question from l in logs.ToList() select new {…} here.
It might be the best approach, if obtaining that list in one step has some advantage. However otherwise:
from l in logs select new {…}
Do the select work on the database, retrieving just what you need.
from l in logs.AsEnumerable() select new {…}
Do the select work in the application, appropriate if part of it cannot be converted to database work, but do it as it comes rather than loading it all into memory first.
from l in await logs.ToListAsync() select new {…}
Has the downside of ToList() but in asynchronous uses, then (assuming your provider has a ToListAsync() method) allows for awaiting.
ToList() is rarely the best option here.

Linq and group by clause

I am curious what the x is for in a a linq group by clause: group x by ...
The x can be replaced by an 1:
var query = from box in c.Boxes
join item in c.Items on box equals item.Box
group 1 by new { BoxId = box.BoxId, item.ItemType.ItemTypeId } into g
select new { g.Key.BoxId, g.Key.ItemTypeId, Count = g.Count() };
Does anybody have a sample where the x (or wathever local variable you chose in the group by ) is really of some value?
I mean
var query2 = from box in c.Boxes
group box by box.BoxId into q
select q.Key;
can be replaced by
var query2 = from box in c.Boxes
group 1 by box.BoxId into q
select q.Key;
That's the expression that determines what will go in the output of the group by clause.
You are actually not inspecting the whole output, but only the Keys, which are the same in the examples above, because the grouping is done by the same thing (box.BoxId).
However, replace the last line:
select q.Key;
with
select q;
You will notice that:
the group 1 by ... query will return an IGrouping<int, int> which will have all values set to 1
the group box by ... query will return an IGrouping<int, Box>, which will have all the boxId keys and, for each of them, the corresponding Box objects with respect to the grouping criteria.
In group x by... the x is what gets aggregated.
So you could write something like
var childrenByAge = from child in class
group getName(child) by child.Age;
This gives you a grouping containing names of children for each age.
Here is a simple example where you can test the difference easily:
static void Main(string[] args)
{
var grouping = from c in "Hello World!"
group c by c; // Replace 'group c by' with 'group 1 by'
// and compare results
foreach (var group in grouping)
{
Console.Write("KEY: {0} VALUES :", group.Key);
foreach (var value in group)
Console.Write(" {0}", value);
Console.WriteLine();
}
Console.ReadKey();
}

Multiple Counts within a single query

I want a list of counts for some of my data (count the number of open.closed tasks etc), I want to get all counts inside 1 query, so I am not sure what I do with my linq statement below...
_user is an object that returns info about the current loggedon user
_repo is am object that returns an IQueryable of whichever table I want to select
var counters = (from task in _repo.All<InstructionTask>()
where task.AssignedToCompanyID == _user.CompanyID || task.CompanyID == _user.CompanyID
join instructions in _repo.GetAllMyInstructions(_user) on task.InstructionID equals
instructions.InstructionID
group new {task, instructions}
by new
{
task
}
into g
select new
{
TotalEveryone = g.Count(),
TotalMine = g.Count(),
TotalOpen = g.Count(x => x.task.IsOpen),
TotalClosed = g.Count(c => !c.task.IsOpen)
}).SingleOrDefault();
Do I convert my object to single or default? The exception I am getting is, this sequence contains more than one element
Note: I want overall stats, not for each task, but for all tasks - not sure how to get that?
You need to dump everything into a single group, and use a regular Single. I am not sure if LINQ-to-SQL would be able to translate it correctly, but it's definitely worth a try.
var counters = (from task in _repo.All<InstructionTask>()
where task.AssignedToCompanyID == _user.CompanyID || task.CompanyID == _user.CompanyID
join instructions in _repo.GetAllMyInstructions(_user) on task.InstructionID == instructions.InstructionID
group task by 1 /* <<=== All tasks go into one group */ into g select new {
TotalEveryone = task.Count(),
TotalMine = task.Count(), // <<=== You probably need a condition here
TotalOpen = task.Count(x => x.task.IsOpen),
TotalClosed = task.Count(c => !c.task.IsOpen)
}).Single();
From MSDN
Returns the only element of a sequence, or a default value if the
sequence is empty; this method throws an exception if there is more
than one element in the sequence.
You need to use FirstOrDefault. SingleOrDefault is designed for collections that contains exactly 1 element (or none).

Reproduce a "DELETE NOT IN" SQL Statement via LINQ/Subsonic

I want to do something like DELETE FROM TABLE WHERE ID NOT IN (1,2,3) AND PAGEID = 9
I have a List of IDS but that could be changed if needs be. I can't work out how to get a boolean result for the LINQ parser.
Here is what Subsonic expects I think.
db.Delete(content => content.PageID == ID).Execute();
I can't work out how to do the NOT IN statement. I've tried the List.Contains method but something not quite right.
UPDATE: One alternative is to do:
var items = TABLE.Find(x => x.PageID == ID)'
foreach(var item in items)
{
item.Delete();
}
This hits the database a lot more though
When you say "something not quite right" what exactly do you mean?
I'd expect to write:
List<int> excluded = new List<int> { 1, 2, 3 };
db.Delete(content => !excluded.Contains(content.PageID)).Execute();
Note that you need to call Contains on the array of excluded values, not on your candidate. In other words, instead of saying "item not in collection" you're saying "collection doesn't contain item."
Try .Contains:
db.Delete(content => content.PageID.Contains(<Array containing ID's>).Execute();
(the above is just an example, might need some polishing for your specific situation)
I have found that this works but its not via LINQ
var table = new WebPageContentTable(_db.DataProvider);
var g = new SubSonic.Query.Delete<WebPageContent(_db.DataProvider)
.From(table)
.Where(table.ID)
.NotIn(usedID)
.Execute();
I have found that this does work and via LINQ - however it hits the database multiple times.
var f = WebPageContent.Find(x => !usedID.Any(e => e == x.ID));
if (f.Count > 0)
{
var repo = WebPageContent.GetRepo();
repo.Delete(f);
}
This I imagine would work in one hit to the database but I get an exception thrown in QueryVisitor::VisitUnary
WebPageContent.Delete(x => !usedID.Any(e => e == x.ID));

DataTable Query

I am new to LINQ. I am trying to find the rows that does not exists in the second data table.
report_list and benchmark both type are : DataTable. Both these datatables are being populated using OleDbCommand,OleDbDataAdapter. I am getting an error "Specified cast is not valid." in foreach ... loop. I would appreciate your help.
var result = from a in report_list.AsEnumerable()
where !(from b in benchmark.AsEnumerable()
select b.Field<int>("bench_id")
)
.Contains(a.Field<int>("BenchmarkID"))
select a;
foreach (var c in result)
{
Console.WriteLine(c.Field<string>("Name"));
}
I don't know if I understood your question. Are you trying to get the items that exists in the first table but not in the second?
var first = new string[] { "b", "c" };
var second = new string[] { "a", "c" };
//find the itens that exist in "first" but not in "second"
var q = from f in first
where !second.Contains(f)
select f;
foreach (var s in q) {
Console.WriteLine(s);
}
//Prints:
//b
I suggest you to make the inner query first, once it does not depend on the outer record.
From a in report_list
Group Join b in benchmark On a.bench_id Equals b.bench_id Into g = Group
Where g.Count = 0
Select a
Note that this is VB syntax.
My suspicion is that one of the fields you are comparing is not an integer in the database. I believe that the invalid cast exception is being thrown by one of the Field<int>() calls since that is one of the three different exceptions that this method can throw. See docs here.
Perhaps use the .Except() extension to get the set difference of the two sets?
(from b in benchmark.AsEnumerable()
select new { id = b.Field<int>("bench_id")}).Except(
from a in report_list.AsEnumerable()
select new {id = a.Field<int>("BenchmarkID")})
Not actually sure of the precise syntax, but that should work by taking the ids in benchmark, and then removing all equivalent ids in report_list, leaving only the ids that don't match. (I hope this is the order you were after...)
Note: This is also assuming that the above issue mentioned by tvanfosson isn't also a problem

Resources