I'm using this search function. but I need it to do an "and" not an "or" I cant seem to get it to return the results I want. I need to perform a search function where the search results match the text entered in box. But only a partial search. For example if I type in "Super D" I want it to find everything that contains a "Super" AND a "D".
public static class ObjectContextExtensions
{
public static IQueryable<T> FullTextSearch<T>(this IQueryable<T> queryable, string searchKey)
{
return FullTextSearch<T>(queryable, searchKey, false);
}
public static IQueryable<T> FullTextSearch<T>(this IQueryable<T> queryable, string searchKey,
bool exactMatch)
{
ParameterExpression parameter = Expression.Parameter(typeof(T), "c");
MethodInfo containsMethod = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
// MethodInfo toStringMethod = typeof (object).GetMethod("ToString", new Type[] {});
var publicProperties =
typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(p => p.PropertyType == typeof(string));
Expression orExpressions = null;
string[] searchKeyParts;
if (searchKey == null)
{
searchKey = "0";
}
searchKeyParts = !exactMatch ? searchKey.Split(' ') : new[] { searchKey };
foreach (MethodCallExpression callContainsMethod in from property in publicProperties
select Expression.Property(parameter, property) into nameProperty
from searchKeyPart in searchKeyParts
let searchKeyExpression = Expression.Constant(searchKeyPart) let containsParamConverted = Expression.Convert(searchKeyExpression, typeof(string))
select Expression.Call(nameProperty, containsMethod, (Expression)containsParamConverted))
{
if (orExpressions == null)
{
orExpressions = callContainsMethod;
}
else
{
orExpressions = Expression.Or(orExpressions,callContainsMethod);
}
}
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryable.ElementType },
queryable.Expression,
Expression.Lambda<Func<T, bool>>(orExpressions, new ParameterExpression[] { parameter }));
return queryable.Provider.CreateQuery<T>(whereCallExpression);
}
}
Looks as though setting exactMatch to true would do the trick as it wouldn't split the search terms up.
FullTextSearch<MyType>(searchKey, true)
Failing that, change
orExpressions = Expression.Or(orExpressions,callContainsMethod);
to
andExpressions = Expression.And(andExpressions,callContainsMethod);
Related
I have a function that I use to OrderBy() dynamically:
internal static Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> GetOrderBy(List<(string, bool)> orderColumnsAndIsDesc)
{
bool IsFirst = true;
MethodCallExpression resultExp = null;
string methodName;
LambdaExpression finalLambda = null;
foreach (var item in orderColumnsAndIsDesc)
{
string prop = item.Item1;
string orderType = item.Item2 == true ? "asc" : "desc";
Type typeQueryable = typeof(IQueryable<TEntity>);
ParameterExpression argQueryable = Expression.Parameter(typeQueryable, "p");
var outerExpression = Expression.Lambda(argQueryable, argQueryable);
IQueryable<TEntity> query = new List<TEntity>().AsQueryable<TEntity>();
Type type = typeof(TEntity);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
PropertyInfo pi = type.GetProperty(prop, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
LambdaExpression lambda = Expression.Lambda(expr, arg);
if (IsFirst)
{
methodName = orderType == "asc" ? "OrderBy" : "OrderByDescending";
resultExp =
Expression.Call(typeof(Queryable), methodName, new Type[] { typeof(TEntity), type }, outerExpression.Body, Expression.Quote(lambda));
}
else
{
methodName = orderType == "asc" ? "ThenBy" : "ThenByDescending";
resultExp =
Expression.Call(typeof(Queryable), methodName, new Type[] { typeof(TEntity), type }, resultExp, Expression.Quote(lambda));
}
finalLambda = Expression.Lambda(resultExp, argQueryable);
IsFirst = false;
}
return (Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>)finalLambda.Compile();
}
It works correctly when there is one item to sort, but it gives an error when there is more than one item. its error:
System.InvalidOperationException: 'variable 'p' of type 'System.Linq.IQueryable'1[CMS.Data.Models.Category]' referenced from scope '', but it is not defined'
I don't know how to fix it, please help me.
Try this:
public static class IEnumerableExtensions {
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> source, params Tuple<string, bool>[] sortDefinitions)
=> OrderBy<T>(source, sortDefinitions.Select(i => new Tuple<Func<T, object>, bool>(GetPropertyLambda<T>(i.Item1), i.Item2)).ToArray());
//you can try use lambda direcly in params
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> source, params Tuple<Func<T, object>, bool>[] sortDefinitions) {
if(source?.Any() ?? false) {
var items = source;
foreach(var sortDefinition in sortDefinitions)
if(items is IOrderedEnumerable<T> ordered)
//thenby
items = sortDefinition.Item2 ? ordered.ThenBy(sortDefinition.Item1) : ordered.ThenByDescending(sortDefinition.Item1);
else
items = sortDefinition.Item2 ? items.OrderBy(sortDefinition.Item1) : items.OrderByDescending(sortDefinition.Item1);
return items;
} else
return source;
}
//based on your code
private static Func<T, object> GetPropertyLambda<T>(string propertyName) {
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
PropertyInfo pi = type.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
expr = Expression.Property(expr, pi);
return (Func<T, object>) Expression.Lambda(expr, arg).Compile();
}
}
If you use extension method ApplyOrderBy, you can write your function in the following way:
public static Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> GetOrderBy<TEntity>(
IEnumerable<(string, bool)> order)
{
return query => (IOrderedQueryable<TEntity>)query
.ApplyOrderBy(order.Select(t => Tuple.Create(t.Item1, t.Item2)));
}
I have a function like this:
public CountryDto FindCountryByName(string name)
{
Country country = _countryRepository.GetAll().Where(g => g.Name.ToLower().Trim() == name.ToLower().Trim()).FirstOrDefault();
var dto = _mapper.Map<Country, CountryDto>(country);
return dto;
}
and it's referred to GetAll-function in the GenericRepository
public IEnumerable<T> GetAll()
{
return table.ToList();
}
Is it possible creating a function like this (in the GenericRepository)?
public IEnumerable<T> FindByName(string objname, string name)
{
return table.Where(t => t.GetType(objname) == name);
}
By example
Country country = _countryRepository.FindByName("CountryName", name);
and
AlbumTrack track = _albumtrackRepository.FindByName("SongTitle", songTitle);
I don't remember where I found this function, I'm using it to generate LINQ queries based on given property name, please note that you can change the search function "Equals" to "Contains" or "StartsWith" :
///<summary>
/// Create query that search in given property of class T for matching results with value
/// </summary>
public static IQueryable<T> CreateSearchQuery<T>(IQueryable<T> queryable, string PropertyName, string value) where T : class
{
IQueryable<T> query = queryable;
List<Expression> expressions = new List<Expression>();
ParameterExpression parameter = Expression.Parameter(typeof(T), "p");
MethodInfo Equals_Method = typeof(string).GetMethod("Equals", new[] { typeof(string) });
MethodInfo ToString_Method = typeof(object).GetMethod("ToString");
//Iterate through all properties Except inherited ones
foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public)
.Where(x => x.PropertyType == typeof(string) //properties of type string
|| x.Name == PropertyName))
{
ConstantExpression value_expression = Expression.Constant(value, typeof(string));
MemberExpression member_expression = Expression.PropertyOrField(parameter, prop.Name);
//Combine ToString() and Equals() methods
MethodCallExpression callChain = Expression.Call(Expression.Call(member_expression, ToString_Method), Equals_Method, value_expression);
expressions.Add(callChain);
}
if (expressions.Count == 0)
return query;
Expression or_expression = expressions[0];
for (int i = 1; i < expressions.Count; i++)
{
or_expression = Expression.OrElse(or_expression, expressions[i]);
}
Expression<Func<T, bool>> expression = Expression.Lambda<Func<T, bool>>(or_expression, parameter);
return query.Where(expression);
}
I have to use Expression from LINQ to evaluate some data. I have created this small test program. The first example works find. It returns TRUE if any string is "abc".
public class ContainProgramClass
{
static void Main(string[] args)
{
ParameterExpression instance = Expression.Parameter(typeof(ContainClass), "item");
var mainClass = new ContainClass { Data = new List<string> { "abc", "def" } };
var expression = mainClass.CreateExpression(instance);
var lambda = Expression.Lambda<Func<ContainClass, bool>>(expression, instance);
Func<ContainClass, bool> f = lambda.Compile();
var result = f(mainClass);
Console.WriteLine($"Result: {result}"); // Result true
mainClass.Data = new List<string> { "foo", "bar" };
result = f(mainClass);
Console.WriteLine($"Result: {result}"); // Result false
Console.ReadKey();
}
}
public class ContainClass
{
private static readonly MethodInfo MethodContains = typeof(Enumerable).GetMethods(
BindingFlags.Static | BindingFlags.Public)
.Single(m => m.Name == nameof(Enumerable.Contains) && m.GetParameters().Length == 2);
private static readonly MethodInfo EnumerableCastMethod = typeof(Enumerable).GetMethod("Cast");
private static MethodInfo GenericContainsMethod = MethodContains.MakeGenericMethod(typeof(object));
public List<string> Data { get; set; }
public Expression CreateExpression(Expression instance)
{
string propertyName = nameof(Data);
MemberExpression collectionPropertyAccessor = Expression.Property(instance, propertyName);
MethodCallExpression genericCollectionPropertyAccessor = Expression.Call(null
, EnumerableCastMethod.MakeGenericMethod(new[] { typeof(object) })
, collectionPropertyAccessor);
var distinctValueConstant = Expression.Constant("abc");
var containsExpression = Expression.Call(
ContainClass.GenericContainsMethod,
genericCollectionPropertyAccessor,
distinctValueConstant);
return containsExpression;
}
}
However, I would like for it to return TRUE if any SUBstring is "abc". Like this:
public class AnyProgramClass
{
static void Main(string[] args)
{
ParameterExpression instance = Expression.Parameter(typeof(ContainClass), "item");
var mainClass = new ContainClass { Data = new List<string> { "abcef", "ghi" } };
var expression = mainClass.CreateExpression(instance);
var lambda = Expression.Lambda<Func<ContainClass, bool>>(expression, instance);
Func<ContainClass, bool> f = lambda.Compile();
var result = f(mainClass);
Console.WriteLine($"Result: {result}"); // Result true
mainClass.Data = new List<string> { "abc", "bar" };
result = f(mainClass);
Console.WriteLine($"Result: {result}"); // Result true
mainClass.Data = new List<string> { "foo", "bar" };
result = f(mainClass);
Console.WriteLine($"Result: {result}"); // Result false
Console.ReadKey();
}
}
public class MainClass1
{
private static readonly MethodInfo MethodContains = typeof(Enumerable).GetMethods(
BindingFlags.Static | BindingFlags.Public)
.Single(m => m.Name == nameof(Enumerable.Contains) && m.GetParameters().Length == 2);
private static readonly MethodInfo MethodAny = typeof(Enumerable).GetMethods(
BindingFlags.Static | BindingFlags.Public)
.Single(m => m.Name == nameof(Enumerable.Any) && m.GetParameters().Length == 2);
private static readonly MethodInfo EnumerableCastMethod = typeof(Enumerable).GetMethod("Cast");
private static MethodInfo GenericContainsMethod = MethodContains.MakeGenericMethod(typeof(object));
private static MethodInfo GenericAnyMethod = MethodAny.MakeGenericMethod(typeof(object));
public List<string> Data { get; set; }
public Expression CreateExpression(Expression instance)
{
string propertyName = nameof(Data);
MemberExpression collectionPropertyAccessor = Expression.Property(instance, propertyName);
// would something like
// listOfStrings.Any(str => str.Contains("abc"))
return anyExpression;
}
}
I really have no idea on how to implement that Any(....). I usually get lost in the data types. Hopes some Expression-expert can lent me hand with this :)
You can do it for example like this:
string propertyName = nameof(Data);
MemberExpression collectionPropertyAccessor = Expression.Property(instance, propertyName);
var anyMethod = MethodAny.MakeGenericMethod(typeof(string));
Expression<Func<string, bool>> contains = x => x.Contains("abc");
return Expression.Call(anyMethod, collectionPropertyAccessor, contains);
Here you let compiler build expression you are passing to Any(..) call. If that doesn't suite your needs, you can build that expression manually:
string propertyName = nameof(Data);
MemberExpression collectionPropertyAccessor = Expression.Property(instance, propertyName);
var anyMethod = MethodAny.MakeGenericMethod(typeof(string));
// find string.Contains(string)
var containsMethod = typeof(string).GetMethods(BindingFlags.Instance | BindingFlags.Public).Single(c => c.Name == "Contains" && c.GetParameters().Length == 1 && c.GetParameters()[0].ParameterType == typeof(string));
// new parameter for sub-expression you pass to Any
ParameterExpression x = Expression.Parameter(typeof(string), "x");
// (string x) => x.Contains("abc") expression
Expression<Func<string, bool>> contains = Expression.Lambda<Func<string, bool>>(
Expression.Call(x, containsMethod, Expression.Constant("abc")), x);
return Expression.Call(anyMethod, collectionPropertyAccessor, contains);
I have to following code:
private static bool DoesColValueExist<T>(IQueryable dataToSearchIn, string colName, string colValue)
{
int noOfClients = 1;
Type type = typeof(T);
if (colValue != "" && colName != "")
{
var property = type.GetProperty(colName);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
Expression left = Expression.Call(propertyAccess, typeof(object).GetMethod("ToString", System.Type.EmptyTypes));
left = Expression.Call(left, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Expression right = Expression.Constant(colValue.ToLower(), typeof(string));
MethodInfo method = typeof(string).GetMethod("Equals", new[] { typeof(string) });
Expression searchExpression = Expression.Call(left, method, right);
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { type },
dataToSearchIn.Expression,
Expression.Lambda<Func<T, bool>>(searchExpression, new ParameterExpression[] { parameter }));
var searchedData = dataToSearchIn.Provider.CreateQuery(whereCallExpression);
noOfClients = searchedData.Cast<T>().Count();
if (noOfClients == 0)
return false;
else
return true;
}
return true;
}
It works with LINQ to SQL but with LINQ to Entities, I get the error:
LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.
Linq to Entities does not support .ToString() method. I also am not sure if this is a great idea to use string comparison for types that are not strings. But not all is lost. I came up with the following solution:
public partial class MyEntity
{
public int ID { get; set; }
public int Type { get; set; }
public string X { get; set; }
}
public class MyContext : DbContext
{
public DbSet<MyEntity> Entities { get; set; }
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyContext>());
using (var ctx = new MyContext())
{
if (!ctx.Entities.Any())
{
ctx.Entities.Add(new MyEntity() { ID = 1, Type = 2, X = "ABC" });
ctx.SaveChanges();
}
Console.WriteLine(DoesColValueExist(ctx.Entities, e => e.X, "aBc"));
Console.WriteLine(DoesColValueExist(ctx.Entities, e => e.X, "aBcD"));
Console.WriteLine(DoesColValueExist(ctx.Entities, e => e.Type, 2));
Console.WriteLine(DoesColValueExist(ctx.Entities, e => e.Type, 5));
}
}
private static bool DoesColValueExist<TEntity, TProperty>(IQueryable<TEntity> dataToSearchIn, Expression<Func<TEntity, TProperty>> property, TProperty colValue)
{
var memberExpression = property.Body as MemberExpression;
if (memberExpression == null || !(memberExpression.Member is PropertyInfo))
{
throw new ArgumentException("Property expected", "property");
}
Expression left = property.Body;
Expression right = Expression.Constant(colValue, typeof(TProperty));
if (typeof(TProperty) == typeof(string))
{
MethodInfo toLower = typeof(string).GetMethod("ToLower", new Type[0]);
left = Expression.Call(left, toLower);
right = Expression.Call(right, toLower);
}
Expression searchExpression = Expression.Equal(left, right);
var lambda = Expression.Lambda<Func<TEntity, bool>>(Expression.Equal(left, right), new ParameterExpression[] { property.Parameters.Single() });
return dataToSearchIn.Where(lambda).Any();
}
}
The nice thing about it is that it is more type safe than string based solution - the value of the parameter has to be the same as the value of the property. The property in turn has to be a member of the entity that is the generic type of the IQueryable'1 passed as the first parameter. Another helpful thing is that when coding against this method intellisense will show you member of the entity when you start typing the lambda expression for the second parameter. In the method itself I added an exception for string type when I call .ToLower() on both property value and the requested value to make the comparison case insensitive. For non-string types the values are compared "as is" i.e. without any modifications.
The example above is complete - you can copy and paste it to a console app project (you need to reference EntityFramework.dll though).
Hope this helps.
Try this:
private static bool DoesColValueExist<T>(IQueryable dataToSearchIn, string colName, string colValue)
{
int noOfClients = 1;
Type type = typeof(T);
if (colValue != "" && colName != "")
{
var property = type.GetProperty(colName);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
Expression left = property.PropertyType == typeof(string) ? propertyAccess : Expression.Call(propertyAccess, typeof(object).GetMethod("ToString", System.Type.EmptyTypes));
left = Expression.Call(left, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Expression right = Expression.Constant(colValue.ToLower(), typeof(string));
MethodInfo method = typeof(string).GetMethod("Equals", new[] { typeof(string) });
Expression searchExpression = Expression.Call(left, method, right);
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { type },
dataToSearchIn.Expression,
Expression.Lambda<Func<T, bool>>(searchExpression, new ParameterExpression[] { parameter }));
var searchedData = dataToSearchIn.Provider.CreateQuery(whereCallExpression);
noOfClients = searchedData.Cast<T>().Count();
if (noOfClients == 0)
return false;
else
return true;
}
return true;
}
Basically, if the property is string then it doesn't call the ToString() method.
Hope it helps.
I am just getting started with expression trees so I hope this makes sense. I am trying to create an expression tree to represent:
t => t.SomeProperty.Contains("stringValue");
So far I have got:
private static Expression.Lambda<Func<string, bool>> GetContainsExpression<T>(string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameter, propertyName);
var containsMethodExp = Expression.*SomeMemberReferenceFunction*("Contains", propertyExp) //this is where I got lost, obviously :)
...
return Expression.Lambda<Func<string, bool>>(containsMethodExp, parameterExp); //then something like this
}
I just don't know how to reference the String.Contains() method.
Help appreciated.
Something like:
class Foo
{
public string Bar { get; set; }
}
static void Main()
{
var lambda = GetExpression<Foo>("Bar", "abc");
Foo foo = new Foo { Bar = "aabca" };
bool test = lambda.Compile()(foo);
}
static Expression<Func<T, bool>> GetExpression<T>(string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
You might find this helpful.
To perform a search like:
ef.Entities.Where(entity => arr.Contains(entity.Name)).ToArray();
which the trace string will be:
SELECT .... From Entities ... Where Name In ("abc", "def", "qaz")
I use the method I created below:
ef.Entities.Where(ContainsPredicate<Entity, string>(arr, "Name")).ToArray();
public Expression<Func<TEntity, bool>> ContainsPredicate<TEntity, T>(T[] arr, string fieldname) where TEntity : class {
ParameterExpression entity = Expression.Parameter(typeof(TEntity), "entity");
MemberExpression member = Expression.Property(entity, fieldname);
var containsMethods = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(m => m.Name == "Contains");
MethodInfo method = null;
foreach (var m in containsMethods) {
if (m.GetParameters().Count() == 2) {
method = m;
break;
}
}
method = method.MakeGenericMethod(member.Type);
var exprContains = Expression.Call(method, new Expression[] { Expression.Constant(arr), member });
return Expression.Lambda<Func<TEntity, bool>>(exprContains, entity);
}
How about this:
Expression<Func<string, string, bool>> expFunc = (name, value) => name.Contains(value);
In the client code:
bool result = expFunc.Compile()("FooBar", "Foo"); //result true
result = expFunc.Compile()("FooBar", "Boo"); //result false
Here is how to create an expression tree of string.Contains.
var method = typeof(Enumerable)
.GetRuntimeMethods()
.Single(m => m.Name == nameof(Enumerable.Contains) && m.GetParameters().Length == 2);
var containsMethod = method.MakeGenericMethod(typeof(string));
var doesContain = Expression
.Call(containsMethod, Expression.Constant(criteria.ToArray()),
Expression.Property(p, "MyParam"));
Actual usage at https://raw.githubusercontent.com/xavierjohn/Its.Cqrs/e44797ef6f47424a1b145d69889bf940b5581eb8/Domain.Sql/CatchupEventFilter.cs