Using GroupBy in .NET Core - c#

I need to grouping in the result of my query. The main problem is that when I use GroupBy, the return type of GroupBy statement is IGrouping that is different from the return type of my method which is List<MyViewModel>. So theses two types does not match. Now the first option is to change the return type of my method. In this case how I can access to the properties of my ViewModel in view layer(HTML part of Razor Page)?
Is there any option to using GroupBy without changing the return type of my method?
public List<MultimediaViewModel> Search(MultimediaSearchModel searchModel)
{
var query = _hContext.Multimedias.Include(x => x.Ceremony)
.Select(g => new MultimediaViewModel
{
Id = g.Id,
Title = g.Title,
CeremonyId = g.CeremonyId,
Ceremony = g.Ceremony.Title,
FileAddress = g.FileAddress,
}).AsEnumerable().GroupBy(g => g.CeremonyId).ToList();
if (!string.IsNullOrWhiteSpace(searchModel.Title))
query = query.Where(g => g.Title.Contains(searchModel.Title)).ToList();
if (searchModel.CeremonyId != 0)
query = query.Where(g => g.Key == searchModel.CeremonyId).ToList();
return query;
}
the above code is not working because of the difference between method return type and Group by return type.

without knowing your business needs completely I can offer 2 ways to get around this
use the GroupBy outside the method, use the Search method to return a list and then use GroupBy to group it
if you only need to return a list of MultimediaViewModel for a specific CeremonyId, you can search the list of IGrouping to get a specific sub list
public List<MultimediaViewModel> Search(MultimediaSearchModel searchModel)
{
var query = _hContext.Multimedias.Include(x => x.Ceremony).Select(g => new MultimediaViewModel
{
Id = g.Id,
Title = g.Title,
CeremonyId = g.CeremonyId,
Ceremony = g.Ceremony.Title,
FileAddress = g.FileAddress
}).GroupBy(g => g.CeremonyId).ToList();
return query.FirstOrDefault(g=>g.Key==searchModel.CeremonyId).ToList();
//you can just use Where to find all of the MultimediaViewModel instead of using GroupBy
EDIT
maybe return the result as a Dictionary
public Dictionary<string, List<MultimediaViewModel>> Search(MultimediaSearchModel searchModel)
{
var query = _hContext.Multimedias.Include(x => x.Ceremony).Select(g => new MultimediaViewModel
{
Id = g.Id,
Title = g.Title,
CeremonyId = g.CeremonyId,
Ceremony = g.Ceremony.Title,
FileAddress = g.FileAddress
}).GroupBy(g => g.CeremonyId).ToList();
return query.ToDictionary(k => k.Key, v => v.ToList());
}

Related

Linq Groupby Not Grouping in c#

Below i have a snippet of code which outputs a list of Appointments based on clients, some clients can have more than one appointment but the latest one is the one that needs to be outputted for said client
the output is not grouping at all and for some reason i cannot figure why the heck not
foreach (ClientRecord client in clients)
{
List<ReturnRecord> records = db.Appointments
.AsNoTracking()
.Include(rec => rec.Property)
.Include(rec => rec.Property.Address)
.Include(rec => rec.AppointmentType)
.ToList()
.Where(rec => rec.ClientID == client.ID)
.Select(rec => new ReturnRecord
{
ClientName = $"{client.FirstNames} {client.Surnames}",
PropertyAddress = $"{rec.Property.Address.FormattedAddress}",
AppStatus = $"{rec.AppointmentStatus.Name}",
StockStatus = $"{rec.Property.Stocks.FirstOrDefault().StockStatus.Name}",
LastUpdated = rec.LastUpdated
})
.ToList();
returnList.AddRange(records);
}
returnList.GroupBy(rec => rec.PropertyAddress);
return Ok(returnList);
here is an attachment of the screen grab of the output
You need to assign result of GroupBy() to variable:
returnList = returnList.GroupBy(rec => rec.PropertyAddress).ToList();
Make sure to actually use the new IEnumerable that the .GroupBy() Method returned.
If you want to return a List you need to use a workaround:
Get the IEnumerable<IGrouping<int, ReturnRecord>> from the .GroupBy()
Use .SelectMany() to select all elements and save them into an IEnumerable
Now you can convert your IEnumerable into a List with .List()
Example:
// Longer Alternative
IEnumerable<IGrouping<int, ReturnRecord>> groups = resultList
.GroupBy((rec => rec.PropertyAddress);
IEnumerable<ReturnRecord> result = groups.SelectMany(group => group);
List<ReturnRecord> listResult = result.ToList();
return Ok(listResult);
// Shorter Alternative
IEnumerable<IGrouping<int, ReturnRecord>> groups = resultList
.GroupBy((rec => rec.PropertyAddress);
IEnumerable<ReturnRecord> result = groups.SelectMany(group => group);
return Ok(result.ToList());

How to aggregate and SUM EntityFramework fields with multiple joins

I am able to produce a set of results that are desirable, but I have the need to group and sum of these fields and am struggling to understand how to approach this.
In my scenario, what would be the best way to get results that will:
Have a distinct [KeyCode] (right now I get many records, same KeyCode
but different occupation details)
SUM wage and projection fields (in same query)
Here is my LINQ code:
private IQueryable<MyAbstractCustomOccupationInfoClass> GetMyAbstractCustomOccupationInfoClass(string[] regionNumbers)
{
//Get a list of wage data
var wages = _db.ProjectionAndWages
.Join(
_db.HWOLInformation,
wages => wages.KeyCode,
hwol => hwol.KeyCode,
(wages, hwol) => new { wages, hwol }
)
.Where(o => regionNumbers.Contains(o.hwol.LocationID))
.Where(o => o.wages.areaID.Equals("48"))
.Where(o => regionNumbers.Contains(o.wages.RegionNumber.Substring(4))); //regions filter, remove first 4 characters (0000)
//Join OccupationInfo table to wage data, for "full" output results
var occupations = wages.Join(
_db.OccupationInfo,
o => o.wages.KeyCode,
p => p.KeyCode,
(p, o) => new MyAbstractCustomOccupationInfoClass
{
KeyCode = o.KeyCode,
KeyTitle = o.KeyTitle,
CareerField = o.CareerField,
AverageAnnualOpeningsGrowth = p.wages.AverageAnnualOpeningsGrowth,
AverageAnnualOpeningsReplacement = p.wages.AverageAnnualOpeningsReplacement,
AverageAnnualOpeningsTotal = p.wages.AverageAnnualOpeningsTotal,
});
//TO-DO: How to Aggregate and Sum "occupations" list here & make the [KeyCode] Distinct ?
return occupations;
}
I am unsure if I should perform the Grouping mechanism on the 2nd join? Or perform a .GroupJoin()? Or have a third query?
var occupations = _db.OccupationInfo.GroupJoin(
wages,
o => o.KeyCode,
p => p.wages.KeyCode,
(o, pg) => new MyAbstractCustomOccupationInfoClass {
KeyCode = o.KeyCode,
KeyTitle = o.KeyTitle,
CareerField = o.CareerField,
AverageAnnualOpeningsGrowth = pg.Sum(p => p.wages.AverageAnnualOpeningsGrowth),
AverageAnnualOpeningsReplacement = pg.Sum(p => p.wages.AverageAnnualOpeningsReplacement),
AverageAnnualOpeningsTotal = pg.Sum(p => p.wages.AverageAnnualOpeningsTotal),
});

LINQ: How to Select specific columns using IQueryable()

I need to select only two columns from Hospital table, HospitalId and Name.
i tried the below code it selects all columns from Hospital table which lead to slow performance. Please help me to select only two columns from Hospital table
public HttpResponseMessage GetAvailableHospitalsByAjax(System.Guid? DirectorateOfHealthID = null, System.Guid? UnitTypeID = null, string DeviceTypeIDs = null)
{
Context db = new Context();
var query = db.Hospitals.AsQueryable();
if (UnitTypeID != null)
{
query = query.Where(j => j.HospitalDepartments.Any(www => www.Units.Any(u => u.UnitTypeID == UnitTypeID)));
}
if (DirectorateOfHealthID != null)
{
query = query.Where(h => h.DirectorateHealthID == DirectorateOfHealthID);
}
query = query.Where(j => j.HospitalDepartments.Any(u => u.Units.Any(d => d.Devices.Any(s => s.Status == Enums.DeviceStatus.Free)))
&& j.HospitalDepartments.Any(hd => hd.Units.Any(u => u.Beds.Any(b => b.Status == Enums.BedStatus.Free))));
var list = query.ToList().Select(w => new HospitalInfo()
{
Id = w.ID,
Name = w.Name
}).ToList();
return Request.CreateResponse(HttpStatusCode.OK, list);
}
IQueryable<T> executes select query on server side with all filters. Hence does less work and becomes fast.
IEnumerable<T> executes select query on server side, load data in-memory on client side and then filter data. Hence does more work and becomes slow.
List<T> is just an output format, and while it implements IEnumerable<T>, is not directly related to querying.
So,
var list = query.ToList().Select(w => new HospitalInfo()
{
Id = w.ID,
Name = w.Name
}).ToList();
In your code you use query.ToList(). This means at first it pull all data into memory then apply Select query.If you want to retrieve HospitalID and Name then remove ToList() then your code like
var list = query.Select(w => new HospitalInfo
{
Id = w.ID,
Name = w.Name
}).ToList();
Remove the ToList call before the projection:
var list = query.Select(w => new HospitalInfo()
{
Id = w.ID,
Name = w.Name
}).ToList();
With that ToList call you are materializing your query before do the projection
Because you do query.ToList() this materialises the entire query with all columns into memory. It's actually a bad habit to get into. Instead, remove that, you already have it at the end anyway. The Select projection you have will only retrieve the relevant columns though:
var list = query.Select(w => new HospitalInfo()
{
Id = w.ID,
Name = w.Name
}).ToList();

Get a specific value out of a DictionaryList with LINQ?

I have a Dictionary:
var dict = new Dictionary<int, string>();
With this values:
[0]: {[1, "Person1"]}
[1]: {[2, "Person2, Person3"]}
[2]: {[3, "Person4"]}
[3]: {[4, "Person5"]}
And when i use a "foreach" to get the values with "id" 2 i get as result "Person2, Person3".
foreach (var test in dict)
{
if (test.Key == 2)
System.Diagnostics.Debug.WriteLine(test.Value);
}
But when i use this LINQ line:
Person = dict.FirstOrDefault(q => q.Key == s.Person.ID).Value.ToString(),
I get this error:
Local sequence cannot be used in LINQ to SQL implementations of query
operators except the Contains operator.
I tried several things but nothing seems to work, so any ideas?
EDIT
I use this to show output on my page:
DataSelectionQuery = p => new
{
p.ID,
p.FirstName,
p.LastName,
TEST = dict.FirstOrDefault(q => q.Key == p.ID).Value.ToString(),
};
public Expression<Func<LinqClass, object>> DataSelectionQuery { get; set; }
And here is where it trows the error:
var results = query.Select(DataSelectionQuery).ToList();
You cannot use this kind of expressions inside Linq to SQL as they cannot be translated to SQL query
Use .ToList() at the end of your query, then use Linq to objects to complete your entity with values from Dictionary
For example when you have code like:
var result = from x in table
select new Entity
{
Id = x.Id,
Sth = x.Sth,
Person = dict.FirstOrDefault(q => q.Key == s.Person.ID).Value.ToString()
};
You need to change it to something like this:
var result = (from x in table
select new { x.Id, x.Sth, x.PersonId }) // LINQ To SQL part
.ToList() // get query results
.Select(x => new Entity
{
Id = x.Id,
Sth = x.Sth,
Person = dict.FirstOrDefault(q => q.Key == x.PersonId).Value.ToString()
}; // this part will be executed with Linq to objects
Please provide the full source code if you need more detailed help
Ok, so your "SelectionQuery" needs to be simple enough to translate into SQL query and should look like this:
DataSelectionQuery = p => new
{
p.ID,
p.FirstName,
p.LastName
};
And the other part should look like this:
var results = query.Select(DataSelectionQuery).ToList()
.Select(p => new
{
p.ID,
p.FirstName,
p.LastName,
TEST = dict.FirstOrDefault(q => q.Key == p.ID).Value.ToString()
});
Alernatively you can create your own class which will contain the Dictionary and will translate the ID into TEST on the getter action
This is how I solved the problem:
Person = string.Join(", ", PersonsQuery.Where(q => q.ID == s.ID).Select(q => q.PersonInformation))

How do i sum a list of items by code(or any field)?

I have an object that has a list of another object in it. i.e Object1 contains List<Object2>.
Assuming this is the definition of object 2:
public class Object2
{
string code,
string name,
decimal amount
}
I want to be a able to make a list2 from the list whose value will contain what something similar to what a select name, code, sum(amount) group by code kinda statement could have given me
this is what i did but it didnt contain what i needed on passing through.
var newlist = obj2List.GroupBy(x => x.code)
.Select(g => new { Amount = g.Sum(x => x.amount) });
I want code and name in the new list just like the sql statement above.
You're almost there:
var newlist = obj2List.GroupBy(x => x.code)
.Select(g => new
{
Code = g.First().code,
Name = g.First().name,
Amount = g.Sum(x => x.amount)
});
This groups the items by code and creates an anonymous object for each group, taking the code and name of first item of the group. (I assume that all items with the same code also have the same name.)
If you are grouping by code and not by name you'd have to choose something for name from the list, perhaps with First() or Last() or something.
var newlist = obj2List.GroupBy(x => x.code).Select(g => new {
Code = g.Key,
Name = g.First().name,
Amount = g.Sum(x => x.amount)
});
var query = Object1.Obj2List
.GroupBy(obj2 => obj2.code)
.Select(g => new {
Names = string.Join(",", g.Select(obj2.name)),
Code = g.Key,
Amount = g.Sum(obj2 => obj2.Amount)
});
Since you group by code only you need to aggregate the name also in some way. I have used string.Join to create a string like "Name1,Name2,Name3" for each code-group.
Now you could consume the query for example with a foreach:
foreach(var x in query)
{
Console.WriteLine("Code: {0} Names: {1} Amount: {2}"
, x.Code, x.Names, x.Amount);
}
Instead of using the LINQ Extension Methods .GroupBy() and .Select() you could also use a pure LINQ statement which is way easier to read if you come from a SQL Background.
var ls = new List<Object2>();
var newLs = from obj in ls
group obj by obj.code into codeGroup
select new { code = codeGroup.Key, amount = codeGroup.Sum(s => s.amount) };

Categories

Resources