SelectMany doesn't work on cosmosDb for child properties? - .net-core

When I try to use selectMany on queryable that I build against cosmosdb I always get exception:
The LINQ expression 'DbSet
.Where(t => t.Id == __timelineId_0)
.SelectMany(
source: t => EF.Property>(t, "GraduationEvents")
.AsQueryable(),
collectionSelector: (t, c) => new TransparentIdentifier(
Outer = t,
Inner = c
))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
The query doesn't matter, always when I use selectMany I get this error.
Example query:
await _dbContext.Timelines.Where(x => x.Id == timelineId).Select(x => x.GraduationEvents).SelectMany(x => x).ToListAsync();
My entity configuration:
public void Configure(EntityTypeBuilder<Timeline> builder)
{
builder.HasKey(x => x.Id);
builder.HasAlternateKey(x => x.Id);
builder.OwnsMany(x => x.GraduationEvents, x => x.OwnsMany(graduationEvent => graduationEvent.Subjects));
}
I also tried to use native cosmosClient but when I query the base with plain sql I get empty objects (all nulls). Any thoughts what am I doing wrong?
Sajid - I tried your soulution but the exception remains the same

Try calling directly .SelectMany() over the List property (GraduationEvents).
I usually then call AsDocumentQuery() to generate the query to CosmosDB and then execute that query to retrieve the results.
Some pseudo c# to clarify this a bit:
var query = this.documentClient.CreateDocumentQuery(uri, options)
.SelectMany(x => x.GraduationEvents).AsDocumentQuery();
List<T> results = new List<T>();
while (query.HasMoreResults)
{
results.AddRange(await query.ExecuteNextAsync());
}
Edit: This approach uses the native Azure DocumentClient library.

Related

EF Core 3.1 Query results in an InvalidOperationException Lambda expression used inside Include is not valid

Why does this EF Core 3.1 typical query result in:
System.InvalidOperationException: Lambda expression used inside
Include is not valid.
(Note: IgnoreQueryFilters() is not the issue here. Even without it, it results in the same error)
var tenant = await context.Tenants.IgnoreQueryFilters()
.Include(i => i.Locations).ThenInclude(x => x.CurrentResidents.DefaultIfEmpty()).IgnoreQueryFilters()
.Include(i => i.Locations).ThenInclude(x => x.DefaultResidents.DefaultIfEmpty()).IgnoreQueryFilters()
.Where(l => l.Id == tenantId)
.FirstOrDefaultAsync()
.ConfigureAwait(false);
Here's my sql interpretation of what I'm trying to achieve:
select t.* from tenants
left join locations l on l.TenantId = t.Id
left join residents c on c.CurrentLocationId = l.Id
left join residents d on d.DefaultLocationId = l.Id
where t.Id = #tenantId
Please help. :pray:
Your problem is including Locations twice. Not sure from your SQL what you are trying to retrieve. The residents for whom both CurrentLocationId AND DefaultLocationId matches LocationId? then use AND in the LINQ statement.
If something else, then what is the type of CurrentResidents and DefaultResidents? Same Resident type? Then you probably want to get two sets...
Please provide more information...
As Felix pointed out the problem is that you include Locations twice which is not correct in ef core. Here is how you can achieve this:
var tenant = await context.Tenants
.IgnoreQueryFilters()
.Include(i => i.Locations.CurrentResidents.DefaultIfEmpty())
.Include(i => i.Locations.DefaultResidents.DefaultIfEmpty())
.FirstOrDefaultAsync(l => l.Id == tenantId, cancellationToken)
.ConfigureAwait(false);
Calling IgnoreQueryFilters once is sufficient, you don't need to call it 3 times.
Calling where and the first or default is unnecessary. You can just use the lambda in the first or default.
This was what I was looking for:
var tenant = await context.Tenants
.IgnoreQueryFilters()
.Include(i => i.Locations).ThenInclude(x => x.CurrentResidents)
.Include(i => i.Locations).ThenInclude(x => x.CurrentResidents)
.FirstOrDefaultAsync(l => l.Id == tenantId)
.ConfigureAwait(false);
Sql generated by ef core is a little weird but yields the result I'm interested in so all good.
#bencel: thank you for pointing out those issues. I was focused on getting the query running so overlooked those.
Thanks everyone!!

Multiple request instead of one in EF Core

I have this select which I expected to be executed by sending one command to SQL Server but I can see 10s requests instead. How can I fix it?
We have Companies which have Customers and Orders. For some reasons Orders are under Company entity.
var q = _dbContext.Companies
.Include(x => x.Customers)
.Include(c => c.Orders)
.Where(a => a.CompanyId == 123);
var total = await q.CountAsync();
q = q.OrderByDescending(x => x.CompanyCode)
.Skip((pageIndex - 1) * pageSize).Take(pageSize);
var res = await q.Select(x => new ResultDto()
{
CompanyCode = x.CompanyCode,
Customers = x.Customers
.Where(c => c.IsActive)
.Select(c => new CustomerDto()
{
FirstName = c.FirstName,
Surname = c.Surname,
Orders = x.Orders
.Where(o => o.IsOpen)
.Select(o => new OrderDto()
{
DateCreated = o.DateCreated
}).ToList()
}).FirstOrDefault(),
}).ToListAsync();
This is EF.NetCore optimization.
You actually cant achieve one query when your navigation properties are collections.
I can't find any links right now, but this is by design.
Once you have a collection in your navigations inside select or inside includes it will produce a separate query for each root entity. The reason I believe is the redundant data amount produced by such a query.
I suggest leave it as is if you have not a lot of data < 1000 rows in a result. You will see a lot of queries but they will be really fast.
As I can see you have pagination here so it shouldn't be a problem.
Otherwise, select your collections separately and join them in memory carefully.
Unfortunately, there is no other way for EF Core
Also, I recommend to turn on EF core logs to what is going on early. I bet FirstOrDefault will produce some warning.

MVC5 combine db .Include with OrderBy

In my controller I'm including Balances and RZiS like this
Analysis1Full analysis1Full = db.Analysis1Full
.Include(u => u.Balance)
.Include(r => r.RZiS)
.SingleOrDefault(u => u.Analysis1FullId == id);
//enter code here
it gives me right object. The point is that I want to sort Balance by string filed (Year). But it gives me an error:
Analysis1Full analysis1Full = db.Analysis1Full
.Include(u => u.Balance.OrderBy(y => y.Year))
.Include(r => r.RZiS)
.SingleOrDefault(i => i.Analysis1FullId == id);
Error:
An exception of type 'System.ArgumentException' occurred in EntityFramework.dll but was not handled in user code
Additional information: The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
The reason why the exception is thrown is simply because it is designed this way. According to MSDN, the Include() only Specifies the related objects to include in the query results.
To have the children in an order, you can add an OrderBy() when accessing them or you'd need to load them in a different query.
I solved it like this ... it is working
Analysis1Full analysis1Full = db.Analysis1Full.Find(id);
analysis1Full.Balance = db.Balances.Where(t=>t.Analysis1FullId == analysis1Full.Analysis1FullId).OrderBy(y=>y.Year).ToList();
analysis1Full.RZiS = db.RZiS.Where(t => t.Analysis1FullId == analysis1Full.Analysis1FullId).OrderBy(y => y.Year).ToList();
do you think it's good approach ?

Search for any word in a Linq query

I'm passing a string to a controller, and need the controller to search for ANY of the words passed, within the Title field of my database.
eg. id="outlook error message"
[HttpPost]
public JsonResult Lookup(string id)
{
List<string> listOfSearch = id.Split(' ').ToList();
var results = db.KS.Where(x => x.Title.Intersect(listOfSearch).Any());
This produces the following two errors:
Instance argument: cannot convert from 'string' to 'System.Linq.IQueryable<string>
'string' does not contain a definition for 'Intersect' and the best extension method overload 'System.Linq.Queryable.Intersect<TSource>(System.Linq.IQueryable<TSource>, System.Collections.Generic.IEnumerable<TSource>)' has some invalid arguments
Can anyone please advise what's wrong, or how to populate results with just a list of Titles that contain any of the words passed in?
Thanks, Mark
var results = db.KS.Where(x => listOfSearch.Any(item => x.Title.Contains(item)));
Update:
For LinqToSql:
var titles = db.KS.Select(item => item.Title)
.AsEnumerable()
.Where(title => listOfSearch.Any(word => title.Contains(word)));
you can try
List<string> listOfSearch = id.Split(' ').ToList();
var result = from item in db.KS
where listOfSearch.Any(v => item.Title.Contains(v))
select item;
or
var result = db.KS.Where(item => listOfSearch.Any(v => item.Title.Contains(v)));
Change the statement to:
db.KS.Intersect(....
KS returns you the IQueryable on which you can directly perform the intersection.

Linq Query with .Any condition returns nullreference exception

I have the following function that returns results from the database based on LINQ Expressions:
IQueryable<TEntity> FindAll<TEntity>(Expression<Func<TEntity, bool>> expression)
When I try pulling data from the function while using the .Any function from a list I get a null reference exception.
However when I pull the data without that specific condition and use the same .Any function in a for each loop everything works correctly.
Here is the call trying to use the .Any function which does not work:
var ppcReports = repository.FindAll<PPCReport>(
x => x.ClientId == clientId &&
(campaigns.Any(c=> c.Id == x.CampaignId))
).ToList();
And the way it does work properly:
var ppcReports = repository.FindAll<PPCReport>(
x => x.ClientId == clientId).ToList();
foreach (var item in ppcReports)
{
if (campaigns.Any(c => c.Id == item.CampaignId))
{
// do something
}
}
I was wondering why was this happening, am I doing something wrong is it just not possible to filter the results before the query finished?
By calling .ToList() before filtering the results it does work, so I suppose I cannot do such an operation on an IQueryable<T> implementation?
var ppcReports = repository.
FindAll<PPCReport>(x => x.ClientId == clientId).
ToList().
Where(w => campaigns.Any(c => c.Id == w.CampaignId)).
ToList();
Like those who commented, I'm surprised that you got a NullReferenceException rather than a complaint about not being able to compile that statement to SQL. However, the following code should let you do this in 1 query (and will do all filtering in SQL):
var campaignIds = (campaigns ?? Enumerable.Empty<Campaign>())
.Select(c => c.Id);
var ppcReports = repository
.FindAll<PPCReport>(pr => pr.ClientId == clientId
&& campaignIds.Contains(pr.CampaignId))
.ToList();
This should work in both EF and Linq-to-SQL.
Queryable.Any() returns an ArgumentNullException when the source is null, as documented here: https://msdn.microsoft.com/it-it/library/bb343630(v=vs.110).aspx

Resources