Using the LIKE operator in Dynamic LINQ [duplicate] - c#

This question already has an answer here:
Entity Core Dynamic LINQ LIKE function not found [closed]
(1 answer)
Closed 4 months ago.
Note: If you have the same problem and found this question before the "duplicated" one, be aware that the answer to that question does not work. I marked the only working solution below.
I'm trying to follow this guide which states that this should be possible in Dynamic LINQ:
var config = new ParsingConfig { ResolveTypesBySimpleName = true };
var example2 = Cars.Where(config, "DynamicFunctions.Like(Brand, \"%t%\")");
example2.Dump();
I'm using string concatination to build a complex query:
IQueryable<DtcViewEntity> queryable = ...
string[] values = { "%a%" };
string myOperator = "and"; // or sometimes "or"
bool isNot = false; // or sometimes "true"
var query = string.Join($" {myOperator} ", values.Select((value, index) => $"DynamicFunctions.Like(MyColumn, #{index})"));
if (isNot) query = $"not ({query})";
var config = new ParsingConfig { ResolveTypesBySimpleName = true };
return queryable.Where(config, query, values);
But the actual error can be reproduced with code that looks almost identical to the example in the guide:
IQueryable<DtcViewEntity> queryable = ...
var config = new ParsingConfig { ResolveTypesBySimpleName = true };
var result = queryable.Where(config, "DynamicFunctions.Like(MyColumn, \"%a%\")").ToList();
And I get:
System.Linq.Dynamic.Core.Exceptions.ParseException : Enum type 'DynamicFunctions' not found
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseAsEnum(String id)
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseMemberAccess(Type type, Expression expression)
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseIdentifier()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParsePrimaryStart()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParsePrimary()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseUnary()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseMultiplicative()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseAdditive()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseShiftOperator()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseComparisonOperator()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseLogicalAndOrOperator()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseIn()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseAndOperator()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseOrOperator()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseLambdaOperator()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseNullCoalescingOperator()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseConditionalOperator()
I've seen this code as well, but using var config = ParsingConfig.DefaultEFCore21; will result in
System.Linq.Dynamic.Core.Exceptions.ParseException : Enum type 'DynamicFunctions' not found
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseAsEnum(String id)
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseMemberAccess(Type type, Expression expression)
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParseIdentifier()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParsePrimaryStart()
at System.Linq.Dynamic.Core.Parser.ExpressionParser.ParsePrimary()
After adding Microsoft.EntityFrameworkCore.DynamicLinq to the dependencies I get:
System.Linq.Dynamic.Core.Exceptions.ParseException : No applicable method 'Like' exists in type 'DynamicFunctions'
Falco Alexander even found a "working example" form ZZZ Projects that produces the very same error message:
Unhandled exception. Enum type 'DynamicFunctions' not found (at index 21)
Command terminated by signal 6
I can't find any information on this exception, so my question is: what am I doing wrong?

my code runs fine if I use EF.Functions directly
var result = context.Cars.Where(c => EF.Functions.Like(c.Brand, "%a%")).ToList();
also testing with Linqpad and Northwind localDB with a different non-EF provider.
var config = new ParsingConfig { ResolveTypesBySimpleName = true };
var example2 = from c in Customers
where SqlMethods.Like(c.City, "[aeiou]%")
select c;

Get Extension methods from this answer. It contains GetItemsPredicate function, which will help in building predicate.
Then we can generate needed predicate without Dynamic.Linq and avoid raw text parsing.
// input parameters
string[] values = { "%a%" };
var isOr = true;
var isNot = false;
var predicate = queryable.GetItemsPredicate(values, (e, v) => EF.Functions.Like(e.MyColumn, v), isOr);
if (isNot)
{
// inversion
predicate = Expression.Lambda<Func<DtcViewEntity, bool>>(Expression.Not(predicate.Body), predicate.Parameters);
}
// applying generated predicate
queryable = queryable.Where(predicate);

Looks like the errors you are getting have to do with Linq not being
able to "transform" the data so it can parse it or something along
those lines Is it possible for you instead of the "Like" operator you
use the "Contains"?
var Something = SomethingElse.Filter(x => x.Argument.Contains(Description));

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

How to get symbol info from semantic model in roslyn?

I am trying to create a code analyzer in Roslyn, and I need to analyze SqlCommand usage in a project. I have written the analyzer and it works fine when I test it out in Visual Studio project, but when I am writing unit test and I am trying to get SymbolInfo from SemanticModel and am always getting null.
What am i missing?
string test = #"public class TestClass
{
public void SomeMethod(int x)
{
var command = new SqlCommand(""Some COmmabnd"",new SqlConnection(""conn string""));
command.ExecuteReader();
}
}";
var tree = CSharpSyntaxTree.ParseText(test);
var systemDataReference = MetadataReference.CreateFromFile(typeof(System.Data.IDbCommand).Assembly.Location);
var systemConfigurationReference = MetadataReference.CreateFromFile(typeof(ConfigurationManager).Assembly.Location);
var systemTransactionReference = MetadataReference.CreateFromFile(typeof(Transaction).Assembly.Location);
var systemXmlnReference = MetadataReference.CreateFromFile(typeof(XPathDocument).Assembly.Location);
var system = MetadataReference.CreateFromFile(typeof(Uri).Assembly.Location);
var mscorRef = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var systemCore = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location);
var systemNumerics = MetadataReference.CreateFromFile(typeof(BigInteger).Assembly.Location);
var compilation = CSharpCompilation.Create("TestCompilatin", new[] {tree},
new[]
{
mscorRef, system, systemXmlnReference, systemTransactionReference, systemDataReference,
systemConfigurationReference,systemCore,systemNumerics
});
var semanticModel = compilation.GetSemanticModel(tree);
var invocationExpressions = tree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach (var invocationExpressionSyntax in invocationExpressions)
{
var memeber = invocationExpressionSyntax.Expression as MemberAccessExpressionSyntax;
var symbolInfo = semanticModel.GetSymbolInfo(memeber);
}
semanticModel.GetSymbolInfo() returns SymbolInfo with null symbol for anything that I try.
Another possible cause is that the source file for the invoked method has not been syntactically parsed and added to the compilation. You can always get some information about the method being invoked in the caller's syntax tree, but unless the invoked method has already been added to the compilation, it won't be available in the semantic model. This is painfully obvious after thinking about it for a bit. Of course the semantic model will not have information about a method that has not been processed. It is possible to add multiple syntax trees to a single compilation using AddSyntaxTrees() method. Note that this method returns a new compilation instance.
Hopefully this will save someone a little bit of time and I haven't embarrassed myself too much by posting this answer.

Convert string to lambda expression that contains variables from other classes

What I've been trying to do is convert a string of the form:
"StudentDatabase.avgHeight > 1.7"
to a lambda expression that looks like this:
() => StudentDatabase.avgHeight > 1.7;
I tried something in the lines of this:
/* String splitting and parsing occurs here */
var comparison = Expression.GreaterThan(
Type.GetType("MyNamespace.StudentDatabase").GetField("avgHeight"),
Expression.Constant(1.7)
);
var lambda = Expression.Lambda<Func<bool>>(comparison).Compile();
Of course something like this wouldn't work since the GetField() method returns type FieldInfo and not Expression.
Here's a list about useful stuff you might want to know about my sample code:
The StudentDatabase class is a static class that contains a static field avgHeight.
I have already done the part of the code that parses the string so there's no need to include it in any provided solutions.
This is just an example so you can change the string and variable/class names if you wish so.
This is not an assignment so feel free to post source code. In fact, that would be greately appreciated.
TL;DR; What I'm trying to do is use LINQ Expressions to access variables from other places of the code.
I disagree with the following comments, Linq expressions is a viable way to do this sort of thing. The below code accomplishes it. However, please consider the following code:
namespace MyNamespace
{
class Program
{
static void Main(string[] args)
{
/* String splitting and parsing occurs here */
var comparison = Expression.GreaterThan(
Expression.Field(null, Type.GetType("MyNamespace.StudentDatabase").GetField("avgHeight")),
Expression.Constant(1.7)
);
var lambda = Expression.Lambda<Func<bool>>(comparison).Compile();
StudentDatabase.avgHeight = 1.3;
var result1 = lambda(); //is true
StudentDatabase.avgHeight = 2.0;
var result2 = lambda(); //is false
}
}
class StudentDatabase
{
public static double avgHeight = 1.3;
}
}
Should result2 be true or false? If you want it to be true, then you have more work to do.
I've created this as a sort of framework you can work off of. It does not use LINQ but will output the value specified by the string.
var type = Type.GetType("MyNamespace.StudentDatabase");
if (type != null)
{
var field = type.GetField("avgHeight");
if (field != null)
{
Func<bool> lambda = () => (double)field.GetValue(type) > 1.7;
}
}
There is some error checking you could add/remove. The other areas such as the > and 1.7 can be parsed elsewhere and inserted but this is how you could get a value from the strings.

How to expand calling expressions other than predicates?

I just discovered LINQKit and am quite happy that it seems to provide a solution for the common problem of wanting to factor out parts of complex linq queries.
All examples, however, show how to factor out predicates for where-clauses.
Although that's a very typical use-case, I also want to factor out other kinds of expressions, typically for selects. Let's say I have the following sub-expression:
Expression<Func<SomeFunkyEntity, String>> GetStatusFromSomeFunkyEntity()
{
return i =>
i.IsExercise ? "Excercise" :
i.IsExpired ? "Expired" :
(i.IsLocked ) ? "Locked" :
i.IsAdmitted ? "Admissible" : "Unusable";
}
Does LINQKit provide a way to expand a call to this? I tried:
var query =
from i in this.DbContext.MyFunkyEntities.AsExpandable()
select new SomeFunkyEntityWithStatus()
{
FunkyEntity = i,
Status = GetStatusFromSomeFunkyEntity().Invoke(i)
};
This complies, but fails at runtime in LINQKit's expander:
Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpressionN' to type 'System.Linq.Expressions.LambdaExpression'.
The stack trace starts with:
at LinqKit.ExpressionExpander.VisitMethodCall(MethodCallExpression m)
at LinqKit.ExpressionVisitor.Visit(Expression exp)
at LinqKit.ExpressionVisitor.VisitMemberAssignment(MemberAssignment assignment)
...
Is this not supported, or do I do something wrong?
Have you tried to assign the expression to a local variable before calling invoke?
var statusFromSomeFunkyEntity = GetStatusFromSomeFunkyEntity();
var query =
from i in this.DbContext.MyFunkyEntities.AsExpandable()
select new SomeFunkyEntityWithStatus()
{
FunkyEntity = i,
Status = statusFromSomeFunkyEntity.Invoke(i)
};
Check out this answer.

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