Fields not visible after groupby? - c#

What I have now:
var batch_pymnts2 = (from a in ctx.WarehouseStatementBatchPayments
join b in ctx.WarehouseStatementBatches on a.WarehouseStatementBatchID equals b.ID
join c in ctx.WarehousePaymentInvoices on a.ID equals c.WarehouseStatementBatchPaymentID
where b.ID == batchID
select new
{
PaymentId = a.ID,
PaymentNet = a.Net,
PaymentType = a.Type
})
.GroupBy(d => d.PaymentId).Where(x => x.Count() == 1);
I need to query these results like so:
var test = (from a in batch_pymnts2 where a.PaymentNet > 100 select a).ToList();
However, I cant see the fields of the (anonymous) type that the first statement uses to project the results into.
Will I need to use a defined type in the query for the projection? Is there a way to do it with anonymous types?
[update]
I managed to change the source query a bit, moving the group by inside and before the group by. This lets the fields of the anonymous type being projected, be "exposed" in further statements.
var count2 = (from a in WarehouseStatementBatchPayments
join b in WarehouseStatementBatches on a.WarehouseStatementBatchID equals b.ID
join c in WarehousePaymentInvoices on a.ID equals c.WarehouseStatementBatchPaymentID
group a by a.ID into grp
from d in grp
where d.WarehouseStatementBatchID == batchID && grp.Count() == 1
select new { PaymentId = d.ID, PaymentNet = d.Net, PaymentType = d.Type }).ToList();

batch_pymnts2 is a sequence of group objects. In effect, it is a collection of collections of your anonymous type. Each item in batch_pymnts2 has this:
group.Key; /* a PaymentId value */
((IEnumerable)group); /* the anon type items grouped together in this group */
Those group objects implement the IGrouping interface. Their Key property is the PaymentId values that define the groups. If you enumerate the groups (they implement IEnumerable<T>), you'll get the anonymous objects that you grouped by PaymentId:
var test = batch_pymnts2.SelectMany(g => g.Where(anon => anon.PaymentNet > 100));
test is now an enumeration of your anonymous type, because we have now enumerated a subset of the anon items from each of the groups, and (in effect) unioned all those little enumerations of anon back into one big one.
If you want to select groups which have at least one anonymous thingy with PaymentNet > 100, try this:
// Groups which have at least one PaymentNet > 100
var t2 = batch_pymnts2.Where(g => g.Any(anon => anon.PaymentNet > 100));
// PaymentIds of the groups which have at least one PaymentNet > 100
var ids = t2.Select(g => g.Key);
// PaymentIds that appear only once
var singles = t2.Where(g => g.Count == 1).Select(g => g.Key);
I don't know why you're grouping them, or what your PaymentNet > 100 query is meant to accomplish, so I'm not sure exactly how to write the query you want. But your starting point is that you're querying a sequence of group objects which contain enumerations of your anonymous type -- not a sequence of that type itself.

Related

Linq using join and grouping

I'm trying to do something very simple.
I have two tables in my database that I would like to query using linq.
Table of Books, and table of GenreTypes. The result of this query would go to my web Api.
Here is a code snippet:
public List<BooksChart> GetBooksChart()
{
var results = from b in _dbcontext.Books
join g in _dbcontext.GenreTypes
on b.GenreTypeId equals g.Id
group g by g.Name into n
select (z => new BooksChart
{
category_name = n.Key,
value = n.Count()
}).ToList();
return results;
}
public class BooksChart
{
public string category_name;
public int value;
}
The results of the grouping "n" I would like to store them in BooksChart class to construct the Api.
This code is not compiling.
Previously, I was querying only one table of Books which I have divided into Books and GenreTypes.
My previous working code for querying Books was :
var results = _dbcontext
.Books
.GroupBy(x => x.GenreType)
.Select(z => new BooksPieChart
{
category_name = z.Key,
value = z.Count()
}).ToList();
return results;
EDIT
What I want to achieve in SQL is the following:
select count(*), g.Name
from books b, GenreTypes g
where b.GenreTypeId = g.Id
group by g.Name;
You are mixing the two syntax options of query and method. For query syntax you need to do the projection (select) like this:
return (from b in _dbcontext.Books
join g in _dbcontext.GenreTypes on b.GenreTypeId equals g.Id
group g by g.Name into n
select new BooksChart {
category_name = n.Key,
value = n.Count()
}).ToList();
The format of (z =>....) is the declaration of the labmda passed to the Select method.
Site notes:
As #Rabbi commented, since you are using EF, consider properly defining navigation properties. It will make querying simpler.
Side note for the sql - consider using joins instead of multiple tables in the from: INNER JOIN ON vs WHERE clause
The parentheses must surround the whole query, like so:
var results = (from b in _dbcontext.Books
join g in _dbcontext.GenreTypes
on b.GenreTypeId equals g.Id
group g by g.Name into n
select new BooksChart
{
category_name = n.Key,
value = n.Count()
}).ToList();
The compilation error is due to this (z => which is not needed at all.

C# : How to get anonymous type from LINQ result

I need to get NewsImage field and list of categories Ids that associated with the news in Many to Many relationship ... but it gives me error:
The type of one of the expressions in the join clause is incorrect.Type inference failed in the call to 'Join'.
My code looks like this
var Result1 = (from c in db.News
join d in db.Categories
on c.NewsId equals d.News.Select(l => l.NewsId)
where c.NewsId == 1
select new { c.NewsImagePath, d.CategoryId }).ToList();
Assuming you have a navigation property defining the n-n relation I would write:
var result = db.News
.Where(x => x.NewsId == 1)
.SelectMany(x => x.Categories,
(news, category) => new { news.NewsImagePath, category.CategoryId })
.ToList();
The problem is inside the on statement.
on c.NewsId equals d.News.Select( l => l.NewsId )
The Select on the right-hand side will return a IEnumerable of news, which is not what you want.
Something like this would technically work:
on c.NewsId equals d.News.Select( l => l.NewsId ).FirstOrDefault()
But it does not make sense logically.
I suspect the whole query should be built differently. I think you want to join when the category list of news contains the news item. In that case, you can't use the join statement, it would look somewhat like this:
from n in db.News
from c in db.Categories
where c.News.Select( ne => ne.NewsId ).Contains( n.NewsId )
select new { n.NewsImagePath, c.CategoryId }

Order table according to the order in another table

I have table B and table R,
They are identical in their field definition.
Table R contains some of the items in B, ordered by a specific order (which is not related to any of the values)).
I want to select items from table B and have them ordered in the order in which they appear in table R.
Basically I need the result as IQueryable<B> - but if I simply try to select from R and cast as IQueryable<B> it throws an exception that I can't do that casting - so a different solution to achieve this is also acceptable.
You can do something like this:
Func<R, B> r2b = r => new B { Id = r.Id, Description = r.Description};
var bs = Rs.Select(r => r2b(r));
It depends on what you mean by order of table R.
You say the two tables have identical objects. So if you want to consider only the items in table R, in that case would want to join them. If no match is found you get an empty sequence. Something like:
var ordered = from b in bs
join r in rs
on b equals r //since you said they are identical
orderby r //or whatever R table is ordered with
select b;
If you want to merely get the sort order of table R (and provided the number of rows in both tables are equal), you can use Zip.
var ordered = bs.Zip(rs, (b, r) => new { b, r })
.OrderBy(x => x.r) //or whatever R table is ordered with
.Select(x = x.b);
Edit:
You have to specify on what basis the equality is defined in case of Join. Also on what basis the R table is ordered. You can specify it like:
var ordered = from b in bs
join r in rs
on b.Id equals r.Id //or whatever the equality is based on.
orderby r.Id //or whatever R table is ordered with
select b;
If you wan't the entire fields to be checked for equality then you can do:
var ordered = from b in bs
join r in rs
on new { b.Id, b.Name } equals new { r.Id, r.Name }
orderby r.Id
select b;
Same goes for the Zip approach:
var ordered = bs.Zip(rs, (b, r) => new { b, r })
.OrderBy(x => x.r.Id) //<- specify here
.Select(x = x.b);
If you want to merely get the rows for R as B, then Laurence's approach is what you have to go for.

Filter LINQ entity include query

I have the following two tables
Groups
Id (int)
People
Id (int)
GroupId (int, Groups.Id)
IsSelected (bit)
This will return all Groups with all their members(People) in a single query
var grps = myDatabase.Groups.Include("People");
How can I write a single query that will return all Groups with People who has been selected(IsSelected = true)?
let me know if this works
var grps = myDatabase.Groups.Select(g=> new { g, people = g.People.Where(p=>p.IsSelected)});
You will want to use the 'join' method, like this:
(from g in myDatabase.Groups
join p in myDatabase.People on g.Id equals p.GroupId
where p.IsSelected == true
select g);
This will give you all groups where there are people selected.
OR check out .Where()
Something like
var grps = myDatabase.Groups.Include("People").Where(x => x.IsSelected);
//x => !x.IsSelected for false

Linq getting a list from another list

I have two collections: one is Items And Another is ActiveItems
The only intersection between these two collection is Name
I want a list with Linq from Items where the Items names are in the ActiveItems with that name
I wrote this code is there a better idea:
Items.Where(i => ActiveItems.Count(v=> v.Name==i.Name) > 0)
I would probably create a set of the names from ActiveItems and then use that:
var activeNames = new HashSet<string>(activeItems.Select(x => x.Name));
var itemsWithActiveNames = items.Where(x => activeNames.Contains(x.Name))
.ToList();
Another option is to use a join, e.g. with a query expression:
var query = from activeItem in activeItems
join item in items on activeItem.Name equals item.Name
select item;
Note that this will give duplicate item values if there are multiple ActiveItem values with the same name. Another alternative join, which doesn't have this problem but is a bit clumsier:
var query = from item in items
join activeItem in activeItems
on item.Name equals activeItem.Name
into g
where g.Any()
select item;
Note that all of these will avoid the O(N * M) check for names - they'll all use hash tables behind the scenes, to give an O(N + M) complexity.
Items.where(i => ActiveItems.Any(a => i.Name == a.Name))
var results = from i1 in collection1.Items
join i2 in collection2.ActiveItems on i1.Name equals i2.Name
select i2.Name;
Using a join:
from item in Items
join active in ActiveItems on item.Name equals active.Name
select item

Categories