Keep distinct value in List depending on condition - c#

I have a list where I'm applying the following condition with linQ:
I want to select all items where Name contains a certain string.
var nameFilter = result
.Where(e => e.Name.Contains(requestedValue))
.ToList();
At the end, sometimes it happens that I am having a list with repeated names:
For example:
requestedValue = 'form';
I end up with:
Name Price
transformer 100
transformer 20
formation 340
former 201
I got transformer twice. In that case, I want to only leave transformer with the least price : 20
How could I do this with linQ without looping?

You can take advantage of GroupBy method
var nameFilter = result.Where(e => e.Name.Contains(requestedValue))
.GroupBy(k=>k.Name, g=>g.Price, (k,g)=>new Model {Name = k, Price = g.Min()})
.ToList();
where new Model should be changed to your class name.
If you have more properties to return probably it will be more convenient to do
var nameFilter = result.Where(e => e.Name.Contains(requestedValue))
.GroupBy(k => k.Name, g => g, (k, g) =>
{
var minPrice = g.Min(x => x.Price);
return g.First(x => x.Price == minPrice);
}).ToList();
Finding minPrice and finding the item with minPrice can be done is a single for loop or, for example, by using following discussion here

Related

How do I to use Where, Group By, Select and OrderBy at same query linq?

I'm trying to make a linq using where, group by and select at same time but I cannot do it works and it always throws an exception.
How could I do it works ?
Linq
public ActionResult getParceiros(){
//return all partners
IList<ViewParceirosModel> lista = new List<ViewParceirosModel>();
lista = context.usuarios.Where(u => u.perfil == RoleType.Parceiro)
.Select(x => new ViewParceirosModel
{
id = x.id,
nomeParceiro = x.nome,
emailAcesso = x.email
})
.GroupBy(x => x.id)
.OrderBy(x => x.nomeParceiro)
.ToList();
return View(lista);
}
Exception
Your program doesn't do what you want. Alas you forgot to tell you what you want, you only showed us what you didn't want. We'll have to guess.
So you have a sequence of Usarios.
IQueryable<Usario> usarios = ...
I don't need to know what a Usario is, all I need to know is that it has certain properties.
Your first step is throwing away some Usarios using Where: you only want to keep thos usarios that have a Perfil equal to RoleType.Parceirdo:
// keep only the Usarios with the Perfil equal to RoleType.Parceirdo:
var result = usarios.Where(usario => usario.Perfil == RoleType.Parceirdo)
in words: from the sequence of Usarios keep only those Usarios that have a Perfil equal to RoleTyoe.Parceirdo.
The result is a subset of Usarios, it is a sequence of Usarios.
From every Usario in this result, you want to Select some properties and put them into one ViewParceirosModel:
var result = usarios.Where(usario => usario.Perfil == RoleType.Parceirdo)
.Select(usario => new ViewParceirosModel
{
Id = x.id,
NomeParceiro = x.nome,
EmailAcesso = x.email,
})
In words: from every Usario that was kept after your Where, take the Id, the Nome and the Email to make one new ViewParceirosModel.
The result is a sequence of ViewParceirosModels. If you add ToList(), you can assign the result to your variable lists.
However your GroupBy spoils the fun
I don't know what you planned to do, but your GroupBy, changes your sequence of ViewParceirosModels into a sequence of "groups of ViewParceirosModels" Every ViewParceirosModel in one group has the same Id, the value of this Id is in the Key.
So if after the GroupBy you have a group of ViewParceirosModel with a Key == 1, then you know that every ViewParceirosModel in this group will have an Id equal to 1.
Similarly all ViewParceirosModel in the group with Key 17, will have an Id equal to 17.
I think Id is your primary key, so there will only be one element in each group. Group 1 will have the one and only ViewParceirosModel with Id == 1, and Group 17 will have the one and only ViewParceirosModel with Id == 17.
If Id is unique, then GroupBy is useless.
After the GroupBy you want to Order your sequence of ViewParceirosModels in ascending NomeParceiro.
Requirement
I have a sequence of Usarios. I only want to keep those Usarios with a Perfil value equal to RoleType.Parceirdo. From the remaining Usarios, I want to use the values of properties Id / Nome / Email to make ViewParceirosModels. The remaining sequence of ViewParceirosModels should be ordered by NomeParceiro, and the result should be put in a List.
List<ViewParceirosModel> viewParceiroModels = Usarios
.Where(usario => usario.Perfil == RoleType.Parceirdo)
.Select(usario => new ViewParceirosModel
{
Id = x.id,
NomeParceiro = x.nome,
EmailAcesso = x.email,
}
.OrderBy(viewParceirosModel => viewParceirosModel.NomeParceiro)
.ToList();
When you create a LINQ query with group by clause, you receive as result a grouped query.
It is a kind of dictionary that has as key the field you chose to group and as value a list of records of this group.
So, you cannot order by "nomeParceiro" because this field is inside the group.
If you detail how you expect the result I can show you a code example for this.
You can find more details in this section of the doc: https://learn.microsoft.com/pt-br/dotnet/csharp/linq/group-query-results
Let's say ViewParceirosModel look like
public class ViewParceirosModel
{
public int id {get; set;}
public List<string> nomeParceiro {get; set;}
public List<string> emailAcesso {get; set;}
}
After that, you can Groupby then select combine with Orderby like below
IList<ViewParceirosModel> lista = new List<ViewParceirosModel>();
lista = context.usuarios.Where(u => u.perfil == RoleType.Parceiro)
.Select(x => new ViewParceirosModel
{
id = x.id,
nomeParceiro = x.nome,
emailAcesso = x.email
})
.GroupBy(x => x.id)
.Select(g => new ViewParceirosModel
{
id = g.Key,
nomeParceiro = g.Select(p => p.nomeParceiro).OrderBy(x => x.nomeParceiro).ToList()
nomeParceiro = g.Select(p => p.emailAcesso).ToList()
})
.ToList();
You can use the following code.
IList<ViewParceirosModel> lista = new List<ViewParceirosModel>();
lista = context.usuarios.Where(u => u.perfil == RoleType.Parceiro)
.Select(x => new ViewParceirosModel
{
id = x.id,
nomeParceiro = x.nome,
emailAcesso = x.email
})
.OrderBy(x => x.nomeParceiro)
.GroupBy(x => x.id)
.ToList();
or
List<List<ViewParceirosModel>> listb = context.usuarios
.Where(u => u.perfil == RoleType.Parceiro)
.GroupBy(g => g.id).OrderBy(g => g.Key)
.Select(g => g.OrderBy(x => x.nomeParceiro)).ToList();

How to get value from Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable in mvc controller

I know the title is somewhat complicate but let I clear the title by explaining the problem.
As per image I want to filter product name in search textbox. For ex: In search textbox if I enter the oil that in datatable whole data want to show which product name is oil.
For filter I used linq query, here is my code,
var dataList = (from x in query
select new
{
PartName = _Db.Part.Where(z => z.Id == x.Select(p => p.PartId).FirstOrDefault()).Select(p => p.Name),
ManufacturerName = _Db.Manufacture.Where(z => z.Id == x.Select(p => p.ManufacturerId).FirstOrDefault()).Select(p => p.name),
CategoryName = _Db.Category.Where(z => z.Id == x.Select(p => p.CategoryId).FirstOrDefault()).Select(p => p.Name),
Pcs = x.Sum(o =>o.Pcs) -
(from m in _Db.MaterialRecord
join s in _Db.ServiceJob on m.ServiceJobId equals s.Id
where m.pid == x.Select(p => p.PartId).FirstOrDefault()
select m).Sum(z => z.Qty),
Weight = _Db.Purchase.Where(p => p.Weight == x.Select(s => s.Weight).FirstOrDefault()).Select(a => a.Weight).FirstOrDefault(),
WeightType = x.Select(p => p.WeightTypeId).FirstOrDefault() > 0 ?((WeightType)x.Select(p => p.WeightTypeId).FirstOrDefault()).ToString() :"",
}).ToList();
//Search
if (!string.IsNullOrEmpty(searchValue))
dataList = dataList.Where(m => m.CategoryName.Contains(searchValue.ToString().ToLower())).ToList();
//Returning Json Data
return Json(new { draw = draw, recordsFiltered = recordsTotal, recordsTotal = recordsTotal, data = data });
As per code I am getting whole data in datalist variable. Now, when I search any product in searchbox at that time the condition of search will true and after that the datalist value will be null[as shown by debug the code].
So, hence from that datatable shows null data.
For understanding that why this happening I add the tostring() in datalist query of category line i.e.
CategoryName = _Db.Category.Where(z => z.Id == x.Select(p => p.CategoryId).FirstOrDefault()).Select(p => p.Name).toString(),
After adding toString() in this line it shows this line Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable. But I want the result value of this line. For more clear lets see the another image
That oil result value I want in search condition line i.e.
dataList = dataList.Where(m => m.CategoryName.Contains(searchValue.ToString().ToLower())).ToList();
but right now in it is showing this line Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable in the place of result value.
First thing you should know is ToString() by its default implementation returns fully-qualified name of the object when applied to collection objects, in this case returns Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable<string> which is a reference type that doesn't override that method.
Let's evaluate this expression first:
CategoryName = _Db.Category.Where(z => z.Id == x.Select(p => p.CategoryId).FirstOrDefault())
.Select(p => p.Name),
Assumed that Category has DbSet<Category> type, the Select() extension method used in code above returns EntityQueryable<string> which contains result set collection from executed SQL statement provided by LINQ-to-Entities query. If you want to pull single string value from that collection, you must use one of the First(), Single(), FirstOrDefault() or SingleOrDefault() extension methods.
Hence, you should add extension method as mentioned above to return a string value instead of a collection:
var dataList = (from x in query
select new {
// other properties
CategoryName = _Db.Category.Where(z => z.Id == x.Select(p => p.CategoryId).FirstOrDefault())
.Select(p => p.Name).SingleOrDefault(),
// other properties
}).ToList();
Then you can use Contains against a substring (searchValue) because CategoryName has string type:
dataList = dataList.Where(m => m.CategoryName.Contains(searchValue.ToString().ToLower())).ToList();
Reference:
Entity Framework/Core and LINQ to Entities Query Methods (Operators)

LINQ: Group By, and retrieve common property

I have the following dataset, which is stored in a variable called list1.
countrypk | countryname | statepk | statename
---------------------------------------------
1 USA 1 New York
1 USA 2 California
2 Canada 3 Manitoba
I want to be able to group by countrypk, and retrieve the country name.
I have the following LINQ that achieves that effect, but was wondering if there was a better or more straight forward way to do it in LINQ.
var finalList = list1
.GroupBy(item => item.countrypk)
.Where(item => item.Count() > 0)
.Select(item => item.First())
The desired output is:
countrypk | countryname
---------------------------------------------
1 USA
2 Canada
The addition of the Where is not needed. If you have a group it contains at least a single item in it. You can do something like this:
list1.GroupBy(item => item.countrypk)
.Select(item => new { item.Key, item.First().countryname} );
Or using a different overload of GroupBy:
list1.GroupBy(item => item.countrypk,
selector => new { selector.countrypk, selector.countryname} )
.Select(group => group.First())
If you grouped by countrypk, your result set wouldn't have duplicates in it. Your desired result set has duplicate countrypk values in it (1). To get your desired result set, do this:
var finalList = list1.Select(s => new { s.countrypk, s.countryname });
Edit: nevermind this part above, OP editted the question.
I want to be able to group by countrypk, and retrieve the country name What you're asking for is different than what your result set shows. If you want a map of countrypk to country name using your list1, here's one way to do it:
var finalList = list1
.GroupBy(g => new { g.countrypk, g.countryname })
.ToDictionary(k => k.Key.countrypk, v => v.Key.countryname);
Note that you don't need GroupBy to do this. Here's another solution:
var finalList = list1
.Select(s => new { s.countrypk, s.countryname })
.Distinct()
.ToDictionary(k => k.countrypk, v => v.countryname);
In either case, to get the country name for id 1, do this:
var countryName = finalList[1];
Try the following
var finalList = list1
.GroupBy(item => item.countrypk)
.Select(g => new { countrypk = g.Key, countryname = g.First().countryname });
which should provided the desired output
Basically you want to remove duplicates by countrypk and select only first two columns? Use this extension:
public static IEnumerable<TSource> DistinctBy<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
HashSet<TResult> set = new HashSet<TResult>();
foreach(var item in source)
{
var selectedValue = selector(item);
if (set.Add(selectedValue))
yield return item;
}
}
And then
var finalList = list1
.DistinctBy(item => item.countrypk)
.Select(item=> new {item.countrypk, item.countryname })
.ToList();

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) };

LINQ - Need help with a statement/ scenario

Here's the scenario:
Given a List of Outputs each associated with an integer based GroupNumber. For each distinct GroupNumber within the List of Outputs starting with the lowest GroupNumber (1). Cycle through that distinct group number set and execute a validation method.
Basically, starting from the lowest to highest group number, validate a set of outputs first before validating a higher groupnumber set.
Thanks,
Matt
There's almost too many ways to solve this:
Here's one for a void Validate method.
source
.GroupBy(x => x.GroupNumber)
.OrderBy(g => g.Key)
.ToList()
.ForEach(g => Validate(g));
Here's one for a bool Validate method.
var results = source
.GroupBy(x => x.GroupNumber)
.OrderBy(g => g.Key)
.Select(g => new
{
GroupNumber = g.Key,
Result = Validate(g),
Items = g.ToList()
})
.ToList();
If you need them as groups:
var qry = source.GroupBy(x=>x.GroupNumber).OrderBy(grp => grp.Key);
foreach(var grp in qry) {
Console.WriteLine(grp.Key);
foreach(var item in grp) {...}
}
If you just need them ordered as though they are grouped:
var qry = source.OrderBy(x=>x.GroupNumber);

Categories

Resources