Unable to create a constant value in LINQ to EF query - c#

I am trying to retrieve data from a user class webpages_membership for verifying a token password change.
Wehn I try to run this query to get the necessary information from the database:
bool any = ttf.webpages_Membership
.Any(x => x.UserId.Equals(userid)
&& x.PasswordVerificationToken == rt
&& x.PasswordVerificationTokenExpirationDate < DateTime.Now);
if (any == true) {
}
I get this exception
Unable to create a constant value of type 'DBContext.Models.Customers'. Only primitive types or enumeration types are supported in this context.
How can I avoid that exception?

Use this, to ensure that the LINQ expression only have constant values. Do also use == instead of .Equals (I'm not sure if .Equals can be correctly processed by LINQ. Perhaps it's possible, but I'm sure that == works fine).
var now = DateTime.Now;
ttf.webpages_Membership
.Any(x => x.UserId == userid &&
x.PasswordVerificationToken == rt &&
x.PasswordVerificationTokenExpirationDate < now);
LINQ examines the expression tree of the lambda predicate, and can transfer constant values to the server. I think it doesn't treat DateTime.Now as a constant, unless you capture it in a variable as shown.

Related

How to skip conditions in "Where" clause with multiple conditions?

I"m working on a WinfForms/C# software for automotive key management that's has a SQL query thats searchs in the table like that:
DataAdapter = SetAdapter($"SELECT * FROM keylist WHERE MANUFACTOR LIKE #manufactor AND KEYTYPE LIKE #type AND SERVICETYPE LIKE #service AND USER_ID = #_user");
So in the begginig I used to use the query directly in the search function but as the project grew, it ended up leaving it without performance, because the query was called directly on the remote server. So I decided to move everything to a list in C # and do the search with lambdas functions.
This is the function with lambda expression:
public List<Key> SearchFilter(string manufactor, string type, string service)
{
return _keys.Where(key => key.Manufactor == manufactor
&& key.Type == type
&& key.ServiceType == service).ToList();
}
The problem is:
In the SQL syntax, when you leave one or more fields, it automatically ignores and checks for the other, but when I use Where <> in the LINQ for example, it ends up returning null or items that do not satisfy conditions and returning other objects.
When I leave one or two of the parameters null, it returns no value. By the way, if I use || instead of && it returns undesirable values.
Is there a way to check if the condition is null and skip to the next clause and return only the values that were passed?
if I understand correctly, I think I should make several "Where" conditions, one for each condition, that is:
return _keys.Where(key => manufactor != string.Empty ? key.Manufactor == manufactor ? key.Manufactor != string.Empty)
.Where(key => key.Type == type)
.Where(key => key.ServiceType == service)
.ToList();
You can also "exclude" or "include" filters if there is value to filter as I did with "key.Manufactor".

.NET Core 3 InvalidOperationException on OrderBy with dynamic field name

I'm migrating an existing web API from .NET Core 2 o 3 version.
After several problems, I manage to make it work, with the exception of Dynamic OrderBy by column name.
This is my code, that worked great with .net core 2:
public async Task<IEnumerable<Clientes_view>> GetClientes(int bActivos, int nRegistroInic, int nRegistros, string sOrdenar,
int nSentido, string sFiltro, int nTipo = -1, int idCliente = -1)
{
var clientes = this.context.Set<Clientes_view>()
.Where(e => e.RazonFantasia.Contains(sFiltro) || e.RazonFantasia.Contains(sFiltro)
|| e.Cuit.Contains(sFiltro) || e.Mail.StartsWith(sFiltro) || string.IsNullOrEmpty(sFiltro))
.Where(e => (e.Activo && bActivos == 1) || bActivos == -1 || (!e.Activo && bActivos == 0))
.Where(e => e.IdTipoCliente == nTipo || nTipo == -1)
.Where(e => e.IdCliente == idCliente || idCliente == -1);
if (!string.IsNullOrEmpty(sOrdenar))
{
var propertyInfo = this.context.Set<Clientes_view>().First().GetType().GetProperty(sOrdenar,
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (propertyInfo != null) if (nSentido == -1) clientes = clientes.OrderByDescending(e => propertyInfo.GetValue(e, null));
else clientes = clientes.OrderBy(e => propertyInfo.GetValue(e, null));
}
clientes = clientes.Skip(nRegistroInic).Take(nRegistros);
return await clientes.ToListAsync();
}
And the error I'm getting is the following:
System.InvalidOperationException: The LINQ expression 'DbSet
.Where(c => True)
.Where(c => c.Activo && True || False || False)
.Where(c => True)
.Where(c => True)
.OrderBy(c => __propertyInfo_3.GetValue(
obj: c,
index: null))' 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().
Any thoughts?
Thanks!
You need to actually generate the member access expression, all you've done was used reflection to get the value of some object, and provided that as the expression. That will not work, the query provider will not be able to translate that.
You need to do something like this:
if (!String.IsNullOrEmpty(sOrdenar))
{
var type = typeof(Clientes_view);
var prop = type.GetProperty(sOrdenar);
if (prop != null)
{
var param = Expression.Parameter(type);
var expr = Expression.Lambda<Func<Clientes_view, object>>(
Expression.Convert(Expression.Property(param, prop), typeof(object)),
param
);
if (nSentido == -1)
clientes = clientes.OrderByDescending(expr);
else
clientes = clientes.OrderBy(expr);
}
}
Your problem is that you are using reflection inside of order by, while probably you should use sorting string.
One of the options
Install-Package System.Linq.Dynamic
using System.Linq.Dynamic;
then you can sort
query.OrderBy("item.item_id DESC")
Other option without any library in case you dont have many sort options would be:
switch(sOrdenar){
case "Field1"
clientes = nSentido == -1 ? clientes.OrderBy(entity=> entity.Field1) : clientes.OrderByDescending(entity=> entity.Field1);
break;
case "OtherField"
clientes = nSentido == -1 ? clientes.OrderBy(entity=> entity.OtherField) : clientes.OrderByDescending(entity=> entity.OtherField);
break;
}
Personally I prefer second option better, because then I can be sure that user is able to sort only on allowed fields otherwise you can have performance issues in case you have large tables and users start sorting on wrong fields (Never trust your users :) ).
EF Core attempts to translate as much of your query to a server-side query (i.e. SQL) as possible. In versions before 3.0, any code that could not be converted was silently run on the client - however, this can cause massive and often unintuitive performance issues, so from 3.0 the decision was made that if any query code cannot be translated, an exception would immediately be thrown.
Reference: https://learn.microsoft.com/en-us/ef/core/querying/client-eval#previous-versions
The end result is that you either need to rearchitect your code to separate the parts that can and can't be run on the server, or alternatively force everything to be run on the client. The referenced document explains how to achieve the latter, but note that doing so will likely have significant performance impact.
In your case, the stuff inside the if (!string.IsNullOrEmpty(sOrdenar)) block is what is causing the problem. You should be aware that this implies that whenever that block has been executed, the paging that follows it (Skip and Take) has not been executed on the server, always the client - so if you've ever had performance problems with this method, now you know why!
It's pretty obvious that calling properties via reflection can't be automatically translated into SQL query.
The only ways it could have worked before was either that this branch was never taken, or the whole query was processed by your application instead of on the database side.
To fix this, do as the error message suggests: break the query into DB and application parts, e.g.
if (!string.IsNullOrEmpty(sOrdenar))
{
IEnumerable<Clientes_view> list = await clientes.AsAsyncEnumerable();
list = list.Where(.....); //here you may use everything you like
return list;
}
If you are searching for a way to generate the OrderBy part dynamically on the server side, take a look at this answer; apparently it's written for classic EF, but should probably work in EF Core with minor adjustments.

Cannot convert string to int32 LINQ

I have code like this:
foreach (DataRow row in tmpDatosModulos.Rows)
{
tmpBSCID += row["ModuloURL"].ToString();
tmpBSCID = tmpBSCID.Replace("../BSC/wf_BSC_Reporte.aspx?BSCID=", "");
}
bsc = _c.ConfiguracionesBalance.Where(x => x.mdEstatusRegistro && x.sEstatus.Equals("A") && x.ID == int.Parse(tmpBSCID)).ToList();
I get error when I debbug it:
LINQ to Entities does not recognize the method 'Int32
Parse(System.String)' method, and this method cannot be translated
into a store expression.
I read another questions about that, and solution explained is to parse outside LINQ expression so I do something like this:
foreach (DataRow row in tmpDatosModulos.Rows)
{
tmpBSCID += row["ModuloURL"].ToString();
tmpBSCID = tmpBSCID.Replace("../BSC/wf_BSC_Reporte.aspx?BSCID=", "");
}
tmpBSCID = int.Parse(tmpBSCID);
bsc = _c.ConfiguracionesBalance.Where(x => x.mdEstatusRegistro && x.sEstatus.Equals("A") && x.ID == tmpBSCID).ToList();
but I get
Cannot implicity convert int to string
in this line tmpBSCID = int.Parse(tmpBSCID);
It's a little confussing because I'm converting string to int and no viceversa. Regards
What kind of type is your variable "tmpBSCID"? From the code you poste seems it's a string, and in the part
tmpBSCID = int.Parse(tmpBSCID);
you are assigning a int field to a string variable. If you need to keep using tmpBSCID, after the parsing method use the "ToString()", otherwise you can create a new int variable (or var).
I would rewrite it as
var tmpBSCIDValue = int.Parse(tmpBSCID);
bsc = _c.ConfiguracionesBalance.Where(x => x.mdEstatusRegistro && x.sEstatus.Equals("A") && x.ID == tmpBSCIDValue).ToList();
You need to assign a new variable (of type integer) to the result of your parsed string, and applying the integral variable to your LINQ query, i.e.:
var myIntegerBSCID = int.Parse(tmpBSCID);
bsc = _c.ConfiguracionesBalance
.Where(x => x.mdEstatusRegistro && x.sEstatus.Equals("A") && x.ID == myIntegerBSCID)
.ToList();
Other than dynamic, C# will not allow you to change the type of an existing variable, i.e.
tmpBSCID = int.Parse(tmpBSCID);
can't work, because the LHS type would need to be Integral, whereas the original RHS type of tmpBSCID is of course string.
As an aside, consider using a StringBuilder to build up tmpBSCID rather than looping and concatenating strings.
And finally, you might also consider the case where tmpBSCID cannot be parsed as an integer. You can use int.TryParse(out var myIntegerBSCID) as an alternative - it returns false if the parse fails.
From your code (+=) I conclude what you're trying to do is: collect a list of ID values from a data table, not just one single ID. So you have to build this list and then use Contains in the LINQ query:
var idValues = tmpDatosModulos.Rows.Select(row =>
{
tmpBSCID = row["ModuloURL"].ToString()
.Replace("../BSC/wf_BSC_Reporte.aspx?BSCID=", "");
return int.Parse(tmpBSCID);
}).ToList();
bsc = _c.ConfiguracionesBalance
.Where(x => x.mdEstatusRegistro && x.sEstatus.Equals("A")
&& idValues.Contains(x.ID)).ToList();
Side note: you seem to be confident that int.Parse always succeeds. I'd prefer using int.TryParse.
Althoug LINQ does not recognize int.Parse() you can do bsc = _c.ConfiguracionesBalance.Where(x => x.mdEstatusRegistro && x.sEstatus.Equals("A") && x.ID == (int)(tmpBSCID)).ToList(); to force the conversion.
Looking at your first error, I gather that _c.ConfiguracionesBalance is an IQueryable.
IQueryables are different that IEnumerables in the fact that an IQueryable doesn't hold the code to create an Enumerator for your sequence. It holds an Expression and a Provider. The Provider knows who should perform the Expression (usually an external process like a database). The provide also knows the language this external process speaks (for example SQL). The provider knows how to translate the Expression into this language.
As soon as you want to access the first element of the sequence represented by your IQueryable, the IEnumerator for this sequence is fetched from the IQueryable. The expression is sent to the Provider who translates it into the language for the other process and orders this other process to perform the query.
This provider can't translate your own functions into SQL. In fact, although there are a lot of .NET functions that can be translated, there is a list of supported and not supported LINQ functions
In this list you can see that Int32.Parse is not supported. Therefore it was wise to parse locally.
You don't write the type of tmpBSCID, but from your statements I gather that it is a string. If you'd written the type, you'd already answered the questioin for yourself:
string tmpBSCID += row["ModuloURL"].ToString(); // from your code
tmpBSCID = Int32.Parse(tmpBSCID);
And you wonder why the compiler complains that it can't convert the int to a string?

Convert string to long type and use in a linq query within asp.net MVC

Is it possible within Linq in C#, to convert a string field in a database, to a long type - and use it in the query?
Here, tme is a unix time (long) - but the field in the database, targetdate - is a string.
I've tried:
var qbt = db.Calls
.Where(x => x.team == id && long.Parse(x.targetdate) <= tme);
However I get the message: LINQ to Entities does not recognize the method 'Int64 Parse(System.String)' method, and this method cannot be translated into a store expression.
I know you can convert before the linq query, but is there any way of using it WITHIN the linq query?
Thanks for any help,
Mark
try
var qbt = db.Calls.ToList()
.Where(x => x.team == id && long.Parse(x.targetdate) <= tme);
if you have many records you can limit them by team first and then call ToList like below
var qbt = db.Calls.Where(x => x.team == id).ToList()
.Where(i=>long.Parse(i.targetdate) <= tme);
Or You can use AsEnumerable
var qbt = db.Calls.AsEnumerable()
.Where(x => x.team == id && long.Parse(x.targetdate) <= tme);
This is to do with the way the Linq is translated into the backing query language, it might be easier to do a string comparison in this case, using tme.ToString(). If you pull the full collection down first, you could query like this but that means what it says: pulling down the full unfiltered (or at least less filtered) set.
You have to either change the database table to not store a string (you could create a computed column that converts it to a long or create a view if you cannot modify the existing table) or compare the value as string. The reason is that Entity Framework LINQ provider does not understand long.Parse and there is no method in SqlFunctions class for this purpose.
var stringTme = tme.ToString(CultureInfo.InvariantCulture);
var qbt = db.Calls
.Where(x => x.team == id && ((x.targetdate.Length < stringTme.Length)
|| (x.targetdate.Length == stringTme.Length && x.targetdate <= stringTme)));
You have to either change the database table to not store a string or compare the value as string. The reason is that Entity Framework LINQ provider does not understand long.Parse and there is no method in SqlFunctions class for this purpose.please use long.Parse()

Linq To Sql - Changing Sort Order At Run-Time with well known static typing

This is not another question about 'How Can I Sort Dynamically (based on an arbitrary user provided field)?'
The question is -- how can I change sort order when I know the potential sorts in advance? (And thus avoid reflection / custom Expression building typically associated with truly dynamic sorting.)
Take for instance this subquery (shortened for this example) of a larger query:
(from solutionIds in context.csExtendedQAIncident_Docs
where solutionIds.tiRecordStatus == 1
&& (from solutionProductAssocation in context.csProductDocs
where solutionProductAssocation.iSiteId == Settings.Current.WebUtility().Onyx.SiteId
&& (from allowedProduct in context.KB_User_Allowed_Products
where allowedProduct.UserId == userId
select allowedProduct.ModelCode
).Contains(solutionProductAssocation.chModelCd)
select solutionProductAssocation.chIdNo).Distinct().Contains(solutionIds.chIdNo)
).OrderByDescending(s => s.dtUpdateDate)
.Select(s => s.chIdNo)
.Take(count ?? Settings.Current.WCFServices().Output.HomePage.MaxRows)
The OrderByDescending portion works as I would expect.
Now -- I want to factor that out like the following:
Expression<Func<csExtendedQAIncident_Doc, IComparable>> ordering = (s) => s.dtUpdateDate;
if (viewType == HomepageViewType.MostViewed)
ordering = (s) => s.vchUserField8;
else if (viewType == HomepageViewType.MostEffective)
ordering = (s) => s.vchUserField4;
and then use:
OrderByDescending(ordering)
This does compile, but blows up at run-time.
Unsupported overload used for query operator 'OrderByDescending'.
This of course comes from deep in the bowels of System.Data.Linq.SqlClient.QueryConverter -- in particular VisitSequenceOperatorCall. Reflectoring that code reveals that the following conditions must be met for OrderByDescending to properly evaluate. 'mc' is the MethodCallExpression passed into the method.
if (((mc.Arguments.Count != 2) || !this.IsLambda(mc.Arguments[1]))
|| (this.GetLambda(mc.Arguments[1]).Parameters.Count != 1))
{
break;
}
So essentially that MethodCallExpression has to have 2 arguments, the second of which has to be a Expressions.LambdaExpression with a single parameter (presumably the sort field). If that code breaks out, the exception that I got is thrown.
So clearly I have not constructed the expression correctly. Without digging in any further here, does anyone know how to correctly construct the sorting Expression?
I think the unsupported part of your code is the use of IComparable as a general return type for your ordering expression. If you consider the plain use of OrderByDescending, the compiler-generated lambda expression has a return type of the type of the property that you're ordering by: for example, an Expression<Func<csExtendedQAIncident_doc, string>> for a string property.
One possible answer, although I'm not sure whether it works in your case, is to first create an unordered query:
IQueryable<Foo> unorderedQuery = from f in db.Foo select f;
And then, depending on the sort:
IOrderedQueryable<Foo> orderedQuery = unorderedQuery
.OrderBy(f => f.DefaultSortKey);
if (sortBy == SortByName)
orderedQuery = unorderedQuery.OrderBy(f => f.Name);
else if (sortBy == SortByDate)
orderedQuery = unorderedQuery.OrderBy(f => f.Date);
// etc.
I believe that this will not work unless the two possible fields have the identical type.
Then the linq to sql will (if possible) correctly create the relevant sql.
so for example if both of those fields were DateTimes:
Expression<Func<csExtendedQAIncident_Doc, DateTime>> ordering =
s => s.dtUpdateDate;
if (viewType == HomepageViewType.MostViewed)
ordering = (s) => s.vchUserField8; // a DateTime
else if (viewType == HomepageViewType.MostEffective)
ordering = (s) => s.vchUserField4; // another DateTime
Then this would work just fine (I tested it and it worked)
You could instead do a per type order by either a series of nested switch/if statements of by constructing a dictionary or similar structure to get them.
For the linq to sql to work without explicit dynamic creation of the query I believe it must know the precise type of the query as opposed to just it being an IComparable...

Categories

Resources