Group by, Count and Lambda Expression - c#

I am trying to translate the following query:
SELECT STATE, COUNT(*)
FROM MYTABLE
GROUP BY STATE;
Into a lambda expression. I am using C# and EntityFramework, however it doesnt seem I can make it work. Here is what I have on my respository so far:
public IEnumerable<object> PorcentajeState(Guid id)
{
return _context.Sates.Where(a => a.Id == id)
.GroupBy(a => a.State)
.Select(n => new { n.StateId , n.Count() });
}
Of course it doesnt compile and I am lost after googling for 2 hours . Could you please help me?
thanks in advance

There are two issues here:
The result of GroupBy will will be an enumerable of type IEnumerable<IGrouping<TKey, TSource>>. The IGrouping interface only has one property you can access, Key which is the key you specified in the GroupBy expression, and implements IEnumerable<T> so you can do other Linq operations on the result.
You need to specify a property name for the anonymous type if it cannot be inferred from a property or field expression. In this case, you're calling Count on the IGrouping, so you need to specify a name for that property.
Try this:
public IEnumerable<object> PorcentajeState(Guid id)
{
return _context.Sates.Where(a => a.Id == id)
.GroupBy(a => a.StateId)
.Select(g => new { g.Key, Count = g.Count() });
}
The equivalent in query syntax would be
public IEnumerable<object> PorcentajeState(Guid id)
{
return from a in _context.Sates
where a.Id == id
group a by a.StateId into g
select new { a.Key, Count = g.Count() };
}
In either case, if you want the first property to be named StateId instead of Key, just change that to
new { StateId = g.Key, Count = g.Count() }

This one is good
public IEnumerable<object> PorcentajeState(Guid id)
{
return _context.Sates.Where(a => a.Id == id)
.GroupBy(a => a.StateId)
.Select(g => new { g.Key, Count = g.Count() });
}
But try this.
public IEnumerable<object> PorcentajeState(Guid id)
{
return _context.Sates.Where(a => a.Id == id)
.GroupBy(a => a.StateId)
.Select(g => new { g.Key.StateId, Count = g.Count() });
}

Related

To divide a list of products by category usinq LINQ without foreach

How to return values in method without foreach? I mean i can easily to divide this in foreach loops, but i need to get the answer in this format IEnumerable<(string category, IEnumerable<string> productsName)>. What can i do?
public static IEnumerable<(string category, IEnumerable<string> productsName)> GroupByCategory()
{
List<Product> products = Products.ProductList;
var orderGroups = products.Where(p => p.ProductId <= 20).GroupBy(p => p.Category,
(Key, g) => new { Category = Key, Products = g });
foreach (var i in orderGroups)
{
Console.WriteLine($"Category={i.Category}:");
foreach (var p in i.Products)
{
var s = $"ProductID={p.ProductId},
Product Name={p.ProductName},
UnitPrice={p.UnitPrice},
UnitsInStock={p.UnitsInStock}";
Console.WriteLine(s);
}
}
}
Something like this would probably work for you. Specifically the select and nested select statements.
public IEnumerable<(string category, IEnumerable<string> productsName)> GroupByCategory()
{
List<Product> products = Products.ProductList;
return products.Where(p => p.ProductId <= 20)
.GroupBy(p => p.Category)
.Select(g => (g.Key, g.Select(p => p.ProductName)));
}
Personally I would create a model to better encapsulate the result, something like CategoryGroup. You could then build a constructor for it which takes IGrouping<string, Product> as an argument to clean up the .Select even further, but that's just preference!

Cast after Join

I need to add some information to an entity before returning it via GET.
I created a class, kind of like a ViewModel, with the original entity plus one int field, to return the complete data to a client, but was enable to make it work. I also don't want to return the Depth field, but need it in the Where.
How do I go from this Join to correctly returning the data to a client?
Is iterating and copying item by item the only way?
public class FluxoHierarchyOutput
{
public FluxoHierarchy FluxoHierarchy { get; set; }
public int ParentId { get; set; }
}
public IEnumerable<FluxoHierarchyOutput> GetFluxoHierarchyByFluxo([FromRoute] int idFluxo)
{
return _context.FluxoHierarchy
.Join(
_context.FluxoClosure,
h => h.NodeId,
c => c.ChildId,
(h, c) => new { FluxoHierarchy = h, c.ParentId, c.Depth }
)
.Where(x => x.FluxoHierarchy.FluxoId == idFluxo && x.Depth == 1)
.ToList(); // Cannot implicitly convert type...
}
Try projecting to your concrete type, rather than an anonymous type.
(h, c) => new FluxoHierarchyOutput { FluxoHierarchy = h, c.ParentId, c.Depth }
You're going to need a Select in there somewhere, probably just before the ToList() call. Otherwise, your result will be the entire join.
The fluent API is not the prettiest with joins. Here is the same thing using query syntax. You completely avoid having to specify an intermediate result prior to the select.
return (
from h in _context.FluxoHierarchy
join c in _context.FluxoClosure on h.NodeId equals c.ChildId
where h.FluxoId == idFluxo && c.Depth == 1
select new FluxoHierarchyOutput {
FluxoHierarchy = h,
ParentId = c.ParentId
}).ToList();
Without a Select, your query returns an anonymous object, which then will have to be converted to your actual return type. This conversion cannot be implicit as the error message is telling you. Add a Select to the end of your query and it will work.
return _context.FluxoHierarchy
.Join(
_context.FluxoClosure,
h => h.NodeId,
c => c.ChildId,
(h, c) => new { FluxoHierarchy = h, c.ParentId, c.Depth }
)
.Where(x => x.FluxoHierarchy.FluxoId == idFluxo && x.Depth == 1)
.Select(s => new FluxoHierarchyOutput {
FluxoHierarchy = s.FluxoHierarchy,
ParentId = s.ParentId,
//Add all the fields you want.
})
.ToList();
You have the same issue in the Join as Robert Harvey explained.

Turn SQL into Lambda/Linq

I've been trying to turn a fairly basic piece of SQL code into Lamda or Linq but I'm getting nowhere. Here is the SQL query:
SELECT * FROM Form a
INNER JOIN FormItem b ON a.FormId = b.FormId
INNER JOIN FormFee c ON a.FormId = c.FormId
INNER JOIN FeeType d ON c.FeeTypeId = d.FeeTypeId
WHERE b.StatusId = 7
I tried this but it isn't doing what I want.
public Form GetFormWithNoTracking(int id)
{
return ObjectSet
.Where(x => x.FormId == id &&
(x.FormItem.Any(di => di.StatusId == (short)Status.Paid)))
.AsNoTracking()
.FirstOrDefault();
}
I'm trying to return only the rows from FormItem whose StatusId is Paid. However, the above returns all. I know that .Any() will check if there are any matches and if there are return all, so in this case my data, for this form, does have items who have a StatusId of Paid and some items whose StatusId is not paid so it brings back them all.
var query = (from a in ObjectSet.FormA
join b in ObjectSet.FormB on a.field equals b.field
where b.StatusId = 7
select new { a, b})
You can join rest with same logic.
This should be what you are asking for:
Get the Form with FormId = id
Of that form, return all FormItems that have StatusId = Paid
public IEnumerable<FormItem> GetFormWithNoTracking(int id)
{
return ObjectSet
.SingleOrDefault(x => x.FormId == id)
.Select(f => f.FormItem
.Where(di => di.StatusId == (short)Status.Paid))
.AsNoTracking();
}
If you need the Form itself too, you might want to create a custom type (edit: see #Burk's answer) or return a Tuple<Form,IEnumerable<FormItem>>, a IEnumerable<Tuple<Form,FormItem>> or whatever suits your needs best instead.
Alternatively you could remove all non-paid items of the form.
public Form GetFormWithNoTracking(int id)
{
var form = ObjectSet
.SingleOrDefault(x => x.FormId == id)
.AsNoTracking();
var nonPaid = form.Select(f => f.FormItem
.Where(di => di.StatusId != (short)Status.Paid)).ToList();
foreach(FormItem item in nonPaid)
form.FormItem.Remove(item);
return form;
}

c# and LINQ- group objects based on their IDs and then find common values in them

I have a object collection like in the following.
List<Product> productList=new List<Product>();
Structure of the class Product is like in the following.
public class Product
{
public int Id;
public Product(int id)
{
Id=id;
}
public List<SomeOtherList> t;
}
The class Product contains some other list
public class SomeOtherList
{
private int value;
public SomeOtherList(int val)
{
value=val;
}
}
Product prodOne=new Product(1);
List<SomeOtherList> temp_1=new List<SomeOtherList>();
temp_1.Add(new SomeOtherList(10));
temp_1.Add(new SomeOtherList(20));
temp_1.Add(new SomeOtherList(30));
prodOne.t=temp_1;
Product prodTwo=new Product(2);
List<SomeOtherList> temp_2=new List<SomeOtherList>();
temp_2.Add(new SomeOtherList(100));
temp_2.Add(new SomeOtherList(40));
temp_2.Add(new SomeOtherList(30));
prodTwo.t=temp_2;
Product prodThree=new Product(1);
List<SomeOtherList> temp_3=new List<SomeOtherList>();
temp_3.Add(new SomeOtherList(10));
temp_3.Add(new SomeOtherList(20));
prodThree.t=temp_3;
productList.Add(prodOne);
productList.Add(prodTwo);
productList.Add(prodThree);
I want to get "SomeOtherList" objects common to all the Products. For example, for Product Id 1, I should get SomeOtherList object 20 as the common One.
I have written the following LINQ query to achieve this.
List<Product> t=productList
.GroupBy(p => p.Id)
.Where(g => g.Count() == productList.Count)
.Select(x => x.First())
.ToList();
But it does not give me what I want . Can someone point what is wrong with that query ?
What you want is the intersection between your SomeOtherList when the product is some specific value (i.e. 1).
So you need to first select the each Product which has the correct Id and then join their SomeOtherList together and group by the value.
To do this we need to flattern the each SomeOtherList for a Product, which we can do with SelectMany
Projects each element of a sequence to an IEnumerable and flattens the resulting sequences into one sequence.
Single Id
If we are only intrested in a single Id then we can do the following
var common1 = productList
.Where(product => product.Id == 1)
.SelectMany(product => product.t)
.GroupBy(groupValue => groupValue.value)
.Where(groupValue => groupValue.Count() > 1)
.Select(values => values.First());
This will:
Filter the products based on Id being equal to 1
Flattern each products SomeOtherList into one IEnumerable
Group each element based on SomeOtherList.value
Filter out any groups which only have 1 entry, as we only want those that are common
All Id's
If, however, we would like to get the list of all duplicates for each key then we can do the same as for a Single Id but have a first step where we group based on the Id.
var common = productList
.GroupBy(groupId => groupId.Id)
.Select(groupId => groupId.SelectMany(product => product.t)
.GroupBy(groupValue => groupValue.value)
.Where(groupValue => groupValue.Count() > 1)
.Select(values => values.First().value));
Difficult to do in single LINQ query, but following code should work.
var min = productList.Min(x=>x.Id);
var max = productList.Max(x=>x.Id);
for(int i = min; i<=max;i++)
{
var products = productList.Where(x=>x.Id = i).ToList();
List<SomeOtherList> cList = new List<SomeOtherList>();
foreach(var product in products)
{
cList.AddRange(product.t);
}
var distinctList = cList.Distinct();
}
Because of the nested lists you need to group unwrap the list of SomeOtherList using SelectMany before grouping again. I think this is what you're after.
var result = productList
.GroupBy(p => p.Id)
.Select(pg =>
new
{
Id = pg.Key,
CommonOtherList = pg
.SelectMany(solg => solg.t)
.GroupBy(solg => solg.value)
.Where(solg => solg.Count() == pg.Count())
.Select(solg => new { OtherId = solg.Key })
});
foreach (var product in result)
{
Console.WriteLine(product.Id);
Console.WriteLine("**Common**");
foreach (var otherProduct in product.CommonOtherList)
{
Console.WriteLine("-->{0}", otherProduct.OtherId);
}
}

The type of the arguments cannot be inferred usage Linq GroupJoin

I'm trying to make a linq GroupJoin, and I receive the fore mentioned error. This is the code
public Dictionary<string, List<QuoteOrderline>> GetOrderlines(List<string> quoteNrs)
{
var quoteHeadersIds = portalDb.nquote_orderheaders
.Where(f => quoteNrs.Contains(f.QuoteOrderNumber))
.Select(f => f.ID).ToList();
List<nquote_orderlines> orderlines = portalDb.nquote_orderlines
.Where(f => quoteHeadersIds.Contains(f.QuoteHeaderID))
.ToList();
var toRet = quoteNrs
.GroupJoin(orderlines, q => q, o => o.QuoteHeaderID, (q => o) => new
{
quoteId = q,
orderlines = o.Select(g => new QuoteOrderline()
{
Description = g.Description,
ExtPrice = g.UnitPrice * g.Qty,
IsInOrder = g.IsInOrder,
PartNumber = g.PartNo,
Price = g.UnitPrice,
ProgramId = g.ProgramId,
Quantity = (int)g.Qty,
SKU = g.SKU
}).ToList()
});
}
I suspect this is the immediate problem:
(q => o) => new { ... }
I suspect you meant:
(q, o) => new { ... }
In other words, "here's a function taking a query and an order, and returning an anonymous type". The first syntax simply doesn't make sense - even thinking about higher ordered functions, you'd normally have q => o => ... rather than (q => o) => ....
Now that won't be enough on its own... because GroupJoin doesn't return a dictionary. (Indeed, you don't even have a return statement yet.) You'll need a ToDictionary call after that. Alternatively, it may well be more appropriate to return an ILookup<string, QuoteOrderLine> via ToLookup.

Categories