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

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

Related

Check value against multiple enum values in IQueryable

I am trying to write an elegant solution to filter a value against multiple Enum values in an IQueryable. Here's what I have got so far:
Extension
public static class EnumExtensions
{
public static bool IsAny<T>(this T value, params T[] choices)
where T : Enum
{
return choices.Contains(value);
}
}
Usage
query = query.Where(x => x.Status.IsAny(OrderStatus.Accepted, OrderStatus.Received, OrderStatus.Approved));
But when I execute this, I get following error:
An unhandled exception has occurred while executing the request.
System.InvalidOperationException: The LINQ expression 'DbSet<SalesOrder>
.Where(s => s.DeletedAt == null)
.Where(s => False || s.SellerId == __request_OwnerId_0)
.Where(s => s.Status
.IsAny(OrderStatus[] { Accepted, Received, Approved, }))' 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.
Any pointers on how I can get this working?
You cannot use custom methods inside Linq queries and in Expression<> params for EF. You will need to do it the long-form way:
query = query
.Where( x =>
x.Status == OrderStatus.Accepted ||
x.Status == OrderStatus.Received ||
x.Status == OrderStatus.Approved
);
Depending on your RDBMS, you might be able to do it this way though:
static readonly OrderStatus[] _these = new[] { OrderStatus.Accepted, OrderStatus.Received, OrderStatus.Approved };
// ...
query = query.Where( x => _these.Contains( x.Status ) );
...which should be translated to this SQL:
WHERE
x.Status IN ( 0, 1, 2 )
(Assuming OrderStatus.Accepted == 0, Received == 1, Approved == 2).

How to sort on DB side, if entities are not connected via Navigation (since it's not possible)

I want to have my EfCore query translated into the following SQL query:
select
c.blablabla
from
codes c
left join lookups l on c.codeId = l.entityid and l.languageCode = <variable - language code of current thread> and l.lookuptype = 'CODE'
where
..something..
order by
l.displayname
Note: tables 'codes' and 'lookups' are not connected! 'lookups' contains a lot of different lookup data in different languages!
I am stuck into limitations of EfCore (like 'NavigationExpandingExpressionVisitor' failed). I don't want to make in-memory filtering, it looks silly to me... Am I missing something obvious?
In perspective, I'd like to make universal method to help sort by displayname (or other lookup name) for different kind of entities - not only codes.
Seems like I figured it out. If there's a better approach - please let me know:
protected override IQueryable<FixCode> SortByDisplayName(IQueryable<FixCode> queryable, string languageCode = null)
{
return queryable
.GroupJoin(
DbContext.FixCodeValues.Where(x =>
x.DomainId == CentralToolConsts.Domains.CENTRAL_TOOLS
&& x.CodeName == CentralToolsFieldTypes.CODE_ORIGIN
&& (x.LanguageCode == languageCode || x.LanguageCode == CentralToolsDbLanguageCodes.English)),
//TODO: this will be a 'selector' parameter
code => code.CodeOriginId,
codeOrigin => codeOrigin.StringValue,
(c, co) => new
{
Code = c,
CodeOrigin = co
}
)
.SelectMany(
x => x.CodeOrigin.DefaultIfEmpty(),
(x, codeOrigin) => new { Code = x.Code, CodeOrigin = codeOrigin }
)
.OrderBy(x => x.CodeOrigin.ShortName)
.Select(x => x.Code);
}

Entity Framework Join inner Include

What is wrong ?
var tests = _repo.Tests.Include(a => a.Answers.Join(_repo.Questions, answer => answer.QuestionNumber, question => question.QuestionNumber, (answer, question) => new { Answer = answer, Question = question })).Where(u => u.User.UserName == username);
error CS0411: The type arguments for method 'Enumerable.Join(IEnumerable, IEnumerable, Func, Func, Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
You can use ThenInclude to join the other tables like this.
var tests = _repo.Tests
.Include(a => a.Answers)
.ThenInclude(q => q.Questions)
.Where(u => u.User.UserName == username)
.ToList();

EF Core: The LINQ expression could not be translated

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

Oring muliple things in a Where clause

I have this code
private CurrencyConversionResult GetNumberOfCurrencyUnitsPerEuro(CurrencyType from, CurrencyType to)
{
...
IEnumerable<ExchangeRate> rate = info.ExchangeRates.Where(e => e.CurrencySymbol == from.ToString() || e.CurrencySymbol == to.ToString()).ToList();
...
I want to change the signature of this method to
private CurrencyConversionResult GetNumberOfCurrencyUnitsPerEuro(IEnumerable<CurrencyType> from, CurrencyType to)
So, what I want to do now is get all ExchangeRates where e.CurrencySymbol is equal to to or any of the froms. Question is I don't know how to write in that in one statement so that there is only 1 database call. Any ideas?
var symbols = from.Select(f => f.ToString());
var rate = info.ExchangeRates
.Where(e => symbols.Contains(e.CurrencySymbol) ||
e.CurrencySymbol == to.ToString())
.ToList();
Not sure if Any can be translated by EF (it does not work on local sequences with Linq to SQL) but you can also try:
var rate = info.ExchangeRates
.Where(e => from.Any(f => e.CurrencySymbol == f.ToString()) ||
e.CurrencySymbol == to.ToString())
.ToList();