GroupBy OrderBy Linq - invalid column - linq

I am trying to build a very simple LinQ query used to return a json to my client.
the following works:
_context.MyTable
.GroupBy(g => new { type= g.TypeId })
.Select(x => x.Count())
However, I would like to retun the count list ordered by type so I wrote the following but it didn't work:
_context.MyTable
.GroupBy(g => new { type= g.TypeId })
.OrderBy(o => o.Key.type)
.Select(x => x.Count())
It returns a 'invalid column name "type"'
I don't get it, the orderby is occuring after the groupBy, I am using the key properly.
It looks like, the orderBy needs the criteria in the Slect statement to work properly.
For example, the following works:
_context.MyTable
.GroupBy(g => new { type= g.TypeId })
.OrderBy(o => o.Key.type)
.Select(x => {x.Count(), x.Key.type})
But it does not return what i want, I just want to get the count values ordered by type.
Any directions why I get this error.
I am using EF Core if it can help.
S.

Although EF Core 2.1 introduced improvements to LINQ GroupBy translation, the implementation is still far from perfect and produces exceptions in many scenarios due to invalid SQL generation. Your scenario is just one of them and is not working even with the recent at this time EF Core 2.1.1 bits.
The first thing you should do is to submit it to the EF Core issue tracker, so they know and fix it when possible.
The problem seems to be related to the key property aliasing (it also doesn't work if you use the "normal" .GroupBy(e => e.TypeId).OrderBy(g => g.Key)). The current workaround I've found is to use anonymous type key having the same property name(s) as the source(s):
_context.MyTable
.GroupBy(e => new { e.TypeId })
.OrderBy(g => g.Key.TypeId)
.Select(g => g.Count())

Related

Select two lists as one list in ASP.NET Core linq

I am trying to create a query with ASP.NET Core EF Core and Linq that would give me a List of users based on two different lists, something like this:
return await _context.Users
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Property)
.ThenInclude(p => p.PropertyTenantLeases)
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Owner)
.Where(u => u.Id == userID)
.Select(u => new List<User>()
{
u.PropertyTenantLeases.Select(ptl => ptl.Tenant).ToList()
u.PropertyOwners.Select(po => po.Owner).ToList()
}).FirstOrDefaultAsync();
The tables that are used in this query are connected in the following way:
Everything is fine with this query except for the Select, with the Select I am trying to achieve that it returns a list of all the tenants in the PropertyTenantLeases table which is a junction table togheter with all the Owners form the PropertyOwners junction table (both Tenant and Owner are IdentityUser classes. When I right this query like this I get the following error:
The best overloeaded Add method 'List<User>.Add(User)' for the collection initializer has some invalid arguments
and also
Argument 1: cannot convert from 'System.Collections.Generic.List<RosyMasterDBManagement.Models.User>' to 'RosyMasterDBManagement.Models.User'
Joining two list is called a union in Linq -- I believe that is what you want:
note: I still can't test this since you gave a picture of the data model instead of the code that would allow me to be certain of how to implement. expect the fields to be named incorrectly etc.
var ownerlist = _context.Users
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Owner)
.ToList();
var tenantlist = _context.Users
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Property)
.ThenInclude(p => p.PropertyTenantLeases)
.ThenInclude(po => po.Tenant)
.ToList();
return ownerlist.Union(tenantlist);
I don't believe you need await() since ToList() forces it to not be lazy. But I could be wrong about that.

How to asynchronously call a database with subrecords using LINQ?

I'm using EF6 and want to make the following query fully asynchronous:
await MyDataContext.ADbSet.
First(a => a.Something == "Something").
ASubCollection.
Select(x => new { x.SubCollectionId }).
ToListAsync();
This doesn't work, I believe due to First() returning the actual entity and access to ASubCollection being an ICollection, not an IQueryable.
I was able to work around this with the following code:
await MyDataContext.ADbSet.
Where(a => a.Something == "Something").
SelectMany(a => a.ASubCollection).
Select(x => new { x.SubCollectionId }).
ToListAsync();
However, this seems "hacky" as I'm using a Where(...) when I should be using a First() as I know at compile time that there will be exactly one element satisfying the query. Is there a better way of doing this?
The call to First() is a call that actually enumerates the underlying sequence and returns an entity instead of a Task. Thus First() won't work together with the await-keyword.
Your second solution is completely valid (and not "hacky" at all) in this context, because there is no need to add a limit to the generated database query, as the Where(...)-call will return exactly one element in this special case - with or without a limit in the query.
If the Where-call is likely to return multiple elements, or you simply want to ensure that there will be only the first element considered, inserting a call to Take(1) will bring the first element of the sequence, but still be an IQueryable:
await MyDataContext.ADbSet
.Where(a => a.Something == "Something")
.Take(1)
.SelectMany(a => a.ASubCollection)
.Select(x => new { x.SubCollectionId })
.ToListAsync();

Need some help on a somewhat complex LINQ query

I have this query that does what I want which is to return true if any of the materials is comparable in the material groups list.
mgroup.MaterialGroups.Select(x => x.Materials
.Any(m => Convert.ToBoolean(m.Comparable)))
.Any(x => x.Equals(true))
What I would like to add to this query is to also include this one.
mgroup.Materials.Any(m => Convert.ToBoolean(m.Comparable));
How can I combine mgroup and it's materialgroups together in the query so I can select both of their Materials? Thanks.
EDIT - After fighting with LINQ for awhile I broke down and just combined as
mgroup.Materials.Any(m => Convert.ToBoolean(m.Comparable) ||
mgroup.MaterialGroups.Select(x => x.Materials
.Any(c => Convert.ToBoolean(c.Comparable)))
.Any(x => x.Equals(true)))
It works as expected but it's horribly long and it's embedded in an Asp.net MVC view to makes things even worse. If anyone can simplify this that would be amazing.
P.S.- If you are wondering why I added the extra .Any(x => x.Equals(true) at the end it's because without it the query returns an IEnumerable of bools instead of bool.
IEnumerable<Material> allMaterials =
mgroup.Materials.Concat(
mgroup.MaterialGroups.SelectMany(group => group.Materials));
bool result = allMaterials.Any(m => Convert.ToBoolean(m.Comparable));

LINQ query works agains SQL Server but not Entity Framework/LINQ to SQL

I have a LINQ query which works fine when I test it in LINQPad agains my SQL Server database, however when I test it against my Entity Framework context (which is required in my LightSwitch .NET app) it gives an error that .First() must be user as a final operation.
Table.Where(re => !re.UserSave.HasValue || re.UserSave == INT PARAMETER)
.GroupBy(re => re.UniqueKey)
.Select(g => g.FirstOrDefault(x => x.UserSave.HasValue) ?? g.First())
It suggest that using FirstOrDefault() instead, but since i call First() only if FirstOrDefault() returns false I have no idea how to get around this problem.
NotSupportedException: The method 'First' can only be used as a final query operation. Consider using the method 'FirstOrDefault' in this instance instead.
Thanks for any help!
Change your query to use FirstOrDefault instead of First:
Table.Where(re => !re.UserSave.HasValue || re.UserSave == INT PARAMETER)
.GroupBy(re => re.UniqueKey)
.Select(g => g.FirstOrDefault(x => x.UserSave.HasValue) ?? g.FirstOrDefault())

Linq ToDictionary Not Defined?

I have this code
var contacts = dr.mktDoctorContacts
.GroupBy(x => x.ContactType)
.Select(zb => new
{
Key = zb.Key,
GroupWiseContacts = zb.Select(x => x.Contact).ToList()
})
.ToDictionary<string,List<string>>(y => y.Key, y => y.GroupWiseContacts)
I don't know what is wrong with this code.
Compile time error msg says:System.Generic.IEnumerable does not contain definition of and best extension method overloads has some invalid arguments. i can see only two overloads of ToDictionary Method in my visual studio tooltip sort of documentation whereas i have come across more than two overloads of ToDictionary on the web
Edit Here is exact Error message at compile time
Error 13 'System.Collections.Generic.IEnumerable<AnonymousType#1>'
does not contain a definition for
'ToDictionary' and the best extension
method overload
'System.Linq.Enumerable.ToDictionary<TSource,TKey>(System.Collections.Generic.IEnumerable<TSource>,
System.Func<TSource,TKey>,
System.Collections.Generic.IEqualityComparer<TKey>)'
has some invalid arguments
The compiler message makes the error clear: There is no method ToDictionary which can accept the arguments and types specified.
The mistake here is in specifying the type arguments on ToDictionary. The MSDN documentation defines the extension method as ToDictionary<TKey, TSource>. The source in your example is IEnumerable<AnonymousType#1>, but you have specified a type of List<string>.
To correct the error, omit the type arguments; the compiler will infer the correct types. You can additionally combine the Select and ToDictionary transformations into a single statement:
var contacts = dr.mktDoctorContacts
.GroupBy(x => x.ContactType)
.ToDictionary(
y => y.Key,
y => y.Select(x => x.Contact).ToList());
Rewrote your code (and added .AsEnumerable()):
var dictionary = dr.mktDoctorContacts
.GroupBy(x => x.ContactType)
.AsEnumerable()
.ToDictionary(
i => i.Key,
i => i.Select(x => x.Contact).ToList()
)
);
Don't run that group operation in the database, it'll cause the elements of each group to be fetched in separate roundtrips.
ILookup<string, string> contacts = dr.mktDoctorContacts
.ToLookup<Contact, string, string>(x => x.ContactType, x => x.Contact);

Resources