Check value against multiple enum values in IQueryable - entity-framework

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

Related

Entity Framework 6 - Linq - Could not be translated. Either rewrite the query in a form that can be translated

I need to write a method that compares the existing entities in a table to a list containing a complete updated set of what the table should look like and update according to the differences:
public async Task Update(List<Person> updatedPersons)
{
IQueryable<Person> existingMinusUpdated = _context.Persons.Where(
existing
=> !updatedPersons.Any(p => p.FirstName == existingPerson.FirstName
&& p.LastName== existingPerson.LastName && p.phone == existingPerson.phone)
);
IEnumerable<Person> updatedMinusExisting = updatedAgreements.Where(
updated
=> !_context.Persons.Any(existingPerson => updated.FirstName == existingPerson.FirstName
&& updated.LastName== existingPerson.LastName && updated.phone == existingPerson.phone)
);
_context.Persons.RemoveRange(existingMinusUpdated.AsEnumerable());
_context.Persons.AddRange(updatedMinusExisting);
await _context.SaveChangesAsync();
}
This code generates the following error:
System.InvalidOperationException: The LINQ expression 'p => p.FirstName==
EntityShaperExpression:
xxx.Model.Person
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False .FirstName && p.LastName == EntityShaperExpression:
xxx.Model.Person
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False .LastName && p.Phone== EntityShaperExpression:
transportdata.Model.Person
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False p.Phone .......' 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'.
Blockquote
How can I rewrite the query?
(The updatedPersons list doesn't contain the Ids, that's why I have to check if all the properties are the same instead)

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

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

Entity Framework calling MAX on null on Records

When calling Max() on an IQueryable and there are zero records I get the following exception.
The cast to value type 'Int32' failed because the materialized value is null.
Either the result type's generic parameter or the query must use a nullable type.
var version = ctx.Entries
.Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
.Max(e => e.Version);
Now I understand why this happens my question is how is the best way to do this if the table can be empty. The code below works and solves this problem, but its very ugly is there no MaxOrDefault() concept?
int? version = ctx.Entries
.Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
.Select(e => (int?)e.Version)
.Max();
Yes, casting to Nullable of T is the recommended way to deal with the problem in LINQ to Entities queries. Having a MaxOrDefault() method that has the right signature sounds like an interesting idea, but you would simply need an additional version for each method that presents this issue, which wouldn't scale very well.
This is one of many mismatches between how things work in the CLR and how they actually work on a database server. The Max() method’s signature has been defined this way because the result type is expected to be exactly the same as the input type on the CLR. But on a database server the result can be null. For that reason, you need to cast the input (although depending on how you write your query it might be enough to cast the output) to a Nullable of T.
Here is a solution that looks slightly simpler than what you have above:
var version = ctx.Entries
.Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
.Max(e =>(int?)e.Version);
Try this to create a default for your max.
int version = ctx.Entries
.Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
.Max(e =>(int?)e.Version) ?? 0;
You could write a simple extension method like this, it returns the default value of type T if no records exist and is then apply Max to that or the query if records exist.
public static T MaxOrEmpty<T>(this IQueryable<T> query)
{
return query.DefaultIfEmpty().Max();
}
and you could use it like this
maxId = context.Competition.Select(x=>x.CompetitionId).MaxOrEmpty();
I couldnt take no for an answer :) I have tested the below and it works, I havent checked the SQL generated yet so be careful, I will update this once I have tested more.
var test = ctx.Entries
.Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
.MaxOrDefault(x => x.Version);
public static TResult? MaxOrDefault<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
where TResult : struct
{
return source
.Select(selector)
.Cast<TResult?>()
.Max();
}
Try this:
IEnumerable<AlertsResultset> alerts = null;
alerts = (from POA in SDSEntities.Context.SDS_PRODUCT_ORDER_ALERT
join A in SDSEntities.Context.SDS_ALERT on POA.ALERT_ID equals A.ALERT_ID
orderby POA.DATE_ADDED descending
select new AlertsResultset
{
ID = POA.PRODUCT_ORDER_ALERT_ID == null ? 0:POA.PRODUCT_ORDER_ALERT_ID ,
ITEM_ID = POA.ORDER_ID.HasValue ? POA.ORDER_ID.Value : POA.PRODUCT_ID.Value,
Date = POA.DATE_ADDED.Value,
orderType = SDSEntities.Context.SDS_ORDER.Where(o => o.ORDER_ID == POA.ORDER_ID.Value).FirstOrDefault().ORDER_TYPE,
TransactionNumber = POA.PRODUCT_ID.HasValue ? (SDSEntities.Context.SDS_PRODUCT.Where(p => p.PRODUCT_ID == POA.PRODUCT_ID.Value).FirstOrDefault().TRANSACTION_NUMBER) : (SDSEntities.Context.SDS_ORDER.Where(o => o.ORDER_ID == POA.ORDER_ID.Value).FirstOrDefault().TRANSACTION_NUMBER),
Publisher = POA.PRODUCT_ID.HasValue ?
(
SDSEntities.Context.SDS_PRODUCT.Where(p => p.PRODUCT_ID == POA.PRODUCT_ID.Value).FirstOrDefault().PRODUCT_TYPE_NUMBER == "ISSUE" ? (from prod in SDSEntities.Context.SDS_PRODUCT
join ji in SDSEntities.Context.SDS_JOURNAL_ISSUE on prod.PRODUCT_ID equals ji.PRODUCT_ID
join j in SDSEntities.Context.SDS_JOURNAL on ji.JOURNAL_ID equals j.JOURNAL_ID
where prod.PRODUCT_ID == POA.PRODUCT_ID
select new { j.PUBLISHER_NAME }).FirstOrDefault().PUBLISHER_NAME : (from prod in SDSEntities.Context.SDS_PRODUCT
join bi in SDSEntities.Context.SDS_BOOK_INSTANCE on prod.PRODUCT_ID equals bi.PRODUCT_ID
join b in SDSEntities.Context.SDS_BOOK on bi.BOOK_ID equals b.BOOK_ID
where prod.PRODUCT_ID == POA.PRODUCT_ID
select new { b.PUBLISHER_NAME }).FirstOrDefault().PUBLISHER_NAME
)
: (SDSEntities.Context.SDS_ORDER.Where(o => o.ORDER_ID == POA.ORDER_ID.Value).FirstOrDefault().PUBLISHER_NAME),
Alert = A.ALERT_NAME,
AlertType = A.ALERT_TYPE,
IsFlagged = POA.IS_FLAGGED.Value,
Status = POA.ALERT_STATUS
});
how about
var version = ctx.Entries
.Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
.Max(e => (int?)e.Version);
less ugly, more elegant
I want to suggest a merge from the existing answers:
#divega answer works great and the sql output is fine but because of 'don't repeat yourself'
an extension will be a better way like
#Code Uniquely showed. But this solution can output more complex sql as you needed.
But you can use the following extension to bring both together:
public static int MaxOrZero<TSource>(this IQueryable<TSource> source,
Expression<Func<TSource, int>> selector)
{
var converted = Expression.Convert(selector.Body, typeof(int?));
var typed = Expression.Lambda<Func<TSource, int?>>(converted, selector.Parameters);
return source.Max(typed) ?? 0;
}
You can use:
FromSqlRaw("Select ifnull(max(columnname),0) as Value from tableName");

Using an existing IQueryable to create a new dynamic IQueryable

I have a query as follows:
var query = from x in context.Employees
where (x.Salary > 0 && x.DeptId == 5) || x.DeptId == 2
order by x.Surname
select x;
The above is the original query and returns let's say 1000 employee entities.
I would now like to use the first query to deconstruct it and recreate a new query that would look like this:
var query = from x in context.Employees
where ((x.Salary > 0 && x.DeptId == 5) || x.DeptId == 2) && (x,i) i % 10 == 0
order by x.Surname
select x.Surname;
This query would return 100 surnames.
The syntax is probably incorrect, but what I need to do is attach an additional where clause and modify the select to a single field.
I've been looking into the ExpressionVisitor but I'm not entirely sure how to create a new query based on an existing query.
Any guidance would be appreciated. Thanks you.
In an expression visitor you would override the method call. Check if the method is Queryable.Where, and if so, the methods second parameter is a quoted expression of type lambda expression. Fish it out and you can screw with it.
static void Main()
{
IQueryable<int> queryable = new List<int>(Enumerable.Range(0, 10)).AsQueryable();
IQueryable<string> queryable2 = queryable
.Where(integer => integer % 2 == 0)
.OrderBy(x => x)
.Select(x => x.ToString());
var expression = Rewrite(queryable2.Expression);
}
private static Expression Rewrite(Expression expression)
{
var visitor = new AddToWhere();
return visitor.Visit(expression);
}
class AddToWhere : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
ParameterExpression parameter;
LambdaExpression lambdaExpression;
if (node.Method.DeclaringType != typeof(Queryable) ||
node.Method.Name != "Where" ||
(lambdaExpression = ((UnaryExpression)node.Arguments[1]).Operand as LambdaExpression).Parameters.Count != 1 ||
(parameter = lambdaExpression.Parameters[0]).Type != typeof(int))
{
return base.VisitMethodCall(node);
}
return Expression.Call(
node.Object,
node.Method,
this.Visit(node.Arguments[0]),
Expression.Quote(
Expression.Lambda(
lambdaExpression.Type,
Expression.AndAlso(
lambdaExpression.Body,
Expression.Equal(
Expression.Modulo(
parameter,
Expression.Constant(
4
)
),
Expression.Constant(
0
)
)
),
lambdaExpression.Parameters
)
)
);
}
}
}