Operator "IN" by key field in Linq to OData - c#

Goodafternoon everyone!
I'm trying to translate request in SQL:
SELECT *
FROM TABLE1
WHERE TABLE1.ID IN (SELECT ID FROM TABLE2)
into LINQ to OData.
As far as "IN" is not supported by ODAta protocol,WHERE part must be like TABLE1.ID=1 OR TABLE1.ID=2 OR ...,
I've tried to code generic method, that takes as input list of id's and returns correct Expression for LINQ in that way:
public static Expression<Func<T,bool>> Lambda<T>(this Expression expr ,List<int> ids)
{
ParameterExpression argParam = Expression.Parameter(typeof(T), "rep");
Expression<Func<T, bool>> lambda = code => 1 == 0;
var lambdaPred = Expression.Lambda<Func<T, bool>>(lambda.Body, argParam);
var attr = (DataServiceKeyAttribute)typeof(T).GetCustomAttribute(typeof(DataServiceKeyAttribute));
string keyName;
try
{
keyName = attr.KeyNames.FirstOrDefault();//get name of key attribute
}
catch
{
return null;
}
foreach (int id in ids)
{
var property = typeof(T).GetProperty(keyName);
Expression<Func<T, bool>> lambdatemp = code => (int)property.GetValue(code) == id;
var tmp = Expression<Func<T, bool>>.Or(lambdaPred.Body, lambdatemp.Body);
lambdaPred = Expression.Lambda<Func<T, bool>>(tmp, argParam);
}
return lambdaPred;
}
usage of this method:
Expression<Func<Client, bool>> lambda = code => 1 == 0;
var query = lambda.Body.Lambda<Client>(ids);
var retr = clientRepository.Retrieve(query).ToList();
but at runtime I'm getting error:
An exception of type 'System.NotSupportedException' occurred in
Microsoft.Data.Services.Client.dll but was not handled in user code
Additional information: The expression (((((False Or (Convert(Int32
Id.GetValue(code)) == 1044)) Or (Convert(Int32 Id.GetValue(code)) ==
8102)) Or (Convert(Int32 Id.GetValue(code)) == 5997)) Or
(Convert(Int32 Id.GetValue(code)) == 7761)) Or (Convert(Int32
Id.GetValue(code)) == 15455)) is not supported.
Do you know any ways to fix this problem?

The generic method you wrote is quite unclear (both signature and implementation). As I understand, the idea is to build expression like this
x => x.Id == Id1 || x.Id == Id2 || ....
Here is one possible way of such a method by using Expression.Equal and Expression.OrElse
public static class PredicateHelper
{
public static Expression<Func<T, bool>> In<T>(this Expression<Func<T, int>> idSelector, IEnumerable<int> ids)
{
Expression body = null;
foreach (var id in ids)
{
var operand = Expression.Equal(idSelector.Body, Expression.Constant(id));
body = body == null ? operand : Expression.OrElse(body, operand);
}
return body != null ? Expression.Lambda<Func<T, bool>>(body, idSelector.Parameters) : null;
}
}
Sample usage equivalent to your example
var idFilter = PredicateHelper.In((Client c) => c.Id, ids);
var result = clientRepository.Retrieve(idFilter).ToList();

Related

How to generate an Expression to query a match on any members of an entity or of its related Entities?

I have the following code that I'm using to perform searches on my efcore data. Since the data set it so huge, I had to start using dynamic / generic types. I've been able to query on entity level properties, but I'm struggling to query entities that would have been defined as .Include(x => x.SomeInclusionEntity)
I've included my working code, as well as the second labelled "THIS SECTION DOESNT WORK" to show my ideas. I know its not perfect, but it works reasonably well for our internal use cases. Most people just use basic string searches for the same things over and over.
public IQueryable<T> GetBySearchTerm(IQueryable<T> queryable, string search)
{
T thisEntityBaseModel = new T();
IEntityType set = _dbContext.Model.GetEntityTypes().First(x => x.ClrType.Name.ToUpper() == thisEntityBaseModel.ModelName.ToUpper());
List<Expression<Func<T, bool>>> predicateArray = new List<Expression<Func<T, bool>>>();
MethodInfo containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
foreach (IProperty columnProp in set.GetProperties()) {
if (columnProp.ClrType == typeof(string)) {
// Define the parameter
ParameterExpression xParam = Expression.Parameter(typeof(T), "x");
// Create the expression representing what column to do the search on
MemberExpression colExpr = Expression.Property(xParam, columnProp.Name);
// Create a constant representing the search value
ConstantExpression constExpr = Expression.Constant(search);
// Generate a method body that represents "column contains search"
MethodCallExpression lambdaBody = Expression.Call(colExpr, containsMethod, constExpr);
// Convert the full expression into a useable query predicate
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(lambdaBody, xParam);
predicateArray.Add(lambda);
}
}
/* THIS SECTION DOESNT WORK===========================================================
// Traverse declared navigation
foreach (INavigation declaredNavigation in set.GetDeclaredNavigations())
{
// These are the navigations included by EFcore that aren't part of the data model. Search them too
IEnumerable<IProperty> x = declaredNavigation.TargetEntityType.GetProperties();
foreach (IProperty columnProp in x)
{
if (columnProp.ClrType == typeof(string))
{
// Define the parameter
ParameterExpression xParam = Expression.Parameter(declaredNavigation.ClrType, "z");
// Create the expression representing what column to do the search on
MemberExpression colExpr = Expression.Property(xParam, columnProp.Name);
// Create a constant representing the search value
ConstantExpression constExpr = Expression.Constant(search);
// Generate a method body that represents "column contains search"
MethodCallExpression lambdaBody = Expression.Call(colExpr, containsMethod, constExpr);
// Convert the full expression into a useable query predicate
LambdaExpression zz = Expression.Lambda(lambdaBody, xParam);
//Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(lambdaBody, xParam);
//predicateArray.Add(lambda);
}
}
}
THIS SECTION DOESNT WORK===========================================================*/
// This performs an "OR" method on the predicates, since by default it wants to do "AND"
var predicate = PredicateBuilder.False<T>();
foreach (Expression<Func<T, bool>> expression in predicateArray) {
predicate = predicate.Or(expression);
}
// Process the ors
return (queryable.Where(predicate));
}
If I get it right, your target is to generate a query that will be equivalent to something like this:
users.Where(u => u.Name.Contains("Foo") ||
u.Alias.Contains("Foo") ||
... ||
u.City.CityName.Contains("Foo") ||
... ||
u.Pets.Any(p => p.Name.Contains("Foo") ||
...
);
In the part that did not work, in the line
MemberExpression colExpr = Expression.Property(xParam, columnProp.Name);
I think it generates u.CityName instead of u.City.CityName.
You need to get the property name associated with the INavigation (in my example, it's City), and inject it in the lambda.
To retrieve the navigation property name just use INavigation.Name
Here is a working example of this implementation:
public static class DbSetExtension
{
public static IQueryable<T> DeepSearch<T>(this DbSet<T> dbSet, string search)
where T : class
{
return DeepSearch(dbSet, dbSet, search);
}
public static IQueryable<T> DeepSearch<T>(this IQueryable<T> queryable, DbContext dbContext, string search)
where T : class
{
var set = dbContext.Set<T>();
return DeepSearch(queryable, set, search);
}
public static IQueryable<T> DeepSearch<T>(this IQueryable<T> queryable, DbSet<T> originalSet, string search)
where T : class
{
var entityType = originalSet.EntityType;
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) })!;
// Ack to retrieve Enumerable.Any<>(IEnumerable<>, Func<,>)
var anyMethod = typeof(Enumerable)
.GetMethods()
.Single(m => m.Name == "Any" &&
m.GetParameters().Length == 2);
// {x}
var xParam = Expression.Parameter(typeof(T), "x");
// {search}
var constExpr = Expression.Constant(search);
// {x.Name.Contains(search)} list
var xDotNames = entityType.GetProperties()
.Where(p => p.ClrType == typeof(string))
.Select(p => Expression.Property(xParam, p.Name))
.Select(e => (Expression)Expression.Call(e, containsMethod, constExpr));
// {x.Navigation.Name.Contains(search)} list
var xDotOtherDotNames = entityType.GetDeclaredNavigations()
.Where(n => !n.IsCollection)
.SelectMany(n => n.TargetEntityType
.GetProperties()
.Where(p => p.ClrType == typeof(string))
.Select(p => NestedProperty(xParam, n.Name, p.Name)))
.Select(e => Expression.Call(e, containsMethod, constExpr));
// {x.Navigations.Any(n => n.Name.Contains(search))} list
var xDotOthersDotNames = entityType.GetDeclaredNavigations()
.Where(n => n.IsCollection)
.SelectMany(n =>
{
var nType = n.TargetEntityType.ClrType;
// Enumerable.Any<NType>
var genericAnyMethod = anyMethod.MakeGenericMethod(nType);
// {n}
var nParam = Expression.Parameter(nType, "n");
// {x.Navigations}
var xDotNavigations = Expression.Property(xParam, n.Name);
return n.TargetEntityType
.GetProperties()
.Where(p => p.ClrType == typeof(string))
.Select(p =>
{
// {n.Name}
var nDotName = Expression.Property(nParam, p.Name);
// {n.Name.Contains(search)}
var contains =
Expression.Call(nDotName, containsMethod, constExpr);
// {n => n.Name.Contains(search)}
var lambda = Expression.Lambda(contains, nParam);
// {Enumerable.Any(x.Navigations, n => n.Name.Contains(search))
return Expression.Call(null, genericAnyMethod, xDotNavigations, lambda);
});
})
.ToList();
// { || ... }
var orExpression = xDotNames.Concat(xDotOtherDotNames)
.Concat(xDotOthersDotNames)
.Aggregate(Expression.OrElse);
var lambda = Expression.Lambda<Func<T, bool>>(orExpression, xParam);
return queryable.Where(lambda);
}
private static Expression NestedProperty(Expression expression, params string[] propertyNames)
{
return propertyNames.Aggregate(expression, Expression.PropertyOrField);
}
}

How do i append a simple expression onto IQueryable

I have the following method:
public List<Customer> SearchTest(string city, int skip, int take)
{
EcomContext db = new EcomContext();
var results = db.Customers.Where(n => n.City == city).OrdeyBy(n => n.Name).Skip(skip).Take(10);
results = AddDeleteCheck<Customer>(results);
return results.ToList()
}
And this reusable method:
private IQueryable<T> AddArchivedCheck<T>(IQueryable<T> data)
{
var parameter = Expression.Parameter(typeof(T));
var e1 = Expression.Equal(Expression.Property(parameter, "Archived"), Expression.Constant(false));
var e2 = data.Expression;
var e3 = Expression.Lambda<Func<T, bool>>(Expression.AndAlso(e1, e2), parameter);
return data.Where(e3);
}
I want to be able to call this method from a number of different functions so i have made it generic. It should take the expression from the IQueryable object and add a check onto this (Archived == false).
I am getting this error:
The binary operator AndAlso is not defined for the types 'System.Boolean' and 'System.Linq.IQueryable` [Ecom.Customer]
It is easier than what you wrote:
private static IQueryable<T> AddArchivedCheck<T>(IQueryable<T> data)
{
var parameter = Expression.Parameter(typeof(T));
var e1 = Expression.Equal(Expression.Property(parameter, "Archived"), Expression.Constant(false));
var lambda = Expression.Lambda<Func<T, bool>>(e1, parameter);
return data.Where(lambda);
}
Remember that in Linq:
var result = query.Where(condition1).Where(condition2);
is equivalent to:
var result = query.Where(condition1 && condition2);

Dynamic predicates for Linq-to-Entity queries

The following Linq-to-Entities query works fine:
var query = repository.Where(r => r.YearProp1.HasValue &&
r.YearProp1 >= minYear &&
r.YearProp1 <= maxYear);
My database has a dozen or so columns that all report year-related information (short? data type). I want to reuse the same Linq-to-Entities logic for all these columns. Something like:
Func<RepoEntity, short?> fx = GetYearPropertyFunction();
var query = repository.Where(r => fx(r).HasValue &&
fx(r) >= minYear &&
fx(r) <= maxYear);
This results in the error:
LINQ to Entities does not recognize the method
'System.Nullable`1[System.Int16] fx(RepoEntity)' method, and this
method cannot be translated into a store expression.
I understand why I am getting the error, but am wondering if there is a workaround that doesn't involve duplicating code a dozen times just to change the property on which the SQL query is operating.
I would be reusing the function in more than one query, so I guess the general version of my question is: Is there a way to convert a simple property-getter lambda function to an Expression that can be consumed by Linq-to-Entities?
Building off of Raphaƫl Althaus' answer, but adding the generic selector you were originally looking for:
public static class Examples
{
public static Expression<Func<MyEntity, short?>> SelectPropertyOne()
{
return x => x.PropertyOne;
}
public static Expression<Func<MyEntity, short?>> SelectPropertyTwo()
{
return x => x.PropertyTwo;
}
public static Expression<Func<TEntity, bool>> BetweenNullable<TEntity, TNull>(Expression<Func<TEntity, Nullable<TNull>>> selector, Nullable<TNull> minRange, Nullable<TNull> maxRange) where TNull : struct
{
var param = Expression.Parameter(typeof(TEntity), "entity");
var member = Expression.Invoke(selector, param);
Expression hasValue = Expression.Property(member, "HasValue");
Expression greaterThanMinRange = Expression.GreaterThanOrEqual(member,
Expression.Convert(Expression.Constant(minRange), typeof(Nullable<TNull>)));
Expression lessThanMaxRange = Expression.LessThanOrEqual(member,
Expression.Convert(Expression.Constant(maxRange), typeof(Nullable<TNull>)));
Expression body = Expression.AndAlso(hasValue,
Expression.AndAlso(greaterThanMinRange, lessThanMaxRange));
return Expression.Lambda<Func<TEntity, bool>>(body, param);
}
}
Could be used somewhat like the original query you were looking for:
Expression<Func<MyEntity, short?>> whatToSelect = Examples.SelectPropertyOne;
var query = Context
.MyEntities
.Where(Examples.BetweenNullable<MyEntity, short>(whatToSelect, 0, 30));
A predicate is a filter in itself that should evaluate to bool (for whether or not to include it in the results). You can rework your method to look like this and it should work:
public static Expression<Func<RepoEntity, bool>> FitsWithinRange(int minYear, int maxYear)
{
return w => w.HasValue && w >= minYear && w <= maxYear;
}
Edit: Oh and to use it:
var query = repository.Where(Repository.FitsWithinRange(minYear, maxYear));
You could do something like that (not sure if it will work "as is" in linq2 entities, but if you have a problem... just tell)
usage
var query = <your IQueryable<T> entity>.NullableShortBetween(1, 3).ToList();
function
public static IQueryable<T> NullableShortBetween<T>(this IQueryable<T> queryable, short? minValue, short? maxValue) where T: class
{
//item (= left part of the lambda)
var parameterExpression = Expression.Parameter(typeof (T), "item");
//retrieve all nullable short properties of your entity, to change if you have other criterias to get these "year" properties
var shortProperties = typeof (T).GetProperties().Where(m => m.CanRead && m.CanWrite && m.PropertyType == typeof(short?));
foreach (var shortProperty in shortProperties)
{
//item (right part of the lambda)
Expression memberExpression = parameterExpression;
//item.<PropertyName>
memberExpression = Expression.Property(memberExpression, shortProperty);
//item.<PropertyName>.HasValue
Expression firstPart = Expression.Property(memberExpression, "HasValue");
//item.<PropertyName> >= minValue
Expression secondPart = Expression.GreaterThanOrEqual(memberExpression, Expression.Convert(Expression.Constant(minValue), typeof (short?)));
//item.<PropertyName> <= maxValue
var thirdPart = Expression.LessThanOrEqual(memberExpression, Expression.Convert(Expression.Constant(maxValue), typeof (short?)));
//item.<PropertyName>.HasValue && item.<PropertyName> >= minValue
var result = Expression.And(firstPart, secondPart);
//item.<PropertyName>.HasValue && item.<PropertyName> >= minValue && item.<PropertyName> <= maxValue
result = Expression.AndAlso(result, thirdPart);
//pass the predicate to the queryable
queryable = queryable.Where(Expression.Lambda<Func<T, bool>>(result, new[] {parameterExpression}));
}
return queryable;
}
EDIT : another solution, based on "simple" reflection, which "looks" as the one you want
public static short? GetYearValue<T>(this T instance)
{
var propertyInfo = typeof(T).GetProperties().FirstOrDefault(m => m.CanRead && m.CanWrite && m.PropertyType == typeof(short?));
return propertyInfo.GetValue(instance, null) as short?;
}
usage
var result = list.Where(item => item.GetYearValue() != null && item.GetYearValue() >= 1 && item.GetYearValue() <= 3).ToList();

Are Linq-To-Sql Dynamic-Where-Clauses Even Possible in Framework 3.5?

UPDATE: It Is Now Working
I was able to finally get it completed. A working-example is detailed in an answer below (which I will be able to mark-off in 2 days).
Everything Below Here Was Part of the Original Question
For the last 3 days, I have been trying to build a dynamic-where-clause on a DBML DataContext using code samples from questions posted here and from other sources as well...none have worked!
For the reasons below, I am beginning to wonder if this is even POSSIBLE using under Framework 3.5:
Predicate Builder notes Framework 4.0 on their site.
Some answers here talk about an equivolent Invoke versions in 4.0 (so I have some hope here).
...I could go on but you get the idea.
I am really at a loss and seem to be "grabbing at strings"...and I need some sound advice on how to approach this.
Original Version Had SOME Success But Only When:
The ONLY time I had a 'inkling' of success the data came-up (all 6178 rows of it) but no WHERE CLAUSE was applied. This was evidenced by the lack of any WHERE CLAUSE applied into the SQL found in the dataContext.GetCommand(query).CommandText.
Other Version #1 Fails:
And generates this error: "Method 'System.Object DynamicInvoke(System.Object[])' has no supported translation to SQL."
// VERSION 1:
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
}
public static Expression<Func<T, bool>> StringLike<T>(Expression<Func<T, string>> selector, string pattern)
{
var predicate = PredicateBuilder.True<T>();
var parts = pattern.Split('%');
if (parts.Length == 1) // not '%' sign
{
predicate = predicate.And(s => selector.Compile()(s) == pattern);
}
else
{
for (int i = 0; i < parts.Length; i++)
{
string p = parts[i];
if (p.Length > 0)
{
if (i == 0)
{
predicate = predicate.And(s => selector.Compile()(s).StartsWith(p));
}
else if (i == parts.Length - 1)
{
predicate = predicate.And(s => selector.Compile()(s).EndsWith(p));
}
else
{
predicate = predicate.And(s => selector.Compile()(s).Contains(p));
}
}
}
}
return predicate;
}
}
// VERSION 1:
public List<QuickFindResult> QueryDocuments(string searchText, string customerSiteId, List<int> filterIds)
{
var where = PredicateBuilder.True<vw_QuickFindResult>();
var searches = new List<String>(searchText.Split(' '));
searches.ForEach(productName =>
{
string like = productName.Replace('"', '%')
.Replace('*', '%');
where = PredicateBuilder.StringLike<vw_QuickFindResult>(x => x.DocumentName, like);
});
var results = DocumentCollectionService.ListQuickFind(where, null);
// Do other stuff here...
return results;
}
// VERSION 1:
public static List<vw_QuickFindResult> ListQuickFind(Expression<Func<vw_QuickFindResult, bool>> where, Expression<Func<vw_QuickFindResult, bool>> orderBy)
{
var connectionString = GetConnectionString(ES_DOCUMENTS_CONNECTION_NAME);
List<vw_QuickFindResult> results = null;
using (HostingEnvironment.Impersonate())
{
using (var dataContext = new ES_DocumentsDataContext(connectionString))
{
IQueryable<vw_QuickFindResult> query = dataContext.vw_QuickFindResults;
query = query.Where(where);
results = query.ToList();
}
}
return results;
}
Other Version #2 Fails:
And generates this error: "Method 'Boolean Like(System.String, System.String)' cannot be used on the client; it is only for translation to SQL."
// VERSION 2:
public List<QuickFindResult> QueryDocuments(string searchText, string customerSiteId, List<int> filterIds)
{
Func<vw_QuickFindResult, bool> where = null;
Func<string, Func<vw_QuickFindResult, bool>> buildKeywordPredicate = like => x => SqlMethods.Like(x.DocumentName, like);
Func<Func<vw_QuickFindResult, bool>, Func<vw_QuickFindResult, bool>, Func<vw_QuickFindResult, bool>> buildOrPredicate = (pred1, pred2) => x => pred1(x) || pred2(x);
// Build LIKE Clause for the WHERE
var searches = new List<String>(searchText.Split(' '));
searches.ForEach(productName =>
{
string like = productName.Replace('"', '%')
.Replace('*', '%');
where = (where == null) ? buildKeywordPredicate(like) : buildOrPredicate(where, buildKeywordPredicate(like));
});
var results = DocumentCollectionService.ListQuickFind(where, null);
// Do other stuff here...
return results;
}
// VERSION 2:
public static List<vw_QuickFindResult> ListQuickFind(Expression<Func<vw_QuickFindResult, bool>> where, Expression<Func<vw_QuickFindResult, bool>> orderBy)
{
var connectionString = GetConnectionString(ES_DOCUMENTS_CONNECTION_NAME);
List<vw_QuickFindResult> results = null;
using (HostingEnvironment.Impersonate())
{
using (var dataContext = new ES_DocumentsDataContext(connectionString))
{
var query = dataContext.vw_QuickFindResults.AsEnumerable();
query = query.Where(where);
results = query.ToList();
}
}
return results;
}
Did you try building the query yourself using only Exression classes?
There should be no particular problems there. It is actually relatively easy to learn.
You can write a sample query, and then in debugging see how it is composed:
Expression<Func<string, bool>> exp = (s) => s.Contains("your query");
Then simply look at the exp variable in the watch, and you can see the structure.
This particular example should be composed like this:
Expression constant = Expression.Constant("your query");
Expression p = Expression.Param(typeof(string);
Expression contains = Expression.Call(p, "Contains", constant);
Expression<Func<string, bool>> lambda = Expression.Lamba(contains, p);
// Now you can send this to your ORM
For what I can tell you, I have used LinqKit and PredicateBuilder back in early 2010 with .Net 3.5, EF 1.0 and EF Poco Adapter. Back then, LinqKit was compiled for Net 3.5
Maybe if you ask the author (Albahari), he could send you (or post on the site) the 3.5 version of that. I don't have it anymore because it is in projects at my old workplace and I don't have access to them.
As a side note, I feel your pain being forced to work with 3.5 after almost 2 years of .Net 4 being around.
THIS IS THE CORRECT ASWER
Here is the working version for those who need it. The issue was a COMBINATION of things. The first of which was the following line was set to True:
var where = PredicateBuilder.True<vw_QuickFindResult>();
It should be False...
var where = PredicateBuilder.False<vw_QuickFindResult>();
I don't know why...but other changes were needed also.
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
}
}
public List<QuickFindResult> QueryDocuments(string searchText, string customerSiteId, List<int> filterIds)
{
var wildCards = new string[] { "*", "\"" };
var where = PredicateBuilder.False<vw_QuickFindResult>();
var searches = new List<String>(searchText.Split(' ')); // TODO: <-- If more complex searches are needed we'll have to use RegularExpressions
// SEARCH TEXT - WHERE Clause
searches.ForEach(productName =>
{
Boolean hasWildCards = (productName.IndexOfAny(new char[] { '"', '*' }) != -1);
if (hasWildCards)
{
Int32 length = productName.Length;
if (length > 1)
{
string like = productName.Replace("%", "")
.Replace("*", "");
string first = productName.Substring(0, 1);
string last = productName.Substring(length - 1);
// Contains
if (wildCards.Contains(first) && wildCards.Contains(last))
where = where.Or(p => p.DocumentName.Contains(like) ||
p.DocumentTitle.Contains(like));
// EndsWith
else if (wildCards.Contains(first))
where = where.Or(p => p.DocumentName.EndsWith(like) ||
p.DocumentTitle.EndsWith(like));
// StartsWith
else if (wildCards.Contains(last))
where = where.Or(p => p.DocumentName.StartsWith(like) ||
p.DocumentTitle.StartsWith(like));
// Contains (default)
else
where = where.Or(p => p.DocumentName.Contains(like) ||
p.DocumentTitle.Contains(like));
}
else // Can only perform a "contains"
where = where.Or(p => p.DocumentName.Contains(productName) ||
p.DocumentTitle.Contains(productName));
}
else // Can only perform a "contains"
where = where.Or(p => p.DocumentName.Contains(productName) ||
p.DocumentTitle.Contains(productName));
});
// FILTER IDS - WHERE Clause
var filters = GetAllFilters().Where(x => filterIds.Contains(x.Id)).ToList();
filters.ForEach(filter =>
{
if (!filter.IsSection)
where = where.And(x => x.FilterName == filter.Name);
});
var dataSource = DocumentCollectionService.ListQuickFind(where);
var collection = new List<QuickFindResult>();
// Other UNRELATED stuff happens here...
return collection;
}
public static List<vw_QuickFindResult> ListQuickFind(Expression<Func<vw_QuickFindResult, bool>> where)
{
var connectionString = GetConnectionString(ES_DOCUMENTS_CONNECTION_NAME);
List<vw_QuickFindResult> results = null;
using (HostingEnvironment.Impersonate())
{
using (var dataContext = new ES_DocumentsDataContext(connectionString))
{
var query = dataContext.vw_QuickFindResults.Where(where).OrderBy(x => x.DocumentName).OrderBy(x => x.DocumentTitle);
results = query.ToList();
}
}
return results;
}

Joining multiple where clauses in LINQ as OR instead of AND

Is there anyway to join LINQ where clauses as OR ?
var ints = new [] { 1, 3, 5, 7 };
var query = from i in ints select i;
query = query.Where (q => q == 3);
query = query..Where (q => q == 7);
What I want is the ability to dynamically add where clauses but make them use OR instead of AND
If you want to stay with your strong-typing Linq queries you should look into LinqKit and predicate building. I have used this for something similar and found it to work well with And / Or stacking of filters.
Check out the C#4.0/3.0 in a Nutshell excerpt for more in depth info. Here is a snip from my code:
//Setup the initial predicate obj then stack on others:
basePredicate = basePredicate.And(p => false);
var predicate1 = PredicateBuilder.True<Person>();
foreach (SearchParms parm in parms)
{
switch (parm.field)
{
case "firstname":
predicate1 = predicate1.And(p => p.FirstName.Trim().ToLower().Contains(sValue));
break;
//etc...
}
}
//Run a switch based on your and/or parm value to determine stacking:
if (Parm.isAnd) {
basePredicate = basePredicate.And(predicate1);
} else {
basePredicate = basePredicate.Or(predicate1);
}
How about something like this?
var query = from i in ints where CheckConditions(i) select i;
public bool CheckConditions(int i)
{
var conditions = WhereConditions; //an IEnumerable<Func<int, bool>> of dynamically added conditions
foreach (var condition in conditions)
{
if (condition(i)) return true;
}
return false;
}
You can probably expand this to be a bit cleverer but that's sort of how I'd do it.
EDIT: Sorry the first example was an AND, have changed it now to be an OR. So the first time it encounters a passing condition it returns true.
Using ExpressionVisitor to help to build the expression base on two expressions with OR/AND relationship. This answer is from Jeffery Zhao's blog.
internal class ParameterReplacer : ExpressionVisitor
{
public ParameterReplacer(ParameterExpression paramExpr)
{
this.ParameterExpression = paramExpr;
}
public ParameterExpression ParameterExpression { get; private set; }
public Expression Replace(Expression expr)
{
return this.Visit(expr);
}
protected override Expression VisitParameter(ParameterExpression p)
{
return this.ParameterExpression;
}
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another)
{
var candidateExpr = Expression.Parameter(typeof(T), "candidate");
var parameterReplacer = new ParameterReplacer(candidateExpr);
var left = parameterReplacer.Replace(one.Body);
var right = parameterReplacer.Replace(another.Body);
var body = Expression.And(left, right);
return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another)
{
var candidateExpr = Expression.Parameter(typeof(T), "candidate");
var parameterReplacer = new ParameterReplacer(candidateExpr);
var left = parameterReplacer.Replace(one.Body);
var right = parameterReplacer.Replace(another.Body);
var body = Expression.Or(left, right);
return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}
You can using Union method:
var ints = new [] { 1, 3, 5, 7 };
var query = ints.Where(q => q == 3);
query = query.Union(ints.Where(q => q == 7));
Are you talking about specifying more than one condition in the lambda?
query = query.Where(q => q == 3 ||
q == 7);
try this
var ints = new [] { 1, 3, 5, 7 };
var query = ints.select(X=>X).where(X=>X==3||X==7);
I am trying to do something similar. Here's what I came up with:
//various test cases
bool useTestCase1 = true;
bool useTestCase2 = true;
bool useTestCase3 = false;
query = query.Where(q =>
(q == 3 && useTestCase1 ) ||
(q == 7 && useTestCase2 ) ||
(q == 10 && useTestCase3 )
);

Categories

Resources