Additional square brackets in Linq result - c#

In my DB, I have the following data:
track_id, point_id, latd, lond
A,1,12,100
A,2,13,101
B,1,10,90
B,2,13,90
I am trying to generate a IEnumerable<object[]> as follow:
[[12, 100],[13, 101]], [[10, 90],[13, 90]]
Which will be used in a multilinestring Geojson:
This is what I have tried:
var multiTrackList = _context.tracks
.GroupBy(g => g.track_id)
.Select(s => new object[] {
_context.tracks
.Where(w => w.track_id == s.Key)
.OrderBy(o => o.track_id).ThenBy(o => o.date).ThenBy(o => o.time)
.Select(e => new object[] { e.lond.Value, e.latd.Value }).ToList()
});
but it keeps returning:
[[[12, 100],[13, 101]]], [[[10, 90],[13, 90]]]
with extra unneeded square brackets. I don't see what I am doing wrong. Please help.

I'm not sure what you're trying to accomplish with all the grouping and ordering, but you're doing too many selects on your data source, generating [2][2][2] arrays. Maybe this will point you in the right direction:
context _context = new();
_context.tracks = new();
_context.tracks.Add(new track("A", 12, 100));
_context.tracks.Add(new track("A", 13, 101));
_context.tracks.Add(new track("B", 10, 90));
_context.tracks.Add(new track("B", 13, 90));
var multiTrackList = _context.tracks
.GroupBy(g => g.track_id) //grouping
.SelectMany(e => e.Where(x => x.track_id == e.Key)) //selection where track_id equals Keys on the grouped elements
.OrderBy(o => o.track_id) //ordering, I removed the date and time for simplicity
.Select(y => new object[2] { y.latd, y.lond }) //then you can generate your 2 positions array
.ToList(); //finally you can convert the IEnumerable into a List, or whatever you need
Console.Write(JsonConvert.SerializeObject(multiTrackList)); //"[[12,100],[13,101],[10,90],[13,90]]"

Thanks to #james-braz and after editing his answer, I was able to get the correct linq query:
var multiTrackList = _context.tracks
.GroupBy(g => g.track_id ) //grouping
.Select(e =>
e.Where(x => x.cruise_id == e.Key) //selection where track_id equals Keys on the grouped elements
.OrderBy(o => o.date).ThenBy(o => o.time) //ordering, I removed the date and time for simplicity
.Select(y => new object[2] { y.latd, y.lond })//then you can generate your 2 positions array
).ToList(); //finally you can convert the IEnumerable into a List, or whatever you need
I obtain the correct output ([[12,100],[13,101],[10,90],[13,90]]), I can use to update the coordinates property of my multilinestring GeoJSON

Related

Linq get Distinct ToDictionary

need help to only select/get distinct entries based on i.Code.
There are duplicates and thus I'm getting an error in my expression "An item with the same key has already been added."
var myDictionary = dbContext.myDbTable
.Where(i => i.shoesize>= 4)
.OrderBy(i => i.Code)
.ToDictionary(i => i.Code, i => i);
Have tried to use Select and/or Distinct in different combinations and also by themselves but am still getting the same error
var myDictionary= dbContext.myDbTable
.Where(i => i.shoesize>= 4)
.OrderBy(i => i.Code)
//.Select(i => i)
//.Distinct()
.ToDictionary(i => i.Code, i => i);
Can anybody help? C#
UPDATE: If there are multiple objects with the same code I only want to add the first object(with that particular code) to myDictionary.
You can group by Code and select the first item from each group (which is equivalent to distinct):
var myDictionary = dbContext.myDbTable
.Where(i => i.shoesize >= 4) // filter
.GroupBy(x => x.Code) // group by Code
.Select(g => g.First()) // select 1st item from each group
.ToDictionary(i => i.Code, i => i);
You don't need the OrderBy since Dictionarys represent an unordered collection. If you need an ordered dictionary you could use SortedDictionary.
It sounds to me that what you are looking for is .DistinctBy() (available in .NET 6), which lets you specify which property to distinct the elements in your collection by:
var myDictionary= dbContext.myDbTable
.Where(i => i.shoesize>= 4)
.DistinctBy(i => i.Code)
.ToDictionary(i => i.Code, i => i);
By dividing it and creating a list first it worked as compared to when it was all bundled up into one linq, guess the First() needed it to be in a list before being able to make it into a dict.
var firstLinq = dbContext.myDbTable
.Where(i => i.shoesize>= 4)
.ToList();
then
var finalLinq = fromConcurWithDuplicates
.GroupBy(i => i.Code)
.Select(i => i.First())
.ToList()
.ToDictionary(i => i.Code, i => i);

EF Core LINQ GROUPBY Then Select to get more than one properties of the entity

I have 2 tables Outlet and Order with below schemas:
Outlet Order
------ -------------------
Id Id
Name Name
OrderCompletedTime
NextOrderDueTime
OutletIds
Earlier when I wanted to get the NextOrderDueTime for each outlet using entity framework core, I did:
return _dbAccessor.RequestContext.MyDbContext.Order
.Where(i => i.OutletId == _dbAccessor.RequestContext.OutletId &&
!i.IsRemoved && i.NextOrderDueTime.HasValue)
.GroupBy(i => i.OutletId)
.Select(g => new { OutletId = g.Key, NextOrderDueTime = g.Min(x => x.NextOrderDueTime) })
.ToDictionary(i => i.OutletId, i => i.NextOrderDueTime);
Now on the UI we need to make this due time as link and wants user to get navigated to that Order details page based on order id
How can I change the above query to also return OrderId along with time?
My thoughts:
Change return type of method from Dictionary<int, DateTimeOffset?> to Dictionary<int, Tuple<int,DateTimeOffset?>>
I tried changing the Linq query to :
return _dbAccessor.RequestContext.MyDbContext.Order
.Where(i => i.OutletId == _dbAccessor.RequestContext.OutletId &&
!i.IsRemoved && i.NextOrderDueTime.HasValue)
.GroupBy(i => i.OutletId)
.Select(g =>
new
{
OutletId = g.Key,
NextOrderDueTime = g.FirstOrDefault(x => x.NextOrderDueTime == g.Min(y => y.NextOrderDueTime)).NextOrderDueTime,
NextOrderId = g.FirstOrDefault(x => x.NextOrderDueTime == g.Min(y => y.NextOrderDueTime)).OrderId
})
.ToDictionary(i => i.OutletId, i => new Tuple<int, DateTimeOffset?>(i.NextOrderId, i.NextOrderDueTime));
But this throws exception at runtime?
Please help to let me know what I am doing wrong here.
You could just take the entire order along with the outletid when you return:
.Select(g => new {
OutletId = g.Key,
NextOrder = g.OrderBy(x => x.NextOrderDueTime).FirstOrDefault()
})
You could select on this to take multiple properties from the order:
.Select(g => new {
OutletId = g.Key,
NextOrder = g.OrderBy(x => x.NextOrderDueTime).FirstOrDefault()
})
.Select(s => new {
s.OutletId,
NextOrderId = NextOrder.Id,
NextOrder.NextOrderDueTime,
NextOrderName = NextOrder.Name
})
etc..
The main thing to appreciate is that grouping gives you an object that has a key, but itself is a list of all things that have that key, so if you order the list by something like the DUeDat and take the first thing then you have an entire object with the lowest duedate from which you can take various things
The .GroupBy(...).Select(...).ToDictionary(...); cannot be converted to SQL since EF Core 3.0.
Due to the breaking change in EF Core 3.0. https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes , EF Core 3.0 will throw exception to make sure you know that all records in Order will be fetched from database before grouping and map to Dictionary.
I was able to get my query working as below:
return _dbAccessor.RequestContext.MyDbContext.Order
.Where(i => i.OutletId == _dbAccessor.RequestContext.OutletId &&
!i.IsRemoved && i.NextOrderDueTime.HasValue).AsEnumerable()
.GroupBy(i => i.OutletId)
.Select(g =>
new
{
OutletId = g.Key,
NextOrderDueTime = g.FirstOrDefault(x => x.NextOrderDueTime == g.Min(y => y.NextOrderDueTime)).NextOrderDueTime,
NextOrderId = g.FirstOrDefault(x => x.NextOrderDueTime == g.Min(y => y.NextOrderDueTime)).OrderId
})
.ToDictionary(i => i.OutletId, i => new Tuple<int, DateTimeOffset?>(i.NextOrderId, i.NextOrderDueTime));
The same can be done as shown in another answer by just adding AsEnumerable() before GroupBy:
_dbAccessor.RequestContext.MyDbContext.Order
.Where(i => i.OutletId == _dbAccessor.RequestContext.OutletId &&
!i.IsRemoved && i.NextOrderDueTime.HasValue).AsEnumerable()
.GroupBy(i => i.OutletId)
.Select(g => new {
OutletId = g.Key,
NextOrder = g.OrderBy(x => x.NextOrderDueTime).FirstOrDefault()
})
.Select(s => new {
s.OutletId,
NextOrderId = NextOrder.Id,
NextOrder.NextOrderDueTime,
NextOrderName = NextOrder.Name
})`enter code here`;

Linq to Entities group query giving list of results in each group

If I have a set of entities with 3 properties (Id, Type, Size) all of which are strings.
Is there a way using Linq to Entities where I can do a group query which gives me the Size + Type as the key and then a list of the related Id's for that Size + Type?
Example below of getting the count:
Items.GroupBy(x => new { x.Size, x.Type})
.Select(x => new { Key = x.Key, Count = x.Count() })
but I am looking to get a list of the Ids for each grouping?
I am looking to see if it is possible using Linq-to-EF before I decide to iterate through this in code and build up the result instead.
If you want to get List of Ids for each group then you have to select x.Select(r => r.Id) like:
var result = Items.GroupBy(x => new { x.Size, x.Type })
.Select(x => new
{
Key = x.Key,
Ids = x.Select(r => r.Id)
});
Another way to build up a Dictionary<string, IEnumerable<string?>> in dotnet 6.0 according to the docs;
where we have the dictionary Key as {Size, Type} and Value the list of Ids, you can write:
Dictionary<string, IEnumerable<string?>> result = Items.GroupBy(item => new { item.Size, item.Type }
item => item.Id),
(itemKey, itemIds) =>
{
Key = itemKey,
Ids = itemIds
})
.ToDictionary(x => x.Key, x=> x.Ids);

LINQ group-by and then display datetime for (dd mmm)

I want Linq to group by date but display in text
here is my code
var groups = _uow.Orders.GetAll()
.Where(x => x.Created > baselineDate)
.GroupBy(x => x.Created.ToString("yyyy-MM-dd"));
var orders = new
{
Day = groups.Select(g => g.Key).ToArray(),
Total = groups.Select(g => g.Sum(t => t.Total)).ToArray(),
};
the result is (not good to put in label of graph)
{"Day": [2, 3, 4, 5], "Total": [9999.00, 9999.00, 9999.00, 9999.00] }
But i want this (Monthly)
"Day": ['Jan', Feb', 'Mar', 'Apr'], "Total": [9999.00, 9999.00, 9999.00, 9999.00] }
Or Daily
"Day": ['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4'], "Total": [9999.00, 9999.00, 9999.00, 9999.00] }
Please advice me for DateTime.ToString() that i can play with.
Thank you all.
Thank you all for helping.
I found the answer.
add
.AsEnumerable()
AND
.GroupBy(x => x.Created.ToString("MMM d"));
here is full code
var groups = _uow.Orders.GetAll()
.Where(x => x.Created > baselineDate)
.AsEnumerable()
.GroupBy(x => x.Created.ToString("MMM d"));
var orders = new
{
Day = groups.Select(g => g.Key).ToArray(),
Total = groups.Select(g => g.Sum(t => t.Total)).ToArray(),
};
This question describes how to convert a date time to the string name of the month.
EDIT: for EF, we have to pull everything into memory before doing any string logic:
var orders = _uow.Orders.GetAll()
.Where(x => x.Created > baselineDate)
// pull into memory here, since we won't be making the result set any smaller and
// we want to use DateTime.ToString(), which EF won't compile to SQL
.AsEnumerable()
// this will group by the whole date. If you only want to group by part of the date,
// (e. g. day or day, month), you could group by x => x.Date.Month or the like
.GroupBy(x => x.Date)
.Select(g => new {
// or use just "MMM" for just the month
Dates = g.Select(g => g.Key.ToString("MMM d")).ToArray(),
Total = ...
});
You need to use functions that EF can understand and translate to SQL, such as EntityFunctions.TruncateTime. Here is your updated code:
var groups = _uow.Orders.GetAll()
.Where(x => x.Created > baselineDate)
.GroupBy(x => EntityFunctions.TruncateTime(x.Created));
Then use the string formatting just in the output, but make sure you're doing it after the value has been returned to .Net, using AsEnumerable to switch to Linq-to-Objects:
var orders = new
{
Day = groups.Select(g => g.Key)
.AsEnumerable()
.Select(x => x.ToString("MMM d"))
.ToArray(),
Total = groups.Select(g => g.Sum(t => t.Total)).ToArray(),
};

Dictionaries: An item with the same key has already been added

In my MVC app I am using 2 dictionaries to populate SelectList for DropDownList. Those dictionaries will be supplied with dates as string and datetime values.
I have this chunk of code for the first dictionary that works just fine:
if (m_DictDateOrder.Count == 0)
{
m_DictDateOrder = new Dictionary<string, DateTime>();
m_DictDateOrder =
m_OrderManager.ListOrders()
.OrderBy(x => x.m_OrderDate)
.Distinct()
.ToDictionary(x => x.m_OrderDate.ToString(), x => x.m_OrderDate);
}
But when I get to the second dictionary:
if (m_DictDateShipped.Count == 0)
{
m_DictDateShipped = new Dictionary<string, DateTime>();
m_DictDateShipped =
m_OrderManager.ListOrders()
.OrderBy(x => x.m_ShippedDate)
.Distinct()
.ToDictionary(x => x.m_ShippedDate.ToString(), x => x.m_ShippedDate);
}
I get a runtime error on the LINQ request for the second dictionary:
An item with the same key has already been added.
I first though that I add to instantiate a new dictionary (that's the reason for the "new" presence), but nope. What did I do wrong?
Thanks a lot!
You are Distinct'ing the rows, not the dates.
Do this instead:
if (m_DictDateShipped.Count == 0)
{
m_DictDateShipped = m_OrderManager.ListOrders()
//make the subject of the query into the thing we want Distinct'd.
.Select(x => x.m_ShippedDate)
.Distinct()
.ToDictionary(d => d.ToString(), d => d);
}
Don't bother sorting. Dictionary is unordered.
My standard pattern for this (since I have disdain for Distinct) is:
dictionary = source
.GroupBy(row => row.KeyProperty)
.ToDictionary(g => g.Key, g => g.First()); //choose an element of the group as the value.
You applied the Distinct to the order, not to the date. Try
m_OrderManager.ListOrders()
.OrderBy(x => x.m_ShippedDate)
.Select(x =>x.m_ShippedDate)
.Distinct()
.ToDictionary(x => x.ToString(), x => x);

Categories

Resources