Using an existing IQueryable to create a new dynamic IQueryable - entity-framework

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

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

Accent insensitive in filter backend to datatable in ASP.NET MVC 5

I made a method on the back-end side to handle the filter of my datatable.
On the other hand, this one does not manage the accents of the French language, so if I have "école" and I write "ecole" it cannot find it.
I found this method on another question on stackoverflow
public static String RemoveDiacritics(this String s)
{
String normalizedString = s.Normalize(NormalizationForm.FormD);
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < normalizedString.Length; i++)
{
Char c = normalizedString[i];
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
{
stringBuilder.Append(c);
}
}
return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
}
and it works, but only for part of my problem. It works on the letter or the word that is written in the search, but I am not able to apply it in my linq query, so with the .RemoveDiacritics() method my "école" becomes "ecole", but I don't am not able to apply it in the column of my table and it always looks for "école".
Here the code for the search:
if (search != null)
{
int n;
search = search.Trim();
var isNumeric = int.TryParse(search, out n);
if (isNumeric)
{
IdFilter = n;
query = query.Where(x => x.UsagerId == IdFilter || x.Niveau == IdFilter);
}
else if (search != "")
{
// this line work
textFilter = search.ToLower().RemoveDiacritics();
// This is the full line, but absolutely takes the accents out to get the right information out
// query = query.Where(x => x.Nom.ToLower().Contains(textFilter) || x.Prenom.ToLower().Contains(textFilter) || x.Username.ToLower().Contains(textFilter) || x.Email.ToLower().Contains(textFilter) || x.EtabNom.ToLower().Contains(textFilter) || x.ActifStatut.ToLower().Contains(textFilter));
// This is the line that will replace the line above, which I try and it doesn't work ( this part: x.Prenom.ToLower().RemoveDiacritics())
query = query.Where(x => x.Prenom.ToLower().RemoveDiacritics().Contains(textFilter));
}
}
This is the basic query:
IQueryable<ListeUsagers> query = (from u in db.USAGERs
join e in db.ETABLISSEMENTs on u.USAGER_INST equals e.ETAB_CODE
where u.USAGER_INST == instId && u.USAGER_NIVEAU > 3 && u.USAGER_NIVEAU < 5 //&& u.USAGER_ACTIF == 1
select new ListeUsagers()
{
UsagerId = u.USAGER_id,
Nom = u.USAGER_NOM,
Prenom = u.USAGER_PRENOM,
EtabCode = e.ETAB_CODE,
EtabNom = e.ETAB_NOM_COURT,
EtabType = e.ETAB_TYPE,
Niveau = u.USAGER_NIVEAU,
Username = u.USAGER_USERNAME,
UserPassword = u.USAGER_MP,
DateCreation = u.USAGER_DATE_INSC,
Sexe = u.USAGER_SEXE,
Lang = u.USAGER_LANGUE,
Telephone = u.USAGER_TELEPHONE,
Email = u.USAGER_EMAIL,
FonctionTravail = u.USAGER_FONCTION,
LieuTravail = u.USAGER_LIEUTRAVAIL,
Note = u.USAGER_NOTE,
Actif = u.USAGER_ACTIF,
ActifStatut = u.USAGER_ACTIF == 0 ? "Inactif" : "Actif"
});
This is the error:
LINQ to Entities does not recognize the method 'System.String RemoveDiacritics(System.String)' method, and this method cannot be translated into a store expression.
There's built-in functionality to do this in entityframework: https://learn.microsoft.com/en-us/ef/core/miscellaneous/collations-and-case-sensitivity if you're using EF 5+
You'll want an accent insensitive collation ("AI", not "AS" in the examples on that page.)

Combining Linq expressions using EFCore 3.0

I have a function doing a complicated Where query on my db context and then applies another transformation passed to it:
static IQueryable<T> Query<T>(Func<IQueryable<ServicesData>, IQueryable<T>> f, string path1 = null, string path2 = null, string path3 = null, string path4 = null, string path5 = null) {
try {
using (var dbc = new MyDbContext() ) {
var res = dbc.ServicesData
.Where(sd =>
(path1 == null || (path1.Contains("%") || path1.Contains("_") ? EF.Functions.Like(sd.Path1, path1) : sd.Path1 == path1))
&& (path2 == null || (path2.Contains("%") || path2.Contains("_") ? EF.Functions.Like(sd.Path2, path2) : sd.Path2 == path2))
&& (path3 == null || (path3.Contains("%") || path3.Contains("_") ? EF.Functions.Like(sd.Path3, path3) : sd.Path3 == path3))
&& (path4 == null || (path4.Contains("%") || path4.Contains("_") ? EF.Functions.Like(sd.Path4, path4) : sd.Path4 == path4))
&& (path5 == null || (path5.Contains("%") || path5.Contains("_") ? EF.Functions.Like(sd.Path5, path5) : sd.Path5 == path5)));
return f(res.ToList().AsQueryable());
//return f(res).ToList().AsQueryable();
}
} catch (Exception ex_) {
return VList<T>.Empty.AsQueryable();
}
}
This is used ie like this:
IQueryable<int> Int1InLastHour(IQueryable<ServicesData> input) {
var lastHour = DateTimeOffset.Now.AddHours(-1).ToUnixTimeMilliseconds();
return input
.Where(v => (v.Time <= lastHour) && (v.Int1 is object))
.Select(v => v.Int1.Value);
}
var lastHourProcessTime = Query(Int1InLastHour, "Publisher", "%", "ItemProcessTime").Sum();
This works, however since I call res.ToList() before calling f the linq in f is done in memory and not on the DB SQL
If I try to replace f(res.ToList().AsQueryable()) with f(res).ToList().AsQueryable() I get an exception:
{"Processing of the LINQ expression '[EntityShaperExpression][ServicesData]' by 'RelationalProjectionBindingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information."}
Is there any way for me to solve this ? can I somehow pass the query (Func<IQueryable<ServicesData>, IQueryable<T>>) and then combine it to the query in Query before excecuting it on the dbc ?
A few issues. You can split the querying to break down your results, but the scope of your DbContext needs to be at the outermost point of the chain, not inside the inner-most:
This here:
static IQueryable<T> Query<T>(Func<IQueryable<ServicesData>, IQueryable<T>> f, string path1 = null, string path2 = null, string path3 = null, string path4 = null, string path5 = null) {
try {
using (var dbc = new MyDbContext() ) { // DbContext should not be scoped here...
var res = dbc.ServicesData
As the simplest re-factor:
static IQueryable<T> Query<T>(MyDbContext dbc, Func<IQueryable<ServicesData>, IQueryable<T>> f, string path1 = null, string path2 = null, string path3 = null, string path4 = null, string path5 = null) {
try
{
var res = dbc.ServicesData.AsQueryable();
if(path1 != null)
if(path1.Contains("%") || path1.Contains("_"))
res = res.Where(EF.Functions.Like(sd.Path1, path1));
else
res = res.Where(sd.Path1 == path1);
// Repeat for Path 2 - 5 ....
return f(res);
}
catch (Exception ex_)
{
return VList<T>.Empty.AsQueryable();
}
}
Firstly, we pass in the DbContext. If the context is scoped here, the list must be materialized before being returned. The goal is to allow callers to further reduce the expression before executing the list. This means the DbContext needs to be scoped outside of this initial generation and passed in. With IoC containers managing lifetime scope you can bypass this if the DbContext is injected and scoped to a Request or common lifetime scope.
The next improvement suggestion is to move the conditional checks for the parameters out of the Linq and into regular conditions so that the Like / Equals check will only be added if the condition was provided. This will result in simpler, faster SQL being run on the server.
So the end result would look something like:
using (var dbContext = new MyDbContext())
{
var lastHourProcessTime = Query(dbContext, Int1InLastHour, "Publisher", "%", "ItemProcessTime").Sum();
}
I sort of get where you're trying to go here, but abstracting expressions from EF is bound to lead to confusing code and still prone to limitations and bugs. IMO keeping it simpler generally leads to less issues, but give this a go and see if it gets you closer.

Valid Postgres query alway return undefined

I am using sails to build an API, one of my function needs to get columns from my PGSQL database. My SQL request is working when I test it in PGadmin, it looks like this :
SELECT "user".id, "user".picture, "user".first_name, "trophe".trophe FROM "user" INNER JOIN "trophe" ON "trophe".user = "user".id WHERE "trophe".foot = 1
When I transcript this request in my sails API, it looks like this :
getHommeAndChevre: function (req, res) {
console.log(req.param("id"));
Trophe.query("SELECT 'user'.id, 'user'.picture, 'user'.first_name, 'trophe'.trophe FROM 'user' INNER JOIN 'trophe' ON 'trophe'.user = 'user'.id WHERE 'trophe'.foot ="+req.param('id'), function(err,trophes){
if(!trophes) {return res.status(400).end();}
if(trophes){
_each(trophes, function(trophe){
if (trophe.trophe == 0) {var chevre = trophe};
if (trophe.trophe == 1) { var homme = trophe};
})
return res.status(200).json({chevre: chevre, homme: homme})
}
});
}
However whatever I do the Trophe.query always returned undefined. What could be wrong here ?
Your return is outside the callback , so you return the value before the end of the query.
_each(trophes, function(trophe, index){
if (trophe.trophe == 0) {var chevre = trophe};
if (trophe.trophe == 1) { var homme = trophe};
if (index == trophes.length - 1) //End of the loop
return res.status(200).json({chevre: chevre, homme: homme});
})
Use double quotes for identifiers
Trophe.query('SELECT "user".id, "user".picture, "user".first_name, ...

How to write this Query in LINQ

I have one LINQ query with foreach loop. Everything is fine. But it takes more time to get the value. So anybody suggest me how can i do this in LINQ query itself.
Code
NormValue = "";
c = 0;
var NormValuelist = db.BCont.Where(x => x.BId == BId && x.TNo == Tag).ToList();
foreach (var item in NormValuelist)
{
if (c == 0)
NormValue = item.NormValue;
else
NormValue += " " + item.NormValue;
c = 1;
}
Thanks
You can rewrite this query with string.Join to avoid creating multiple string objects in a loop, like this:
string NormValue = string.Join(" ", db.BCont.Where(x => x.BId == BId && x.TNo == Tag));
The number of round-trips to DB will remain the same, but the creation of List<string> and the partially concatenated string objects will be optimized out.
In addition to using String.Join, you could also use Enumerable.Aggregate:
var NormValueList =
db.BCont.Where(x => x.Bid == BId && x.TNo == Tag)
.Select(x => x.NormValue)
.Aggregate((s, x) => s + " " + x);
If you are having large items in "NormValuelist" then it would be better to use StringBuilder instead of string(NormValue)