C# Dynamic Linq: Implement "Like" in The Where Clause - c#

So I want to make a general sorter for my data. I have this code to get data from the database which will extract the data only which contains value.
using System.Linq.Dynamic;
public static IQueryable<object> SortList(string searchString, Type modelType,
IQueryable<object> model)
{
....
string toStringPredicate = type == typeof(string) ? propertyName +
".Contains(#0)" : propertyName + ".ToString().Contains(#0)";
model = model.Where(propertyName + " != NULL AND " + toStringPredicate, value);
}
The model is this:
public class ManageSubscriberItems
{
public int? UserId { get; set; }
public string Email { get; set; }
public Guid SubscriberId { get; set; }
}
When I call:
models = (IQueryable<ManageSubscriberItems>)EcommerceCMS.Helpers.FilterHelper
.SortList(searchString, typeof(ManageSubscriberItems), models);
if(models.Any())
It throws this error:
"LINQ to Entities does not recognize the method 'System.String
ToString()' method, and this method cannot be translated into a store
expression."
EDIT
I found the problem, but I still cannot fix it. So if the property is not string, it will throw an error when calling .ToString().Contains().
model = model.Where(propertyName + " != NULL AND " + propertyName +
".ToString().Contains(#0)", value);
What I want is to implement LIKE in the query. Can anyone help me?

If you use System.Linq.Dynamic.Core with EF Core, you have an option to use
var q = context.Cars.Where(config, "DynamicFunctions.Like(Brand, \"%a%\")");
See this link for an example:
https://github.com/StefH/System.Linq.Dynamic.Core/blob/6fc7fcc43b248940560a0728c4d181e191f9eec1/src-console/ConsoleAppEF2.1.1/Program.cs#L117
And I just tested in linqpad connecting to a real database, and code like this just works?
var result1 = Entity1s.Where("Url != NULL AND it.Url.Contains(#0)", "e");
[UPDATE 2019-04-17]]
In case you don't know the type, you can cast it to object and then cast that to a string.
Code:
var r = Entity1s.Select("string(object(Rating))").Where("Contains(#0)", "6");

so the problem here is that IQueryable thing happens on the SQL server not in C#... so SQL server doesn't know anything about .toString() method.
so => and Like operator it self works on strings.. so it's nvarchar and varchar data types in SQL server.
I could give You an example of how to achieve it if you could tell me more about your problem and what You want to achieve.
could do a sample.

You already have a "Like" in Linq that can run in the database and works with strings, it's called "IndexOf":
((IQueryable)model).Where(m => m.Property.IndexOf(searchString) == 1);
According to MSDN:
IndexOf(string)
'The zero-based index position of value if that string is found, or -1 if it is not. If value is Empty, the return value is 0.'

So I want to make a general sorter for my data.
instead of fixing 'invoke issue', general way should use generics, like
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source,
string property,
bool asc = true) where T : class
{
//STEP 1: Validate MORE!
var searchProperty = typeof(T).GetProperty(property);
if (searchProperty == null) throw new ArgumentException("property");
....
//STEP 2: Create the OrderBy property selector
var parameter = Expression.Parameter(typeof(T), "o");
var selectorExpr = Expression.Lambda(Expression.Property(parameter, property), parameter)
//STEP 3: Update the IQueryable expression to include OrderBy
Expression queryExpr = source.Expression;
queryExpr = Expression.Call(typeof(Queryable), asc ? "OrderBy" : "OrderByDescending",
new Type[] { source.ElementType, searchProperty.PropertyType },
queryExpr,
selectorExpr);
return source.Provider.CreateQuery<T>(queryExpr);
}
having property name string and direction usually used for making 'column sorting' on data.
Next are relation predicates are coming, and 'Linq.Dynamic' seems reasonable when doing from scratch, but there is generic and canonical form exists.

Related

Regex.IsMatch() in dynamic linq

how to invoke Regex.IsMatch() in IQueryable?
i found the same question but it doesnt work
Invoking Regex.IsMatch() inside a dynamic linq query
first i have
IQueryable Database = data.Verses.Select($"new {{ ID, {TextSearchType.ToString()} }}");
ive tried
var searchResult = Database.Where(Parse());
public static LambdaExpression Parse()
{
ParsingConfig.Default.CustomTypeProvider = new MyCustomTypeProvider();
var options = RegexOptions.IgnoreCase;
string compilableExpression = $"Regex.IsMatch({TextSearchType.ToString()}, \"(^| ){Keyword}($| )\", #0) == true";
ParameterExpression parameter = Expression.Parameter(typeof(Verses));
var DynamicExpression = DynamicExpressionParser.ParseLambda(new[] { parameter },
null,
compilableExpression,
options);
return DynamicExpression;
}
public class MyCustomTypeProvider : DefaultDynamicLinqCustomTypeProvider
{
public override HashSet<Type> GetCustomTypes()
{
return new HashSet<Type>
{
typeof(Object),
typeof(Boolean),
typeof(System.Text.RegularExpressions.Regex),
typeof(System.Text.RegularExpressions.RegexOptions),
};
}
}
TextSearchType is property name in Verses class.
im getting this error
No generic method 'Where' on type 'System.Linq.Queryable' is
compatible with the supplied type arguments and arguments. No type
arguments should be provided if the method is non-generic.
here is the linq code the im trying to convert to linq dynamic
var rx = new Regex("(^| )" + keyword + "($| )", RegexOptions.IgnoreCase);
var searchResult = Database.AsEnumerable()
.Where(x => rx.IsMatch(x.AyahText)).ToList();
You can not call IsMatch or any other Non-SQL supported functions (see the full list here) in LINQ to Entities.
In order to do what you want, you to have two possible options (maybe more, but I know two at this moment):
Get the items by raw filtering (without using IsMatch), converting the result to List, by calling .ToList() at the end of your query. And then you can filter the collection by Regex.IsMatch.
To be honest, this is not a greater solution, and I wouldn't say I like it.
Create a stored procedure and call it from the c# (official documentation here).

Creating a LINQ Select expression dynamically from string column names

I have a method which looks something like this:
public string GetColVal(string aAcctField, bool callM1, bool callM2)
{
if (callM1)
{
// create select clause here to select a string column with name
// equal to the value of aAcctField from Table1
// Expression<Func<Table1, string>> selector = ?
return M1(selector);
}
if (callM2)
{
// create select clause here to select a string column with name
// equal to the value of aAcctField from Table2
// Expression<Func<Table2, string>> selector = ?
return M2(selector);
}
}
And M1() is something like this:
public string M1(Expression<Func<Table1, string>> columnToSelect, string xType)
{
// other logic
string acct = _cntx.Where(c => c.Type == xType)
.Select(columnToSelect)
.FirstOrDefault();
return acct;
}
M2() is also something similar. Please note that the methods are over-simplified. And the methods M1() and M2() work perfectly. I can invoke them this way:
// MyColumn is a strongly typed column in Table1
string acct = M1(x => x.MyColumn, "some value");
But, inside the method GetColVal() how do I construct the select clauses? The comments regarding selector will help you understand what I intend to do. So, please go ahead and read the comment.
I have tried this:
public string GetColVal(string aAcctField, bool callM1, bool callM2)
{
if (callM1)
{
// create select clause here to select a string column with name
// equal to the value of aAcctField from Table1
Expression<Func<Table1, string>> selector = (w) => w.GetType().GetProperty(aAcctField).Name;
return M1(selector);
}
...
}
and I get the exception:
LINQ to Entities does not recognize the method
'System.Reflection.PropertyInfo GetProperty(System.String)' method,
and this method cannot be translated into a store expression
I have looked at these:
Create dynamic LINQ expression for Select with FirstOrDefault inside
Linq access property by variable
Get property value from string using reflection in C#
and many others.
but none of them is quite something like what I need.
Basically you need to use the Expression class methods like Expression.Lambda, Expression.PropertyOrField etc. to build the desired selector like:
static Expression<Func<T, TValue>> MemberSelector<T, TValue>(string name)
{
var parameter = Expression.Parameter(typeof(T), "item");
var body = Expression.PropertyOrField(parameter, name);
return Expression.Lambda<Func<T, TValue>>(body, parameter);
}
To support nested properties, change var body = ... line to
var body = name.Split('.').Aggregate((Expression)parameter, Expression.PropertyOrField);
Sample usage:
if (callM1)
{
return M1(MemberSelector<Table1, string>(aAcctField));
}
...

Creating an IQueryable to Equal int32?

I am trying to create an query extension which would compare a nullable int sql column value with a value. But i am struggling already over 8 hours to find any working solution.
I have already found a lot of help on this side. But all the remarks did not helped me.
I have altered the code so many times, but nothing seems to work. I want to create something similar as WHERE ManagerID IN (10,20,30)
The main code
IQueryable<Users> query = _context.CreateObjectSet<Users>();
query = query.IsMember(a => a.ManagerID, new Int32?[] { 10,20,30 });
return query.ToList();
Currently while executing the query.ToList(); it returns me a
Unable to create a constant value of type 'System.Object'. Only primitive types or enumeration types are supported in this context.
public static IQueryable<T> IsMember<T>(this IQueryable<T> source, Expression<Func<T, Int32?>> stringProperty, params Int32?[] searchTerms)
{
if (searchTerms == null || !searchTerms.Any())
{
return source;
}
Expression orExpression = null;
foreach (var searchTerm in searchTerms)
{
var searchTermExpression = Expression.Constant(searchTerm, typeof(object)); // <<--- This cast would make it no longer a primitive type
var containsExpression = Expression.Call(stringProperty.Body, typeof(Int32?).GetMethod("Equals"), searchTermExpression);
orExpression = BuildOrExpression(orExpression, containsExpression);
}
var completeExpression = Expression.Lambda<Func<T, bool>>(orExpression, stringProperty.Parameters);
return source.Where(completeExpression);
}
private static Expression BuildOrExpression(Expression existingExpression, Expression expressionToAdd)
{
return existingExpression == null ? expressionToAdd : Expression.OrElse(existingExpression, expressionToAdd);
}
The line marked to give the constant another datatype, is indeed causing the issue, but if I dont make it of type object, the Expression will not work, as it could not match the Int32? datatype.
Can anybody help me?
thanks
=============================================
Additional info
It is indeed to had a a larger picture.
I just want to create something more dynamic which could be used on other projects also.
I would like to use some functions which will look appealing than all those multilines
query = query.Like(a => a.UserName, filter.UserName, true);
query = query.Equals(a => a.UserTown, filter.UserTown, true);
query = query.IsMember(a => a.Division, filter.Division); // is an array of possible divisions
it worked fine for Like and Equals which are string based. But want to have a similar product for (nullable) integers
I was inspired by the following post. Which created a search function (which i renamed for my project to Like)
link
I wanted to create others similar. the last boolean is to verify if nullable in the column is allowed or not.
The reason also to use an extension, is that i also have alot of filters in my filter page.
With this extension, i would easily check in the beginning of my Like and Equal function if a filter was given without checking if my filter has a value 20x.
public static IQueryable<T> Like<T>(this IQueryable<T> source, Expression<Func<T, string>> stringProperty, string searchTerm, bool isnullValueAllowed)
if (String.IsNullOrEmpty(searchTerm))
{
return query;
}
It's not clear why you want to create this extension as you could simply write something like:
query.Where(user => (new[]{10,20,30}).Contains(user.ManagerId)).ToList();
However, assuming the real use case is somewhat more complicated than the example you've given, could you not construct your expression as either a comparison against a constant Int32 or a comparison against null, depending on whether searchTerm.HasValue() was true?
You need to use the equality operator == rather than the Equals method here. It actually makes your expression code a tad simpler:
foreach (var searchTerm in searchTerms)
{
var comparison = Expression.Equals(stringProperty.Body,
Expression.Constant(searchTerm));
orExpression = BuildOrExpression(orExpression, comparison);
}
Of course, as is mentioned by others, you don't need to build up an expression representing an OR of each of these comparisons, you can simply use Conatains on the set in a lambda and the query provider will do all of this for you.

Dynamic Linq query on relationship with foreign key of type Guid

I'm using System.Linq.Dynamic to query an IQueryable datasource dynamically using a where-clause in a string format, like this:
var result = source.Entities.Where("City = #0", new object[] { "London" });
The example above works fine. But now I want to query on a foreign key-property of type Guid like this:
var result = source.Entities.Where("CompanyId = #0", new object[] { "838AD581-CEAB-4B44-850F-D05AB3D791AB" });
This won't work because a Guid can't be compared to a string by default. And I have to provide the guid as a string because it's originally coming from a json-request and json does not support guid's.
Firstly, is this even a correct way of querying over a relationship or is there an other syntax for doing this?
Secondly, how do I modify Dynamic.cs from the Dynamic Linq-project to automatically convert a string to guid if the entity-property being compared with is of type guid?
You have many ways for solving. Simplest, as i think, will be change your query like this
var result = source.Entities.Where("CompanyId.Equals(#0)", new object[] { Guid.Parse("838AD581-CEAB-4B44-850F-D05AB3D791AB") });
If you want use operators = and == then in Dynamic.cs you need change interface IEqualitySignatures : IRelationalSignatures like this
interface IEqualitySignatures : IRelationalSignatures
{
....
F(Guid x, Guid y);
....
}
after that you can use next query
var result = source.Entities.Where("CompanyId=#0", new object[] { Guid.Parse("838AD581-CEAB-4B44-850F-D05AB3D791AB") });
OR
var result = source.Entities.Where("CompanyId==#0", new object[] { Guid.Parse("838AD581-CEAB-4B44-850F-D05AB3D791AB") });
But if you want use string parameter you need change ParseComparison method in ExpressionParser class. You need add yet another checking for operand types like this
....
//you need add this condition
else if(left.Type==typeof(Guid) && right.Type==typeof(string)){
right = Expression.Call(typeof(Guid).GetMethod("Parse"), right);
}
//end condition
else {
CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), op.text, ref left, ref right, op.pos);
}
....
and then you query will be work
var result = source.Entities.Where("CompanyId = #0", new object[] { "838AD581-CEAB-4B44-850F-D05AB3D791AB" });

Retrieving an Expression from a property and adding it to an expression tree

I've tried to simplify this example, as the actual code I'm playing with is more complex. So while this example may seem silly, bear with me. Let's say I'm working with the AdventureWorks database and I decide I want to add a property called Blarg to the Product table that returns an expression that contains code I would like to use in several places:
public partial class Product
{
public Expression<Func<Product, string>> Blarg
{
get { return product => product.ProductModelID.HasValue ? "Blarg?" : "Blarg!"; }
}
}
What I want to do is create an expression expression tree, have it get the Expression from Product.Blarg, and group by the result. Something like this:
var productParameter = Expression.Parameter(typeof(Product), "product");
// The Problem
var groupExpression = Expression.Lambda<Func<Product, string>>(
Expression.Invoke(
Expression.Property(productParameter, "Blarg"),
productParameter),
productParameter);
using (AdventureWorksDataContext db = new AdventureWorksDataContext())
{
var result = db.Products.GroupBy(groupExpression).ToList();
// Throws ArgumentException: "The argument 'value' was the wrong type.
// Expected 'System.Delegate'.
// Actual 'System.Linq.Expressions.Expression`1[System.Func`2[LINQ_Test.Product,System.String]]'."
}
Obviously groupExpression is incorrect (see the code comment for the exception), but I'm not sure how I should be doing it. What I thought I was saying is "get the Expression from the product.Blarg, execute it, and return the string result." I guess that's not what I'm actually saying there, though. I'm still trying to figure out expression trees. Any idea how I could pull this off?
When you call Expression.Invoke, the first argument must be an existing LambdaExpression - it can't be an Expression to a LambdaExpression. Or in other words: it isn't going to evaluate Product.Blarg per row and use a different sub-expression each time.
Instead, you would retrieve this lambda first, perhaps making it static and accessing it via reflection if you only know it by name:
var lambda = (LambdaExpression) typeof(Product)
.GetProperty("Blarg").GetValue(null,null);
And pass lambda in as the argument to Expression.Invoke; here's a fully working LINQ-to-Objects example showing this (via AsQueryable()):
using System;
using System.Linq;
using System.Linq.Expressions;
public partial class Product
{
public static Expression<Func<Product, string>> Blarg
{
get { return product => product.ProductModelID.HasValue ? "Blarg?" : "Blarg!"; }
}
public int? ProductModelID { get; set; }
static void Main()
{
var lambda = (LambdaExpression)typeof(Product)
.GetProperty("Blarg").GetValue(null, null);
var productParameter = Expression.Parameter(typeof(Product), "product");
// The Problem
var groupExpression = Expression.Lambda<Func<Product, string>>(
Expression.Invoke(
lambda,
productParameter),
productParameter);
var data = new[] {
new Product { ProductModelID = 123},
new Product { ProductModelID = null},
new Product { ProductModelID = 456},
};
var qry = data.AsQueryable().GroupBy(groupExpression).ToList();
}
}
var qry = data.AsQueryable().GroupBy(Blarg).ToList();
That works, same as Marc's code.
Note: Blarg is already correct, there is no reason to 're-invoke' it.

Categories