Here is my code:
IEnumerable<ServiceTicket> troubletickets = db.ServiceTickets.Include(t => t.Company).Include(t => t.UserProfile);
var ticketGroups = new Dictionary<string, List<ServiceTicket>>();
ticketGroups = troubletickets
.GroupBy(o => o.DueDate).ToDictionary(
group => {
var firstOrDefault = #group.FirstOrDefault();
return firstOrDefault != null
? firstOrDefault.DueDate.HasValue
? firstOrDefault.DueDate.Value.ToShortDateString()
: ""
: "";
},
group => group.ToList()
).OrderBy(g => g.Key).ToDictionary(g => g.Key, g => g.Value);
The error that I am getting is: 'An item with the same key has already been added.' This is because the DueDate value is occasionally repeated. My question is how can I keep the key from being added if it already exists in the dictionary?
It seems that you are grouping by one value (the DueDate value), but using a different value as the dictionary key.
Can you not just use the custom code for grouping instead?
ticketGroups = troubletickets
.GroupBy(o => o.DueDate.HasValue
? o.DueDate.Value.ToShortDateString()
: "")
.ToDictionary(g => g.Key, g => g.ToList());
Note that I took our the superfluous OrderBy and second ToDictionary call - I assumed you were trying to "order" the dictionary which won't work as a plain dictionary is not ordered.
You get duplicate keys because there are two ways to get an empty string as key, either an empty group, or an empty date. The duplicate will always be the empty string. I wonder if you really intended to get an empty string as key when the group is empty. Anyway, it's not necessary, you can always filter empty groups later.
It's easier to group by date (including null) first through the database engine and then apply string formatting in memory:
IQueryable<ServiceTicket> troubletickets = db.ServiceTickets
.Include(t => t.Company)
.Include(t => t.UserProfile);
Dictionary<string, List<ServiceTicket>> ticketGroups =
troubletickets
.GroupBy(ticket => ticket.DueDate)
.AsEnumerable() // Continue in memory
.ToDictionary(g => g.Key.HasValue
? g.Key.Value.ToShortDateString()
: string.Empty,
g => g.Select(ticket => ticket));
Now the grouping is by the Key value, not by the First element in the group. The Key is never null, it's always a Nullable<DateTime>, with or without a value.
Side note: you'll notice that EF will not generate a SQL group by statement, that's because the SQL statement is "destructive": it only returns grouped columns and aggregate data, not the individual records that a LINQ GroupBy does return. For this reason, the generated SQL is pretty bloated and it may enhance performance if you place the AsEnumerable before the .GroupBy.
Related
i have this hashtable that i am converting to dictionary on the same line
Hashtable ids = new Hashtable();
ids = new Hashtable(_AppContext.TBL_PERSON.Where(oItem => oItem.DELETED == false).ToDictionary(o => o.CODE.ToUpper(), o => o.PERSON_ID));
thing is i am getting an error
"An item with the same key has already been added."
after Checking rows it turns out that CODE column has same row value multiple times.
is there a way to select only first value that occurs like First() but without making it first datatable then changing it to hashtable ?
Sure - use GroupBy and then pick the First() object in the group for the value:
ids = new Hashtable(
_AppContext.TBL_PERSON.Where(oItem => oItem.DELETED == false)
.GroupBy(o => o.CODE.ToUpper())
.ToDictionary(g => g.Key, g => g.First().PERSON_ID)
);
Keep in mind this gives you a HashTable of KeyValuePair<T,U> objects, which seems odd. If you just want the dictionary you can still use GroupBy and just remove the outer HashTable creation.
Hello this is a LINQ Query but it doesn't sort properly because four different dates are involved.
var EventReportRemarks = (from i in _context.pm_main_repz
.Include(a => a.PM_Evt_Cat)
.Include(b => b.department)
.Include(c => c.employees)
.Include(d => d.provncs)
where i.department.DepartmentName == "Finance"
orderby i.English_seen_by_executive_on descending
orderby i.Brief_seen_by_executive_on descending
orderby i.French_seen_by_executive_on descending
orderby i.Russian_seen_by_executive_on descending
select i).ToList();
All i want is that it should somehow combine the four dates and sort them in group not one by one.
For Example, at the moment it sorts all English Reports based on the date that executive has seen it, then Brief Report and So on.
But i want that it should check which one is seen first and so on. For example if the first report which is seen is French, then Brief, then English then Russian, so it should sort it accordingly.
Is it Possible??
You need to have them all in one column. The approach I would do, assuming that the value of the respective cells is null, when you don't want them to show up in the order by:
var EventReportRemarks = (from i in _context.pm_main_repz
.Include(a => a.PM_Evt_Cat)
.Include(b => b.department)
.Include(c => c.employees)
.Include(d => d.provncs)
where i.department.DepartmentName == "Finance"
select new
{
Date =
(
i.English_seen_by_executive_on != null ? i.English_seen_by_executive_on :
i.Brief_seen_by_executive_on != null ? i.Brief_seen_by_executive_on :
i.French_seen_by_executive_on != null ? i.French_seen_by_executive_on :
i.Russian_seen_by_executive_on
)
}).ToList().OrderBy(a => a.Date);
In the select clause you could add more columns if you whish.
Reference taken from here.
Why not just use .Min() or .Max() on the dates and then .OrderBy() or .OrderByDescending() based on that?
Logic is creating a new Enumerable (here, an array) with the 4 dates for the current line, and calculate the Max/Min of the 4 dates: this results in getting the latest/earliest of the 4. Then order the records based on this value.
var EventReportRemarks = (from i in _context.pm_main_repz
.Include(a => a.PM_Evt_Cat)
.Include(b => b.department)
.Include(c => c.employees)
.Include(d => d.provncs)
where i.department.DepartmentName == "Finance"
select i)
.OrderBy(i => new[]{
i.English_seen_by_executive_on,
i.Brief_seen_by_executive_on,
i.French_seen_by_executive_on,
i.Russian_seen_by_executive_on
}.Max())
.ToList();
Your problem is not a problem if you use method syntax for your LINQ query instead of query syntax.
var EventReportRemarks = _context.pm_main_repz
.Where(rep => rep.Department.DepartmentName == "Finance")
.OrderByDescending(rep => rep.English_seen_by_executive_on)
.ThenByDescending(rep => rep.Brief_seen_by_executive_on)
.ThenByDescending(rep => rep.French_seen_by_executive_on descending)
.ThenByDescending(rep => resp.Russian_seen_by_executive_on descending)
.Select(rep => ...);
Optimization
One of the slower parts of a database query is the transport of selected data from the DBMS to your local process. Hence it is wise to limit the transported data to values you actually plan to use.
You transport way more data than you need to.
For example. Every pm_main_repz (my, you do love to use easy identifiers for your items, don't you?), every pm_main_repz has zero or more Employees. Every Employees belongs to exactly one pm_main_repz using a foreign key like pm_main_repzId.
If you use include to transport pm_main_repz 4 with his 1000 Employees every Employee will have a pm_main_repzId with value 4. You'll transport this value 1001 times, while 1 time would have been enough
Always use Select to select data from the database and Select only the properties you actually plan to use. Only use Include if you plan to update the fetched objects
Consider using a proper Select where you only select the items that you actually plan to use:
.Select(rep => new
{
// only Select the rep properties you actually plan to use:
Id = rep.Id,
Name = rep.Name,
...
Employees = rep.Employees.Select(employee => new
{
// again: select only the properties you plan to use
Id = employee.Id,
Name = employee.Name,
// not needed: foreign key to pm_main_repz
// pm_main_repzId = rep.pm_main_repzId,
})
.ToList(),
Department = new
{
Id = rep.Department,
...
}
// etc for pm_evt_cat and provencs
});
I'm really fighting to understand how aggregate works, and I have a solution that maps an IEnumerable to a newer C# 7 Tuple.
I'm thinking I could understand this a little bit more if this were written as Linq Sql Syntax.
Would anyone like to take a stab at it?
IEnumerable<(string Key, string Value)> many = DataToPivot();
(string XXXX, string YYYY, string ZZZZ) agg =
many.Aggregate((XXXX: default(string),
YYYY: default(string),
ZZZZ: default(string)),
(a, i) =>
{
switch (i.Key)
{
case "xxxx":
return (i.Value, a.YYYY, a.ZZZZ);
case "yyyy":
return (a.XXXX, i.Value, a.ZZZZ);
case "zzzz":
return (a.XXXX, a.YYYY, i.Value);
default:
return a;
}
});
As far as I know Aggregate doesn't have a query syntax (for more info see documentation). The documentation should also be able to explain how the function works.
The overload you're using is taking the initial value of the aggregate (1st argument), and applying the accumulation function (2nd argument) to each element, returning the intermediate aggregate value. So your example produces 3 strings from the input data basically returning the last string value for each key (or default(string) when input data doesn't contain any items for that key).
If this is your requirement you don't (and shouldn't) need to use the Aggregate function, because you are not aggregating. You can get identical results with the following example (assuming all keys are present in the many input):
IEnumerable<(string Key, string Value)> many = DataToPivot();
var d = many.GroupBy(i => i.Key)
.ToDictionary(g => g.Key, g => g.Last().Value);
(string XXXX, string YYYY, string ZZZZ) agg = (d["xxxx"], d["yyyy"], d["zzzz"]);
If the tuple is not required the following handles also cases where a key is not present in the data set at all (the defaults will be returned if the key doesn't exist):
d.TryGetValue("xxxx", out string x);
d.TryGetValue("yyyy", out string y);
d.TryGetValue("zzzz", out string z);
Aggregate would be used e.g. for string concatenation - but there you would go with String.Join() instead:
many.GroupBy(i => i.Key)
.ToDictionary(g => g.Key, g => string.Join(",", g));
If you would still want to use Aggrergate you can rewrite it like this:
many.GroupBy(i => i.Key)
.ToDictionary(g => g.Key, g => g.Aggregate((a, i) => i));
This is basically Last() implemented using Aggregate(); and with TryGetValue you can get what you need.
On a bit more general note: using this approach you can accommodate multiple key values without needing to specifically code them. In that case you might not even need the ToDictionary call, e.g. like this:
many.GroupBy(i => i.Key)
.Select(g => new { g.Key, Result = g.Aggregate((a, i) => i) })
.ToList();
** Found the actual problem that was happening...due to records it wasn't obvious at first this was the case LINQ Query returns multiple copies of first result **
As the title says, the "pgnIDs.Contains(item.pgn)" part below is only selecting based on the first element of the hashset. I'd expect it to select a record if "item.pgn" is ANY of the values in pgnIDs.
HashSet<string> pgnIDs = new HashSet<string> { "EFFA", "EFFF", "FEE8", "FEE6", "FEF3", "FFF8", "FFFF" };
//...
var innerQ = (db1.can_raw_data_grainquality_2017
.Where(item => item.ref_id == q.ref_id &&
pgnIDs.Contains(item.pgn))
.OrderBy(item => item.ts_sec)
.ThenBy(item => item.ts_usec)
.Select(item => item)).ToList();
** update **
I tried with a straight "OR" for two of the values in the hashset and LINQ still only selected based on one of them. Is this behavior expected? Either one of the values below will work alone (in that records will be found).
var innerQ = (db1.can_raw_data_grainquality_2017
.Where(item => item.ref_id == 29225 &&
(item.pgn == "FFF8" || item.pgn == "EFFA"))
.OrderBy(item => item.ts_sec)
.ThenBy(item => item.ts_usec)
.Select(item => item.pgn)).ToList();
Per Ivan's comment (thanks!) I got the raw SQL query out and even tried it in SSMS. It did in fact work as expected in SSMS/raw SQL???
SELECT
[Extent1].[pgn] AS [pgn]
FROM (SELECT
[can_raw_data_grainquality_2017].[ts_sec] AS [ts_sec],
[can_raw_data_grainquality_2017].[ts_usec] AS [ts_usec],
[can_raw_data_grainquality_2017].[channel] AS [channel],
[can_raw_data_grainquality_2017].[mid] AS [mid],
[can_raw_data_grainquality_2017].[pgn] AS [pgn],
[can_raw_data_grainquality_2017].[sa] AS [sa],
[can_raw_data_grainquality_2017].[dlc] AS [dlc],
[can_raw_data_grainquality_2017].[d0] AS [d0],
[can_raw_data_grainquality_2017].[d1] AS [d1],
[can_raw_data_grainquality_2017].[d2] AS [d2],
[can_raw_data_grainquality_2017].[d3] AS [d3],
[can_raw_data_grainquality_2017].[d4] AS [d4],
[can_raw_data_grainquality_2017].[d5] AS [d5],
[can_raw_data_grainquality_2017].[d6] AS [d6],
[can_raw_data_grainquality_2017].[d7] AS [d7],
[can_raw_data_grainquality_2017].[ref_id] AS [ref_id]
FROM [dbo].[can_raw_data_grainquality_2017] AS [can_raw_data_grainquality_2017]) AS [Extent1]
WHERE (29225 = [Extent1].[ref_id]) AND ([Extent1].[pgn] IN (N'FFF8',N'EFFA'))
ORDER BY [Extent1].[ts_sec] ASC, [Extent1].[ts_usec]
I get data from my db using linq. I would like to use a GroupBy and an OrderBy on the same Request.
I actually have this code which doesn't work:
var mydata = _db.table1
.Join(_db.table2, h => h.col1, t => t.col2,
(h, t) => new ActivatedScripts()
{
col1 = h.col1, col2 = h.col2, col3 = (t.col3 == "Y"), col4 = ""
})
.GroupBy(z => z.col1).OrderBy(s => s.....);
the OrderBy(s => s...) suggest me all the LINQ method and a famous KEY which doesn't match any column in my code.
EDIT :
I follow the official tutorial to sort my table (http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/sorting-filtering-and-paging-with-the-entity-framework-in-an-asp-net-mvc-application).
When I groupBy then sort (I tried to sort then groupby) I have an exception :
The GroupBy method returns a collection of groups. You can easily sort on the col1 column as that is the Key property of each group:
.OrderBy(s => s.Key)
If you want to sort on anything else, you have to get that value from the items in the group. If you for example know that a value is the same for all items in each group, you can get the value from the first item in the group:
.OrderBy(s => s.First().col2)
You can also use aggregates to calculate values using all the items in the group, for example sorting on the sum of the values:
.OrderBy(s => s.Sum(x => x.col2))
You can always order first and then group the ordered data:
.Join(...).OrderBy(x => x.col1).GroupBy(x => x.col2)