EF Core: The LINQ expression could not be translated - entity-framework

I am trying to implement a query in EF Core in which I need to get data where any name in a string array is contained by the name of the data object. Here is the code sample:
var searchKeys = search.Split(' ');
var objects = _db.Objects
.Where(o => searchKeys.Any(k => o.name.Contains(k))))
.OrderBy(o => o.Name)
.Select o
But the query cannot be translated resulting in the following error:
The LINQ expression 'DbSet
.Where(o => __searchKeys_1
.Any(k => __Functions_2
.Contains(
_: o.Name,
propertyReference: k)))' 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.
How can I contruct the query to fix the error?

For some reason, I could not get Obie's answer to work for me in .Net Core 3.1. Luckily, the LinqKit NuGet package has been upgraded for .Net Core. So I defined this extension method:
// Where any search predicates are true.
public static IQueryable<T> WhereAny<T>(this IQueryable<T> q, params Expression<Func<T, bool>>[] predicates)
{
var orPredicate = PredicateBuilder.New<T>();
foreach (var predicate in predicates)
{
orPredicate = orPredicate.Or(predicate);
}
return q.AsExpandable().Where(orPredicate);
}
And then you would use it like this:
var searchKeys = search.Split(' ');
var predicates = searchKeys.Select(k => (Expression<Func<MyObject, bool>>)(x => x.Name.Contains(k)));
var objects = _db.Objects
.WhereAny(predicates.ToArray())
.OrderBy(o => o.Name);

This works for me:
var searchKeys = search.Split(' ');
var objects = _db.Objects
.Where(o => searchKeys.Any(k => o.Name.Contains(k)))
.OrderBy(o => o.Name)
.Select(o => o);

Related

Comparing OffSetDateTime's Date part with Today in EF/Linq with NodaTime

I am using ASP.NET core 3.1.
I am trying to retrieve a list of sessions today that have finished.
I can retrieve all sessions that have finished like this:
var zonedClock = SystemClock.Instance.InTzdbSystemDefaultZone();
OffsetDateTime now = zonedClock.GetCurrentOffsetDateTime();
return _context.Session
.Where(x => (Local.Compare(x.EndDateTime, now) <0))
.Include(o => o.Tournaments)
.ToList<Session>();
where EndDateTime is a NodaTime OffsetDateTime and Local is an OffsetDateTime.Comparer. However when I attempt to also match the date like this:
return _context.Session
.Where(x => (Local.Compare(x.EndDateTime, now) <0)
&& x.StartDateTime.Date.Equals(now.Date))
.Include(o => o.Tournaments)
.ToList<Session>();
I get the following error:
InvalidOperationException: The LINQ expression 'DbSet<Session>()
.LeftJoin(
inner: DbSet<Venue>(),
outerKeySelector: s => EF.Property<Nullable<int>>(s, "VenueId"),
innerKeySelector: v => EF.Property<Nullable<int>>(v, "VenueId"),
resultSelector: (o, i) => new TransparentIdentifier<Session, Venue>(
Outer = o,
Inner = i
))
.Where(s => __Local_0.Compare(
x: s.Outer.EndDateTime,
y: __now_1) >= 0 && s.Outer.StartDateTime.Date.Equals(__now_Date_2) && s.Inner == __Venue_3 && s.Outer.Lane == __Lane_4)' 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 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.<VisitMethodCall>g__CheckTranslated|15_0(ShapedQueryExpression translated, ref <>c__DisplayClass15_0 )
adding .AsEnumerable() doesn't appear to help.
Any ideas?
I solved this. Needed to include the tournaments table at the start, then use .AsEnumerable before the .Where then generate instances of the comparer from the static like so:
return _context.Session
.Include(o => o.Tournaments)
.AsEnumerable()
.Where(x => (OffsetDateTime.Comparer.Local.Compare(x.EndDateTime, now) <0)
&& x.StartDateTime.Date.Equals(now.Date))
.ToList<Session>();

Use method inside LINQ GroupBy

I'm trying to manipulate properties in a GroupBy clause to be used in a dictionary:
var lifeStages = await _dbContext.Customers
.GroupBy(x => GetLifeStage(x.DoB))
.Select(x => new { LifeStage = x.Key, Count = x.Count() })
.ToDictionaryAsync(x => x.LifeStage, x => x.Count);
I'm expecting results like
adolescent: 10,
adult: 15,
senior: 12 etc
But getting error:
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().
Offcourse I can't combine ToDictionary() with any of the mentioned calls, and splitting up the query did not resolve the issues or taught my anything)
I've tried with making GetLifeStage() static and async, no difference there as well. The method gets called, performs what it needs to do, and still GroupBy can't be translated
If I leave out the Select() part and work with the Key of the GroupBy, same error:
"...could not be translated."
I saw an error too that said I couldn't combine a GroupBy() with a ToDictionary() during try-outs, but doesn't seem to pop up atm.
As I'm running out of ideas, all suggestions are welcome!
update:
private LifeStage GetLifeStage(DateTimeOffset doB)
{
var ageInMonths = Math.Abs(12 * (doB.Year - DateTimeOffset.UtcNow.Year) + doB.Month - DateTimeOffset.UtcNow.Month);
switch (ageInMonths)
{
case < 216:
return LifeStage.Adolescent;
case < 780:
return LifeStage.Adult;
case >= 780:
return LifeStage.Senior;
}
}
The problem is the usage of the custom GetLifeStage method inside the GroupBy expression. Custom methods cannot be translated to SQL because the query translator code has no way to know what is inside that method. And it cannot be called because there are no objects at all during the translation process.
In order to make it translatable, you have to replace the custom method call with its body, converted to translatable expression - basically something which can be used as expression bodied method. You can't use variables and switch, but you can use conditional operators. Instead of variable, you could use intermediate projection (Select).
Here is the equivalent translatable query:
var lifeStages = await _dbContext.Customers
.Select(c => new { Customer = c, AgeInMonths = Math.Abs(12 * (c.DoB.Year - DateTimeOffset.UtcNow.Year) + c.DoB.Month - DateTimeOffset.UtcNow.Month) })
.GroupBy(x => x.AgeInMonths < 216 ? LifeStage.Adolescent : x.AgeInMonths < 780 ? LifeStage.Adult : LifeStage.Senior)
// the rest is the same as original
.Select(x => new { LifeStage = x.Key, Count = x.Count() })
.ToDictionaryAsync(x => x.LifeStage, x => x.Count);

Dynamic linq query in EF

I am using dynamic LINQ query in EF4.
Below code throws error: 'New' cannot be resolved into a valid type or function.
var x = ent.OM_COMPANY
.Where(qry)
.OrderBy("it.CM_CODE")
.Select("New(it.CM_CODE, it.CM_NAME)");
What am I doing wrong?
The below code executes without any error.
var x = from cmp in ent.OM_COMPANY
where (qry)
orderby cmp.CM_CODE
select new { cmp.CM_CODE, cmp.CM_NAME };
I don't even know how you got that first code block to compile. Both OrderBy and Select take lambdas not strings. It should be written as:
var x = ent.OM_COMPANY
.Where(qry)
.OrderBy(c => c.CM_CODE)
.Select(c => c.CM_CODE, c.CM_NAME);

Generic repository: how to filter eager-loaded navigation properties

this is my generic respoitory that im using, as the title states I want to know how to filter the navigation properties.
public IEnumerable<T> Query(
Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
string includeProperties = "")
{
IQueryable<T> query = _objectSet.Where(e => !e.IsDeleted);
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
Controller:
var viewModel = new StudentViewModel();
viewModel.Students= _unitOfWork.Students.Query(
includeProperties: "Subjects, Instructors");
Now my problem is I want to add a .Where(e => !e.IsDeleted)
to [Subjects] and [Instructors] using the repository.
Thanks
EDIT:
according to Ladislav, currently it is not possible (also mentioned here in msdn: http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx)
Can i just use this instead?
viewModel.Subjects = viewModel.Students.Where(i => i.StudentID ==Id.Value)
.Single().Subjects.Where(e => !e.IsDeleted);
My only worry is that the query may return lots of records with isDeleted==true. Sure the code I posted as an alternative works, I just don't want to pull the data that I don't need even though I can filter it using the above code
LINQ to SQL supports this scenario using the LoadWith DataLoadOption. The example at http://msdn.microsoft.com/en-us/library/system.data.linq.dataloadoptions.loadwith.aspx shows the simple case that EF does support with the Include statement.
Northwnd db = new Northwnd(#"c:\northwnd.mdf");
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Customer>(c => c.Orders);
db.LoadOptions = dlo;
However, unlike EF, LINQ to SQL also supports the following:
dlo.LoadWith<Customer>(c => c.Orders.Where(o => o.ShippedDate is Null);
If you feel this is an important enhancement scenario for EF, consider voting for it at http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1015345-allow-filtering-for-include-extension-method.
For now, your best option is to project your filter in the Select clause, but that gets tricky with a generic repository.

In Linq to Entities can you convert an IQueryable into a string of SQL?

eg.
var result = myObject.Where(x => x.prop == 5);
string s = result.toSQL();
Result:
s is "SELECT * FROM [myObjects] WHERE prop = 5"
If it's IQueryable/ObjectQuery you can use ToTraceString. If it's IDbSet/DbSet you can use ToString directly.
Using EF 6 I could not get .ToString() to return SQL for something similar to:
db.Entry(parent)
.Collection(p => p.Children)
.Query()
.Where(c => c.Active)
.Load();
Then I remembered you can log it to debug output with:
db.Database.Log = (entry) => System.Diagnostics.Debug.WriteLine(entry);
Just set above before loading queries (duh) :)
db here is an instance of some DbContext derived class.