How to asynchronously call a database with subrecords using LINQ? - 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();

Related

GroupBy OrderBy Linq - invalid column

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())

Linq to Entities Query explanation

Is there any way I can make this Linq to entities query in another way (better) and understand what I did?
First, can I have the string.jon() in the first part (select(p => new {...)?
Second, why do I need the first select to end with .ToList() for the string.join() to work?
The tables relation are as follow:
And here is the code:
Productos.Select(p => new {
Id = p.Id,
Code = p.CodigoProd,
Name = p.Nombre,
Cant = p.Inventario.Sum(i => i.Cantidad),
Pric = p.Inventario.OrderBy(i => i.Precio).Select (i => i.Precio).FirstOrDefault(),
cate = p.ProductosXCategoria.Select(pc => pc.CategoriasdeProducto.Nombre)
}).Where (p => p.Cant != null).ToList()
.Select (r => new {
r.Id, r.Code, r.Cant, r.Name, r.Pric, Categ = string.Join("-",r.cate)
})
the result is this (which is the result i expected to be):
IEnumerable<> (17 items)
**Id-- Code-- Cant-- Name-- Pric-- Categ**
1-- AXI-- 30-- Pepsi-- 10-- Granos
3-- ASI-- 38-- Carne blanca-- 12-- Granos-Limpieza
The query looks fine to me.
The reason you can't move the string.Join method to the first Select, is that LINQ-to-Entities ultimately has to be able to translate to SQL. string.Join has no direct translation to SQL, so it doesn't know how to translate your LINQ query to it. By calling ToList() first, you bring the results of the first Select into memory, where the subsequent Select works with Linq-to-Objects. Since Linq-to-Objects does not need to translate to SQL, it can operate directly on the results of the first query in memory.
Generally, you would want to put everything that would be better left to SQL before the ToList() call (such as filtering, sorting, averaging, grouping, etc.), and leave additional work that can't be translated to SQL (or isn't as efficient to do so) for after the results have been brought into local memory.

Can this code be converted into a single linq statement?

I'm trying to filter a list within a list from an entity framework entity.
I've managed to get the code working however, i'm not convinced it's the cleanest way of achieving the goal.
Here's the code I have so far:
foreach (var n1 in tier.MatchNodes)
{
n1.LenderMatchNodes = n1.LenderMatchNodes.Where(x => x.Commission == 0).ToList();
}
Effectively MatchNodes contains a collection of LenderMatchNodes, however I want to return only the nodes where the commission == 0.
Thanks in advance.
Try
tier.MatchNodes.ToList().ForEach(n1=>n1.LenderMatchNodes = n1.LenderMatchNodes.Where(x => x.Commission == 0).ToList());
Try using SelectMany():
var result = dataContext.Table<Tier>()
.Where(some condition to get you the tier)
.SelectMany(tier => tier.MatchNodes)
.SelectMany(node => node.LenderMatchNodes)
.Where(x => x.Commission == 0)
.ToList();
This has the additional benefit of being able to execute it a single SQL query.
If you're goal is to actually update the node list in the database, you can still minimize the number of queries using Include() (assuming you're using EF):
var nodes = dataContext.Table<Tier>()
.Where(some condition to get you the tier)
.SelectMany(tier => tier.MatchNodes)
.Include(node => node.LenderMatchNodes) // loads this eagerly
.ToList();
nodes.ForEach(n => n.LenderMatchNodes = n.LenderMatchNodes.Where(condition));

preserving the order of returning entities when using .Contains(Id)

I want to hydrate a collection of entities by passing in a List of Ids and also preserve the order.
Another SO answer https://stackoverflow.com/a/15187081/1059911 suggested this approach to hydrating the entities which works great
var entities = db.MyEntities.Where(e => myListOfIds.Contains(e.ID)).ToList();
however the order of entities in the collection is different from the order of Ids
Is there a way to preserve the order?
May be that helps:
var entities = db.MyEntities
.Where(e => myListOfIds.Contains(e.ID))
.OrderBy(e => myListOfIds.IndexOf(e.ID)).ToList();
EDIT
JohnnyHK clarified that this will not work with LINQ to Entities. For this to work you need to order IEnumerable instead of IQueryable, since IQueryProvider don't know how to deal with local list IndexOf method when it sends query to server. But after AsEnumerable() OrderBy method deals with local data. So you can do this:
var entities = db.MyEntities
.Where(e => myListOfIds.Contains(e.ID))
.AsEnumerable()
.OrderBy(e => myListOfIds.IndexOf(e.ID)).ToList();
Entity Framework contains a subset of all of the LINQ commands so you won't have all the commands that LINQ to Objects has.
The following approach should give you your list of MyEntities in the same order as supplied by myListOfIds:
var entities = myListOfIds.Join(db.MyEntities, m => m, e => e.ID, (m,e) => e)
.ToList();

Optimising Lambda Linq to SQL query with OrderBy

I have the following lambda expression:
IEnumerable<Order> query
= _ordersRepository.GetAllByFilter(
o =>
o.OrderStatus.OrderByDescending(os => os.Status.Date).First()
.Status.StatusType.DisplayName != "Completed"
||
o.OrderStatus.OrderByDescending(os => os.Status.Date).First()
.Status.Date > sinceDate
).OrderBy(o => o.DueDate);
As you can see, I'm having to order the collection twice within the main query (so three times in total) in order to perform my OR query.
1) Is the query optimiser clever enough to deal with this in an efficient way?
2) If not, how can I rewrite this expression to only order by once, but keeping with lambda syntax?
This is linked to this previous question, which explains the query in a bit more detail if the above code isn't clear.
1) Is the query optimiser clever enough to deal with this in an efficient way?
You can get the SQL for this query (one way is to use the SQL profiler), and then ask SQL Studio for the execution plan. Unless you do this, there is no way to know what the optimizer thinks. My guess is the answer is "no".
2) If not, how can I rewrite this expression to only order by once, but keeping with lambda syntax?
Like this:
IEnumerable<Order> query = _ordersRepository.GetAllByFilter( o =>
o.OrderStatus
.OrderByDescending(os => os.Status.Date)
.Take(1)
.Any(os => os.Status.StatusType.DisplayName != "Completed"
|| os.Status.Date > sinceDate)
})
.OrderBy(o => o.DueDate);
Regarding your first point: You can see the SQL that is generated by subscribing to the output of the DatabaseContext object. This is usually in a property called Log.
As for optimising your query, try the following (I've not tested it so I don't know if it will work)
IEnumerable<Order> query
= _ordersRepository.GetAllByFilter(
o =>
o.OrderStatus.Max(os => os.Status.Date).Any(os =>
os.Status.StatusType.DisplayName != "Completed"
|| os.Status.Date > sinceDate)
).OrderBy(o => o.DueDate);
Hopefully that will only perform the subquery once, and also performs a max rather than an order by with top 1.

Resources