How to get dynamic column data linq - c#

I have following code,
public List<MemberDto> GetMembers(out int rowCount,int pageIndex,int pageSize, string seachColumn = "", string searchTerm = "", string sortBy = "", string sortDiection = "")
{
var members = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.MemberNumber.Equals(searchTerm))
|| (string.IsNullOrEmpty(searchTerm) || m.LastName.Equals(searchTerm))
select m).AsEnumerable();
if (!string.IsNullOrEmpty(sortBy))
{
PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(EFModel.ClientData.Member)).Find(sortBy, true);
members = (sortDiection.ToLower() == "descnding") ? members.OrderByDescending(x => prop.GetValue(x)).ToList() : members.OrderBy(x => prop.GetValue(x)).ToList();
}
rowCount = (!string.IsNullOrEmpty(searchTerm)) ? members.Count() : rowCount = context.Members.Count() ;
members = members.Skip(pageIndex).Take(pageSize).ToList();
List<MemberDto> memberDtos = new List<MemberDto>();
mapper.Map(members, memberDtos);
return memberDtos;
}
In the above code, I seachColumn value can be ("a","b",or ""). When seachColumn = "a" I need to search table data by column MemberNumber based on searchTerm value
When seachColumn = "b" I need to search table data by column LastName. based on searchTerm value
to achieve that I wrote following code.
if(seachBy == "a")
{
var sa = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.MemberNumber.Equals(searchTerm))
select m).AsEnumerable();
}
else if (seachBy == "b")
{
var sa = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.LastName.Equals(searchTerm))
select m).AsEnumerable();
}
I know, I tried code is bit fool. Have any proper way to do this?

Yes there is a better way to do this. First of all you want to do this all as an IQueryable - Why? - Because as soon as you do .AsEnumerable() or .ToList() the query is executed on the DB Server and the data is loaded into Memory.
So in your code here - because you have called .AsEnumerable() it has loaded all context.Members.Where condition is true into Memory:
var members = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.MemberNumber.Equals(searchTerm))
|| (string.IsNullOrEmpty(searchTerm) || m.LastName.Equals(searchTerm))
select m)
.AsEnumerable();
Your second part of code is pagination. What we normally do is write a IQueryable extension methods.
So add the following class with the 2 extension methods to a common location.
public static class IQueryableExtensions
{
public static IQueryable<T> ApplyPagination<T>(this IQueryable<T> source, string sortDirection, string sortBy, int pageNumber, int pageSize)
{
var sortDirectionInternal = sortDirection == "asc" ? "OrderBy" : "OrderByDescending";
var orderBy = sortBy;
if (pageSize != -1) // -1 is for All - I don't apply pagination if pageSize == -1.
{
return source.OrderBy(orderBy, sortDirectionInternal)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize);
}
return source.OrderBy(orderBy, sortDirection);
}
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, string sortDirection)
{
var type = typeof(T);
// Get Property to Sort By
var property = type.GetProperty(ordering);
// If Property is NULL (not found) - Just use the first Property (Default) to ORDER BY - as this will prevent Exception
if (property == null)
{
property = type.GetProperties().First();
}
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
var resultExp = Expression.Call(typeof(Queryable), sortDirection, new[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
return source.Provider.CreateQuery<T>(resultExp);
}
}
So now your above code will look something like this - I didn't use VS so there might be some sytnax errors:
// NOTE: you will need to include the namespace for the new IQueryableExtensions class
var members = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.MemberNumber.Equals(searchTerm))
|| (string.IsNullOrEmpty(searchTerm) || m.LastName.Equals(searchTerm))
select m);
// retrieve count only if you need to of total members that match the above criteria
rowCount = members.Count();
// This is all you need to do! - ApplyPagination(params) -
members = members.ApplyPagination(sortDiection, sortBy, pageIndex, pageSize);
return mapper.Map<List<MemberDto>>(members);

Related

LINQ - How to ignore empty lists in where

these are my first steps with LINQ. I have two lists for filters as parameters, which can also be empty. If I execute the code this way, I don't get any values back from an empty list.
What does the code have to look like if empty lists are to be ignored?
public List<PersonDTO> GetPersons(int pageNumber, int pageSize, List<string> departments, List<string> locations, string filterText)
{
if (filterText == null)
{
filterText = "";
}
List<Person> personsList = _dbContext.Persons
.Where(a => (a.firstName.ToLower().Contains(filterText.ToLower()) || a.lastName.ToLower().Contains(filterText.ToLower()))
&& departments.Contains(a.department)
&& locations.Contains(a.location))
.Skip(pageNumber * pageSize).Take(pageSize).ToList();
return _mapper.Map<List<PersonDTO>>(personsList);
}
Handle the case that they are empty not in the query but with if:
IEnumerable<Person> persons = _dbContext.Persons;
if(!string.IsNullOrEmpty(filterText))
{
string lowerFilterText = filterText.ToLower();
persons = persons
.Where(p => p.firstName.ToLower().Contains(lowerFilterText) || a.lastName.ToLower().Contains(lowerFilterText));
}
if(departments.Any())
{
persons = persons.Where(p => departments.Contains(p.department));
}
if(locations.Any())
{
persons = persons.Where(p => locations.Contains(p.location));
}
List<Person> personList = persons.Skip(pageNumber * pageSize).Take(pageSize).ToList();
Due to LINQ's deferred execution this will execute the final query only once, at the final ToList.
See if negating Any() can help you.
Example:
string[] arr = new string[10] {"a","b","c","d","e","f","g","h","i","j"};
List<string> vowels = new List<string>() {"a","e","i","o","u"};
List<string> empty = new List<string>();
arr.Where(letter => vowels.Contains(letter));
//yields "a","e","i"
arr.Where(letter => (!empty.Any() || empty.Contains(letter)));
//yields "a","b","c","d","e","f","g","h","i","j"
Following your example, I'd chain .Where() expressions rather than putting it all into a big, single one.
You need to wrap the lists in parenthesis and use a count validation to make it optional for each list, like so:
List<Person> personsList = _dbContext.Persons
.Where(a =>
(a.firstName.ToLower().Contains(filterText.ToLower()) ||
a.lastName.ToLower().Contains(filterText.ToLower())) &&
(departments.Count == 0 || departments.Contains(a.department)) &&
(locations.Count == 0 || locations.Contains(a.location)))
.Skip(pageNumber * pageSize)
.Take(pageSize)
.ToList();
This way you switch the array conditional to an optional state, so whenever there is an item in the array !(list.Count == 0) it then tries to evaluate the filter.
You need something like this :
Why would you use Expression> rather than Func?
Expression<Func<Persons, bool>> expresionFinal = c => c.Active;
if (departments.Any())
{
Expression<Func<Persons, bool>> expresionDepartments = c => departments.Contains(p.department);
expresionFinal = PredicateBuilder.And(expresionFinal, expresionDepartments);
}
IQueryable query = dataContext.Persons;
query = query.Where(expresionFinal);
Try using .Where(s => !string.IsNullOrWhiteSpace(s)) to filter out null and empty strings entries in the list.
There are several way to check if list is empty:
1. If(list.Count() >0)
2. The best way, is to use "Any" instead of "Where" ,this will return boolian result, if true so there is some data, else there is nothing.
If you want to ignore empty list (departements and locations) in your where clause, you should be able to use Any():
public List<PersonDTO> GetPersons(int pageNumber, int pageSize, List<string> departments, List<string> locations, string filterText)
{
if (filterText == null)
{
filterText = "";
}
List<Person> personsList = _dbContext.Persons
.Where(a => (a.firstName.Contains(filterText, StringComparison.OrdinalIgnoreCase)
|| a.lastName.Contains(filterText, StringComparison.OrdinalIgnoreCase))
&& (!departments.Any() || departments.Contains(a.department))
&& (!locations.Any() || locations.Contains(a.location)))
.Skip(pageNumber * pageSize).Take(pageSize).ToList();
return _mapper.Map<List<PersonDTO>>(personsList);
}
and the code for contains:
public static class StringExtensions
{
public static bool Contains(this string source, string toCheck, StringComparison comp)
{
return source?.IndexOf(toCheck, comp) >= 0;
}
}
source: https://stackoverflow.com/a/444818/1248177
This should work. You should really build up your query before you call ToList() if you can. This will allow you to be able to do things in stages and make your application more efficient.
public List<PersonDTO> GetPersons(int pageNumber, int pageSize, List<string> departments, List<string> locations, string filterText = "")
{
List<Person> personList = new List<Person>();
if (!string.IsNullOrEmpty(filterText)) {
personsList = _dbContext.Persons
.Where(a => (a.firstName.ToLower().Contains(filterText.ToLower()) || a.lastName.ToLower().Contains(filterText.ToLower()))
&& departments.Contains(a.department)
&& locations.Contains(a.location)).ToList();
} else {
personList = _dbContext.Persons.ToList();
}
personList = personList.Skip(pageNumber * pageSize).Take(pageSize).ToList();
return _mapper.Map<List<PersonDTO>>(personsList);
}
Here is another example using IQueryable.
public List<PersonDTO> GetPersons(int pageNumber, int pageSize, List<string> departments, List<string> locations, string filterText = "")
{
IQueryable<List<Person>> personQuery = _dbContext.Persons.AsQueryable();
if (!string.IsNullOrEmpty(filterText))
{
personQuery = personQuery
.Where(a => (a.firstName.ToLower().Contains(filterText.ToLower()) || a.lastName.ToLower().Contains(filterText.ToLower()))
&& departments.Contains(a.department)
&& locations.Contains(a.location));
}
personQuery = personQuery.Skip(pageNumber * pageSize).Take(pageSize);
return _mapper.Map<List<PersonDTO>>(personQuery.ToList());
}
This is an example of how I did what you are trying to do.
public List<CourseSearchDetail> GetPaginated(SearchRequest searchRequest, bool admin, out int totalRecords,
out int recordsFiltered)
{
var query = _courseRepo
.GetDataTableQuery();
if (!admin) query = query.Where(x => x.CourseDate > DateTime.Now);
var courseList = query.ToList();
totalRecords = courseList.Count();
if (!string.IsNullOrEmpty(searchRequest.Search.Value))
courseList = courseList.Where(x => x.CourseTitle.ToLower().Contains(searchRequest.Search.Value.ToLower())).ToList();
recordsFiltered = courseList.Count();
if (searchRequest.Order == null)
courseList = courseList.OrderByDescending(x => x.CourseDate).ToList();
else
courseList = courseList.OrderResults(searchRequest);
var skip = searchRequest.Start;
var pageSize = searchRequest.Length;
courseList = pageSize > 0
? courseList.Skip(skip).Take(pageSize).ToList()
: courseList.ToList();
return courseList;
}

Expression tree for groupby with where clause and than select

From UI dynamic column are coming as parameter in API and based on the parameter I have to fetch data from database.
Example : In the below code, based on the column if condition linq query is being executed. Now I want to make it generic so that it serve if new column condition come in future.
public List<string> GetFilteredTypeAhead(string searchText,string searchForRole,int fiscalyear,int fiscalPeriod)
{
if (searchForRole == "column1")
{
var accounts = (from a in _context.Account
where a.column1.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear ==fiscalyear
group a.column1 by a.column2 into g
select g.Key).ToList();
return accounts;
}
else if(searchForRole == "column2")
{
var accounts = (from a in _context.Account
where a.column2.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear == fiscalyear
group a.column2 by a.column2 into g
select g.Key).ToList();
return accounts;
}
else if (searchForRole == "column3")
{
var accounts = (from a in _context.Account
where a.column3.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear == fiscalyear
group a.column3 by a.column3 into g
select g.Key).ToList();
return accounts;
}
else if (searchForRole == "column4")
{
var accounts = (from a in _context.Account
where a.column4.StartsWith(searchText) && a.FiscalPeriod.Equals(fiscalPeriod) && a.FiscalYear.Equals(fiscalyear)
group a.column4 by a.column4 into g
select g.Key).ToList();
return accounts;
}
else
{
return new List<string>();
}
}
To convert it to generic. I created a expression tree.
static IQueryable<T> ConvertToExpression<T>(IQueryable<T> query, string propertyValue, PropertyInfo propertyInfo, int fiscalyear, int fiscalPeriod)
{
ParameterExpression e = Expression.Parameter(typeof(T), "e");
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(propertyValue, typeof(string));
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
Expression call = Expression.Call(m, mi, c);
PropertyInfo propertyInfoFiscalPeriod = typeof(T).GetProperty("FiscalPeriod");
MemberExpression memberPropertyFiscalPeriod = Expression.Property(e, propertyInfoFiscalPeriod);
ConstantExpression right = Expression.Constant(fiscalPeriod);
Expression equalsFiscalPeriod = Expression.Equal(memberPropertyFiscalPeriod, Expression.Convert(right, typeof(Int16)));
PropertyInfo propertyInfoFiscalYear = typeof(T).GetProperty("FiscalYear");
MemberExpression memberPropertyFiscalYear = Expression.Property(e, propertyInfoFiscalYear);
right = Expression.Constant(fiscalyear);
Expression equalsFiscalYear = Expression.Equal(memberPropertyFiscalYear, Expression.Convert(right, typeof(Int16)));
Expression combineExpression = Expression.And(equalsFiscalPeriod, equalsFiscalYear);
Expression predicateBody = Expression.And(call, combineExpression);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(predicateBody, e);
return query.Where(lambda);
}
And To call it I used code like below
"searchForRole" comes as parameter in as "column1","column2" etc
PropertyInfo propertyInfo = typeof(Account).GetProperty(searchForRole);
IQueryable<Account> query = _context.Account;
query = ConvertToExpression(query, searchText, propertyInfo,fiscalyear,fiscalPeriod);
var list = query.ToList();
Now this is working fine but the result having duplicate records. I wanted to have some distinct or group by on passed parameter column. In Simple words I wanted to remove if condition and make my search method generic. Please help.
It's possible, but IMHO it's better to keep the dynamic parts at minimum and use the C# compile time safety as much as possible.
The sample query in question
var accounts = (from a in _context.Account
where a.column1.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear ==fiscalyear
group a.column1 by a.column1 into g
select g.Key).ToList();
can be rewritten as follows
var accounts = _context.Account
.Where(a => a.FiscalPeriod == fiscalPeriod && a.FiscalYear == fiscalyear)
.Select(a => a.column1)
.Where(c => c.StartsWith(searchText))
.Distinct()
.ToList();
As you can see, the only dynamic part is a => a.column1 of type Expression<Func<Account, string>>. So all you need is a method like this:
static Expression<Func<T, M>> MemberSelector<T>(string name)
{
var parameter = Expression.Parameter(typeof(T), "e");
var body = Expression.PropertyOrField(name);
return Expression.Lambda<Func<T, M>>(body, parameter);
}
and to replace
.Select(a => a.column1)
with
.Select(MemberSelector<Account, string>(searchForRole))

Entity, Contains or intersect, is this query possible?

I have a list of string retreived this way :
List<string> keyWords = db.MotCleRecherche.Select(t => t.MotClé).ToList();
I also have a query that takes many parameters to be executed :
object = db.DAapp.Where(t => t.CODE_ART.StartsWith(s) && t.DATE_CREAT >= debut && t.DATE_CREAT < fin).ToList()
now... I want to add this kind of condition :
db.DAapp.Where(t => t.DESC_ART.ToLower().Contains(keywords.ToLower()))
or
db.DAapp.Where(t => t.DESC_ART.ToLower().Intersect(keywords.ToLower()))
I guess you could see it comming... I can't figure how to really make this work... all i know is considering a list X filed and Y list filled:
X.Intersect(Y).Any()
will return true if there is something equal... but DESC_ART is just ONE long string and i want to know if some of my keywords are in there
I agree with Stephen that you should cast the keyWords to lower first before comparing. But if you really need to do this with linq you can do something like this.
var result = db.DAapp.Where(t => keywords.Any(keyword=> string.Equals(keyword,t.DESC_ART, StringComparison.InvariantCultureIgnoreCase )));
This will cause a to lower to get called on each string every iteration of your linq loop so its expensive.
First add this to your project (for example to your controller):
static Expression<Func<T, bool>> AnyOf<T>(
params Expression<Func<T, bool>>[] expressions)
{
if (expressions == null || expressions.Length == 0) return x => false;
if (expressions.Length == 1) return expressions[0];
var body = expressions[0].Body;
var param = expressions[0].Parameters.Single();
for (int i = 1; i < expressions.Length; i++)
{
var expr = expressions[i];
var swappedParam = new SwapVisitor(expr.Parameters.Single(), param)
.Visit(expr.Body);
body = Expression.OrElse(body, swappedParam);
}
return Expression.Lambda<Func<T, bool>>(body, param);
}
class SwapVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public SwapVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
I find this from stackoverflow. now you can create desired query as below :
var filters = new List<Expression<Func<Models.DAapp, bool>>>();
foreach (var st in keyWords)
filters.Add(d => d.DESC_ART.ToLower().Contains(st.ToLower()));
var lambda = AnyOf(filters.ToArray());
var q = db.DAapp.Where(t =>
t.CODE_ART.StartsWith(s)
&& t.DATE_CREAT >= debut
&& t.DATE_CREAT < fin
);
q = q.Where(lambda);
var res = q.ToList();
Please be noticed that, this solution creates only one select query with multiple where expressions. which is more efficient that other solutions like below that contains multiple select queries inside where clause :
var q = db.DAapp.Where(t =>
t.CODE_ART.StartsWith(s)
&& t.DATE_CREAT >= debut
&& t.DATE_CREAT < fin
&& keyWords.Any(k => t.DESC_ART.ToLower().Contains(k.ToLower()))
);

Create a search method with AND/OR options in Linq and C#

I'm trying to create some methode for searching and filtring data in databese using c# and asp.net mvc 4 (linq)
public ActionResult Search_Names_Using_Location(string b,string d, int c=0,int Id=0)
{
ViewBag.Locations = db.Locations.ToList();
var agentlocation = new AgentLocationViewModel();
agentlocation.agents = new List<Agent>();
agentlocation.agents = (from a in db.Agents
where a.LocationId == Id
&& (a.LocationName == b)
&& (a.age > c )
select a).ToList();
return View(agentlocation);
}
The problem is that user can let some texboxes empty, so the value of Id or a or b can be null so the query will get nothing.
Is their any suggestions to do that (i can go with if else but that's hard if i have 7 or 8 strings)?
You can check for null inside query
public ActionResult Search_Names_Using_Location(string b,string d,
int c=0,int Id=0,)
{
ViewBag.Locations = db.Locations.ToList();
var agentlocation = new AgentLocationViewModel();
agentlocation.agents = new List<Agent>();
var noId = string.IsNullOrWhitespace(Id);
var noB = string.IsNullOrWhitespace(b);
agentlocation.agents = (from a in db.Agents
where (noId || a.LocationId == Id)
&& (noB || a.LocationName == b)
&& (a.age > c )
select a).ToList();
return View(agentlocation);
}
If you have AND conditions only you can use
var query = db.Agents;
if (Id != 0)
{
query = query.Where(x => x.LocationId == Id)
}
if (!string.IsNullOrWhitespace(b))
{
query = query.Where(x => x.LocationName == b)
}
...
var result = query.ToList(); // actual DB call
This will remove useless empty conditions, like WHERE (0 = 0 OR LocationId = 0)
In case of OR conditions and combinations you can take a look at PredicateBuilder
So you can use Or and And predicate combinations like this:
IQueryable<Product> SearchProducts (params string[] keywords)
{
var predicate = PredicateBuilder.False<Product>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or (p => p.Description.Contains (temp));
}
return dataContext.Products.Where (predicate);
}

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();

Categories

Resources