How to pass dynamically-generated search criteria to LINQ - c#

I have some trouble with LINQ. In my program I generate a SQL search query like
select * from emp "where empId=1 and empname='abc'"
(where the quoted text is generated in my code). I can pass the generated "where empId..." string text to the SQL query.
I'd like to do the same thing in LINQ - I want to pass this string as the search criteria i.e. something like
var employee=from a in Employee.AsEnumerable()
"where empId=1 and empname='abc'"
select a;
Is this possible? Thanks in advance.

You can take the base query (in your case Employee.AsEnumerable()) and use the logic you use to generate the string to compose a new query. For example:
if(/*your logic for generating the string "where empId=1" here*/)
{
query = query.Where(a.empId == 1);
}
if(/*your logic for generating the string "empname='abc'" here*/)
{
query = query.Where(a.empname == "abc");
}
The resulting query object will have all the operators composed. However as others have said this is not trivial in the general case. It is not trivial with SQL strings either. If all you need to generate are several filters it will work but if you need complex expressions it will be a problem.

101 LINQ Samples

It's pretty hard, unless you intend to employ:
Dynamic code compilation, or
You are willing to create a (very complicated) parser to analyze the query and call the respective linq extension methods
I personally have no experience in the latter. As for the former, it is a bit tricky and can go nastily wrong if you don't do proper caching and security checks. Executable code injection is very dangerous.
I think you had better use different methods to filter content using methods like Where() if the number of queries can be predetermined or return to SQL if not. Usually you don't need to do this unless the query is manually entered by the user.

Related

Equals() is insensitive to letter cases in LINQ

I have recently been learning LINQ queries to the database. I have encountered quite a weird problem. In my query I am trying to compare 2 strings with the Equal() function. I use the ToUpper() function to have one string in upper case and compare it to the same one but in lower case. The Equals() function is sensitive to the cases of letters, which I checked outside of the query, but the Equals() in the query returns the "true" value. It is really a stupid error, but I really checked if it should work like that and if I didn't make a mistake. I write in C#, work in Visual Studio 2022 and connect to the SQLServer database through the Entity Framework. I am going to put my code below:
var wynik = from pracownik in Context.Pracownik
let P = pracownik
let stan = P.stanowisko.ToUpper()
where stan.Equals("informatyk")
select new
{
id_pracownika = P.id_pracownika,
nr_departamentu = P.nr_departamentu
};
foreach (var prac in wynik)
{
Console.WriteLine(prac.id_pracownika + " " + prac.nr_departamentu);
}
Basically the Equals() function returns true to "informatyk"=="INFORMATYK", but only in the LINQ query. Does someone know what the problem is?
LINQ to Entities doesn't perform the actual comparison. Instead, it translate your statements to SQL (or whichever language your database uses) and lets the database worry it.
Depending on which database that you're using, that SQL will look similar to this:
SELECT id_pracowinka, nr_departamentu
FROM Pracownik
WHERE UPPER(stanow) = "informatyk"
By default, SQL Server use case-insensitive collation. (Many other databases such as Oracle and PostgreSQL are case-sensitive by default.)
This can be modified on the tables itself and is probably going to be your easiest way to handle this.
If instead, you'd like LINQ to Entities to render the SQL differently, you'll want to brush up on Expression Trees.
https://learn.microsoft.com/en-us/dotnet/csharp/expression-trees
SQL server is by default case insensitive. LINQ converts your C# expression tree to a SQL query with Equals mapped to ordinary equality checks in SQL Server. As those checks are case insensitive, you get the behavior you see here.

How to compare a list of integers in EF Core Interpolated Query

I am trying to convert my sql queries being run with EF Core's .FromSqlRaw() method into queries to be run with .FromSqlInterpolated() so they are less vulnerable to SQL injection attacks. I've gotten almost everything converted and working fine, but the one thing that is stumping me is how to filter by a list of integers in an or configuration on a single field.
The database is postgres, and the quotes are because I used code first EF Core which means that all of the tables are capitalized like the class. ProjectTypeId is an integer column in my table, and projectTypes is a List<int> type variable.
The code in my where clause I'm trying to replace is:
WHERE ""PartGroups"".""ProjectTypeId"" IN({string.Join(",", projectTypes)})
The closest I've been able to get to getting it working is with this:
""PartGroups"".""ProjectTypeId""::text IN({string.Join(",", projectType)})
or
""PartGroups"".""ProjectTypeId""::text LIKE ANY(ARRAY[{string.Join(",", projectTypes)}])
These will work when there is only one value in projectTypes, but any more than that and it fails. I don't know how to view the resulting query + parameter set, just the query, so I'm not sure what's it's doing to the parameter, so I've been struggling to figure out something that works. Also, the query has ~80 parameters total, so manually setting each one with a raw query isn't really feasible.
One way you could approach it, perhaps, is to leverage EF's ability to compose over the top of a raw
context.SomeTable
.FromSqlInterpolated($"SeLeCt t.* FrOm ...")
.Where(st => idList.Contains(st.id))
EF will put your SQL in as a sub query and write the IN for you in the outer. The DB query optimizer will (probably) then push the IN into the sub query if it can..
SELECT ...
FROM
(
SeLeCt t.* FrOm ...
) x
WHERE x.ID IN (.., ..)
If you want to have a look at the SQL EF made and you're on EF5+ the easiest thing to do is do everything apart from ToList/ToArray, and capture the queryable:
var q = context.Thing.Where(...)
Then pause in the debugger and look at the DebugView property of the q. You'll get the full SQL text and you can slot it into SSMS and inspect the execution plan, check it runs the same as a single level (non hierarchical) query..
The other thing you can do, is create your query in a FormattableString yourself. So long as FromSqlInterpolated receives a FormattableString it will pull it apart and parameterize
var args = new object[] { your, single, arguments, here}.Concat(yourListOfThingsHere.Cast<object>()).ToArray();
var fs = FormattableStringFactory.Create(#"SELECT
some,
columns/{0},
here
FROM
table t
WHERE
someColumn = {1} AND
otherColumn BETWEEN {2} and {3} AND
columnToBeINned IN({" + string.Join("},{", Enumerable.Range(4, yourListOfThingsHere.Count)) + #"})
GROUP BY some, columns/{0}", args);
var qq = context.Table.FromSqlInterpolated(fs).Where(m => ...);
It's, of course, possible to write a helper to do this for you...
So I found a way to actually see how EF Core is interpreting the parameters for the interpolated query and was able to play around with it to get what I wanted out of it. So just in case someone wants to or needs to stick to a pure interpolated query, you can use the following pattern for your needs:
WHERE MyTable.MyIntColumn::text LIKE ANY(Array[{string.Join(",", myIntList).Split(",", StringSplitOptions.None)}])
This also works with text values (just no need to cast to text) and you can use wildcards like this:
WHERE MyTable.MyStringColumn ILIKE ANY(ARRAY[{("%" + string.Join("%,%", myStringList) + "%").Split(",", StringSplitOptions.None)}])

Linq Check if a string contains any query from a list

I have a list of strings that are search Queries.
I want to see if a string from the database contains anyone of those terms in the Query. I'd like to do this on one line of code, that doesn't make multiple calls to the database. This should work but I want it to be more optimized.
var queries = searchQuery.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries).Distinct();
var query = context.ReadContext.Divisions.AsQueryable();
queries.ForEach(q => {
query = query.Where(d => (d.Company.CompanyCode + "-" + d.Code).Contains(q));
});
Is there a function that can do this better or a more optimal way of writing that?
There are two issues with your proposed solution:
Most LINQ to SQL providers don't understand string.Contains("xyz") so the provider will either throw an exception or fetch all the data to your machine. The right thing to do is to use SqlMethods.Like as explained in Using contains() in LINQ to SQL
Also, the code you show will check whether the division contains all of the specified strings.
To implement the 'any' behavior you need to construct a custom expression, which will not be possible using plain C#. You would need to look at the System.Linq.Expressions namespace: https://msdn.microsoft.com/en-us/library/system.linq.expressions(v=vs.110).aspx
It is possible, but quite involved.

Use a NaturalSortComparer in a LINQ Where Clause

Say I have a table Table1 with a string field [ProductString] with values:
Alpha, alphanumeric or numeric: eg ABC, B4, U2, C 5, 100, U1, U5, U6, U11
I want to be able to take a where clause like "ProductString >= U5", and pass this to a LINQ statement as a string so it evaluates
Table1.Where(t=> t.ProductString >= 'U5');
Normally this would return results U5 and U6.
However, this I want to be able to use a NaturalSortComparer somehow so that the results returned are U5, U6 and U11.
I know how to use the comparer in an OrderBy, by I wanted to be able to use it at the Where stage.
Using natural sort comparer:
var comparer = new NaturalComparer();
Table1.Where(t=>
comparer.Compare(t.ProductString, "U5") >= 0);
Presuming all your product strings is on the format U%number% then why not abuse that fact?
Table1.Where(t=> int.Parse(t.ProductString.Replace("U","")) >= 5);
If you're using LINQ to Entities I'm not certain this will compile to a store expression (i.e that SQL knows what to do with this - I guess it should).
I'm a little confused, given the accepted answer, about whether this question relates to LINQ to Entities or not. The accepted answer doesn't appear to be a solution that would work in the LINQ to Entities context, but the comments on the question by the OP seem to confirm that this is being executed in the database context. Anyway, this answer is specifically targeted toward LINQ to Entities.
I think doing this in SQL Server would be hard, but not impossible. The problem is that .NET knows what NaturalSortComparer is, but SQL Server (where you want the query to ultimately take place) has no such concept. The best idea I can think of would consist of 2 parts:
Create a UDF (User Defined Function) in SQL server that will give a product that is orderable via natural sort: CREATE FUNCTION Naturalize(#val as nvarchar(max)) RETURNS nvarchar(1000). There's a pretty cool answer here that creates a UDF wrapper around a CLR function to accomplish just that.
Next create a function mapping for your DbContext that maps the UDF above to a function that can be called inside an EF query against the DbContext. Something like this:
[DbFunction("MyContext", "Naturalize")]
public static string Naturalize(this string value)
{
throw new NotSupportedException("This function can only be invoked from LINQ to Entities.");
}
Once you've got these two pieces in place, you can readily use this new function inside an entity query to compare strings using the Naturalized value in the comparison:
Table1.Where(t=> t.ProductString.Naturalize() >= "U5".Naturalize());
Bear in mind that the UDF will be executed against every row contained in the query, which is the whole table in the above example. You'll want to make sure to pare down your query to something manageable before applying the function as a sub-query. Or you may want to try applying some type of UDF-based index on the table in question.
If you are going to be doing searches like this a lot, then what will be the best thing to do is add two new fields to your table, [ProductCode] & [ProductNumber] which separate the two parts of the [ProductString].
Then you comparison becomes:
Table1.Where(t=> t.ProductCode == "U" && t.ProductNumer > 5);

Advanced Linq sorting C#

I have an IQueryable that has a list of pages.
I want to do: Pages.OrderByDescending(o => CalculateSort(o.page));
the method calculate sort is similar to that here is a plain english version:
public int calculatesort(page p)
{
int rating = (from r in db.rating select r). sum();
int comments = //query database for comments;
float timedecayfactor = math.exp(-page.totalhoursago);
return sortscore = (rating +comments)* timedecayfactor;
}
when I run a code similar to the one above an error is thrown that the mothode calculatesort cannot be converted to sql.
How can I do a conver the function above to be understood by sql so that I can use it to sort the pages?
Is this not a good approach for large data? Is there another method used to sort sets of results other than dynamically at the database?
I havent slept for days trying to fix this one :(
your code is nowhere near compiling so I'm guessing a lot here but I hope this gives an idea none the less.
As several have posted you need to give Linq-2-Sql an expression tree. Using query syntax that's what happens (by compiler magic)
from p in pages
let rating = (from r in db.rating
where r.PageId == p.PageId
select r.Value).Sum()
let comments = (from c in db.Comments
where c.PageId == p.PageId
select 1).Count()
let timedecayfactor = Math.Exp(-(p.totalhoursago))
orderby (rating + comments)*timedecayfactor descending
select p;
I haven't actually tried this against a database, there's simply too many unknown based on your code, so there might still be stuff that can't be translated.
The error occurs because LINQ cannot convert custom code/methods into SQL. It can convert only Expression<Func<>> objects into SQL.
In your case, you have a complex logic to do while sorting, so it might make sense to do it using a Stored Procedure, if you want to do it in the DB Layer.
Or load all the objects into main memory, and run the calculate sort method on the objects in memory
EDIT :
I don't have the code, so Describing in english is the best I can do :
Have table with structure capable of temporarily storing all the current users data.
Have a calculated field in the Pages table that holds the value calculated from all the non-user specific fields
Write a stored procedure that uses values from these two sources (temp table and calc field) to actually do the sort.
Delete the temp table as the last part in the stored proc
You can read about stored procs here and here
var comments = db.comments.Where(...);
Pages.OrderByDescending(p=>(db.rating.Sum(r=>r.rate) + comments.Count()) * Math.Exp(-p.totalhoursago))
Linq is expecting Calculatesort to return a "queryable" expression in order to generate its own SQL.
In can embed your 'calculatesort' method in this lambda expression. (I replaced your variables with constants in order to compile in my environment)
public static void ComplexSort(IQueryable<string> Pages)
{
Pages.OrderByDescending(p =>
{
int rating = 99;//(from r in db.rating select r). sum();
int comments = 33;//query database for comments;
double timedecayfactor = Math.Exp(88);
return (rating + comments) * timedecayfactor;
});
}
Also, you can even try to run that in parallel (since .net 4.0) replacing the first line with
Pages.AsParallel().OrderByDescending(p =>
Yes, counting previous answers: the LINQ to SQL doesn't know how to translate CalculateSort method. You should convert LINQ to SQL to ordinary LINQ to Object before using custom method.
Try to use this in the way you call the CalculateSort by adding AsEnumerable:
Pages.AsEnumerable().OrderByDescending(o => CalculateSort(o.page));
Then you're fine to use the OrderByDescending extension method.
UPDATE:
LINQ to SQL will always translate the query in the code into Expression tree. It's quite almost the same concept as AST of any programming language. These expression trees are further translated into SQL expression specific to SQL Server's SQL, because currently LINQ to SQL only supports SQL Server 2005 and 2008.

Categories

Resources