LINQ where condition with dynamic column - c#

I have this code
// IQueryable<General> query
if (columnName == "Column1")
{
query = query.Where(x => x.Column1 == searchValue);
}
else if (columnName == "Column2")
{
query = query.Where(x => x.Column2 == searchValue);
}
else if (columnName == "Column3")
{
query = query.Where(x => x.Column3 == searchValue);
}
else if (columnName == "Column4")
{
query = query.Where(x => x.Column4 == searchValue);
}
// next zilions columns to come
// ...
and my question is. How can i past x.Column as a parameter inside ".Where" condition ?

You can create a predicate manually. Use this method:
public static Expression<Func<General, bool>> CreatePredicate(string columnName, object searchValue)
{
var xType = typeof(General);
var x = Expression.Parameter(xType, "x");
var column = xType.GetProperties().FirstOrDefault(p => p.Name == columnName);
var body = column == null
? (Expression) Expression.Constant(true)
: Expression.Equal(
Expression.PropertyOrField(x, columnName),
Expression.Constant(searchValue));
return Expression.Lambda<Func<General, bool>>(body, x);
}
Now you can apply your predicate:
IQueryable<General> query = //
var predicate = CreatePredicate(columnName , searchValue);
query = query.Where(predicate);

You could use reflection and extension methods. As a rough example:
public class Foo
{
public int Column1 { get; set; }
public int Column2 { get; set; }
...
}
public static class FooExtensions
{
// I would use the actual type here instead of object if you know the type.
public static object GetProperyValue(this Foo foo, string columnName)
{
var propertyInfo = foo.GetType().GetProperty(columnName);
var value = propertyInfo.GetValue(foo);
// as well as cast value to the type
return value;
}
}
...
query = query.Where(x => x.GetProperyValue(columnName) == searchValue);
...
As a side note, that is not a well-designed query because every time you add a column to your model, you'd need to update your if-else. It violates the O in SOLID.

You can either use Reflection to retrieve the property via name
x.GetType().GetProperty(propertyName,BindingFlags).SetValue(x,value)
// propertyName = "Column1" for example
// BindingFlags are most likely Instance, Public and Property (IIRC)
or pass in the PropertyInfo directly into the method as a parameter. Your choice depends on the abstraction level you want to expose to consumers of your method.

Related

LINQ Query to check for a predicate in all columns in a table

I have a table with 30 columns,
and it contains 1000 rows.
I want a single LINQ query, which checks for a particular value in all columns and converts the result into a list.
For example:
table.where(allcolumnvalue.contains(searchvalue)).Tolist()
How to accomplish the above using one LINQ query. Any help is much appreciated.
For your request all of fields should have same type, at least in the static typed C#.
The method Queriable.Where gets the Expression<Func<T, bool>> predicate as parameter. So you need build the predicate o.p1 == val || o.p2 == val || o.p3 = val ... as Expression value. Here o is a parameter of Expression<Func<T, bool>>:
public Expression BuildExpression<TObj, TVal>(TObj obj, TVal val)
{
Expression<Func<TObj, bool>> predicate = (o) => o.p1 == val || ... || o.pN == val;
return predicate;
}
but we need build predicate dynamically for all properties of TObj that have type TVal.
To simplify the code we will build equal expression false || o.p1 == val || ... || o.pN == val.
public Expression<Func<TObj, bool>> BuildExpression<TObj, TVal>(TVal val)
{
var parameter = Expression.Parameter(typeof(TObj), "o");
var valExpression = Expression.Constant(val, typeof(TVal));
var body = Expression.Constant(false, typeof(bool));
var properties = typeof(TObj).GetProperties()
.Where(p => p.PropertyType == typeof(TVal));
foreach (var property in properties)
{
var propertyExpression = Expression.Property(parameter, property);
var equalExpression = Expression.Equal(propertyExpression, valExpression);
body = Expression.Or(body, equalExpression);
}
return Expression.Lambda<Func<TObj, bool>>(body, parameter);
}
. . .
using (var dbContext = new DbContext())
{
var whereExpression = BuildExpression<User, string>("foo");
var contaningsFoo = dbContext.Users.Where(whereExpression);
}
I got answer But Is not perfect answer But Is Worked well
public class GenericList<T>
{
void Add(T input) { }
public List<T> SerachFun(List<T> input, string search)
{
List<T> output = new System.Collections.Generic.List<T>();
foreach (var aa in input)
{
var columns = aa.GetType().GetProperties().ToList();
foreach (var bb in columns)
{
var cccc = bb.GetValue(aa);
bool result = cccc.ToString().Contains(search);
if (result)
{
output.Add(aa);
continue;
}
}
}
return output;
}
}
The Generic Class Object Created
public GenericList<table1> g = new GenericList<table1>();
the Generic Class Method Called :
var tabledetails=db.table1.ToList();
var resultcommonsearch = g.SerachFun(tabledetails, "Dhoni");
using code
public class GenericList<T>
{
public List<T> SerachFun(List<T> input, string search)
{
List<T> output = new System.Collections.Generic.List<T>();
foreach (var aa in input)
{
var columns = aa.GetType().GetProperties().ToList();
foreach (var bb in columns)
{
var cccc = bb.GetValue(aa);
if(cccc!=null)
{
bool result = cccc.ToString().Contains(search);
if (result)
{
output.Add(aa);
continue;
}
}
}
}
return output;
}
}
Try call method
public GenericList<table1> g = new GenericList<table1>();
var tabledetails=db.table1.ToList();
var resultcommonsearch = g.SerachFun(tabledetails, "Dhoni");

Error with an EF query with nested predicates - variable of type X referenced from scope but it is not defined

I'm trying to construct an expression that ultimately results in a query like
SELECT p.*
FROM MyEntity p
WHERE EXISTS(SELECT *
FROM filters
WHERE (filter.type = 1
AND filter.objectid = p.id
AND filter.value = 1
OR filter.type = 1
AND filter.objectid = p.id
AND filter.value = 2))
AND EXISTS(...)
Obviously it won't look exactly like that, but that's the general idea.
I'm using PredicateBuilder to build the query based on the filters passed in, so I have something like this:
var query = context.Set<MyEntity>().AsExpandable();
var predicate = PredicateBuilder.New<MyEntity>(true);
//loop through the group filters. The filters in a group have an or relationship
foreach (FilterGroup group in filters)
{
predicate = predicate.And(
p => context.Set<FilteringValue>().AsExpandable().Any(getFilteringPredicate(p,group ))
);
}
return query.Where(predicate);
And the getFilteringPredicateMethod:
Expression<Func<FilteringValue,bool>> getFilteringPredicate(MyEntity p, FilterGroup filters) {
var fPredicate = PredicateBuilder.New<FilteringValue>(true);
foreach(var filter in filters.FilterList)
{
fPredicate= fPredicate.Or(fv => fv.objectid == p.Id && fv.Type== 1 && fv.value == filter.Value);
}
return fPredicate
}
This seems relatively simple, however I'm getting the error
variable 'p' of type 'Models.MyEntity' referenced from scope '', but it is not defined.
Is there no way to pass the product object into the getFilteringPredicate() method? MyEntity and Filter are not related in Entity Framework.
So... I think I finally got it, you want to relate two expression parameters and build up a composite query (what I mean by the informal definition of "composite" is a subquery having a reference to the main query parameter(s)):
Unfortunately, LinqKit does not support multi-parameter expressions 'AFAIK', which is something that would be a perfect match for your case:
Well, anyway... here it goes. By the way FilteringValues and MyEntities are just two DbSets, I just happen to be using LinqPad to test this out ATM (Questions?):
void Main(string[] args)
{
var entityQuery = MyEntities.AsExpandable();
var filterGroups = GetFilterGroups();
// Initialize with TRUE since no group filter implies Everything matches
var predicate = PredicateBuilder.New<MyEntity>(true);
var filteringValueQuery = FilteringValues.AsExpandable();
foreach (var g in filterGroups)
{
if (!g.FilterList.Any())
{
// If we have no filters in the group, skip
continue;
}
var expressionForGroupFilters = BuildExpressionForGroupFilters(g.FilterList);
predicate = predicate.And(entity => filteringValueQuery.Any(filteringValue => expressionForGroupFilters.Invoke(entity, filteringValue)));
}
entityQuery = entityQuery.Where(predicate);
var data = entityQuery.ToList();
data.Dump();
}
public static Expression<Func<MyEntity, FilteringValue, bool>> BuildExpressionForSingleFilter(Filter groupFilter)
{
var value = groupFilter.Value;
return (entity, filteringValue) =>
filteringValue.Type == 1
&& filteringValue.ObjectId == entity.Id
&& filteringValue.Value == value;
}
public static Expression<Func<MyEntity, FilteringValue, bool>> BuildExpressionForGroupFilters(IReadOnlyCollection<Filter> groupFilters)
{
Expression<Func<MyEntity, FilteringValue, bool>> result = null;
foreach (var groupFilter in groupFilters)
{
var expression = BuildExpressionForSingleFilter(groupFilter);
if (result == null)
{
result = expression;
continue;
}
var tempResult = result.Expand();
result = (entity, filteringValue) => tempResult.Invoke(entity, filteringValue) || expression.Invoke(entity, filteringValue);
}
return result.Expand();
}
public static FilterGroup CreateFilterGroupWithValues(params int[] values)
{
var filterList = values
.Select(x => new Filter { Value = x })
.ToList();
return new FilterGroup { FilterList = filterList };
}
public static IEnumerable<FilterGroup> GetFilterGroups()
{
return new[] {CreateFilterGroupWithValues(0, 2, 4), CreateFilterGroupWithValues(1)};
}
public class Filter
{
public int Value { get; set; }
}
public class FilterGroup
{
public FilterGroup()
{
FilterList = new List<Filter>();
}
public List<Filter> FilterList { get; set; }
}

Pass orderBy or OrderByDescending as parameter

I have method like this :
GetUsallyOpeningClosingHour(Func<OpeningDay, TimeSpan> groupByRule)
{
var openingClosingHours = listOfSpecificDayOfWeek.GroupBy(groupByRule).OrderByDescending(x => x.Key);
}
and the problem is that I can't stick all the time with OrderByDescending depends on groupByRule parameter sometimes it has to be orderByDescending or OrderBy
I don't want to depend on this parameter, so I could pass another one for that,
Right now I call my method this way:
GetUsallyOpeningClosingHour(x => x.From)
or
GetUsallyOpeningClosingHour(x => x.To)
How can I pass orderBy type as well ?
The simplest way is adding a parameter, which will specify an order in your collection.
public void GetUsallyOpeningClosingHour(
Func<OpeningDay, TimeSpan> groupByRule,
bool orderByDesc = false)
{
var groupedDays = listOfSpecificDayOfWeek.GroupBy(groupByRule);
var openingClosingHours =
orderByDesc
? groupedDays.OrderByDescending(x => x.Key)
: groupedDays.OrderBy(x => x.Key);
}
It could be a boolean or custom enum (I prefer enum, because it actually specifies a kind of ordering operation, while boolean specifies whether collection should be ordered by desc or not).
public enum OrderingType
{
Ascending,
Descending,
None
}
Or you could provide an additional Func, which will perform an ordering operation. But its signature will be awkward.
public static void GetUsallyOpeningClosingHour(
Func<OpeningDay, TimeSpan> groupByRule,
Func<IEnumerable<IGrouping<TimeSpan, OpeningDay>>,
IEnumerable<IGrouping<TimeSpan, OpeningDay>>> orderBy)
{
var groupedDays = listOfSpecificDayOfWeek.GroupBy(groupByRule);
var openingClosingHours = orderBy(groupedDays);
}
I guess you could create your own OrderBy extension that let you select ascending/descending based on a parameter.
Something like this:
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
bool descending
)
{
return descending ? source.OrderByDescending(keySelector)
: source.OrderBy(keySelector);
}
You can also use an enum instead of the boolean to make things more readable when calling this method.
This is the most direct way to parameterise for OrderBy and OrderByDescending. Fortunately the type can be inferred for you by Visual Studio; unfortunately the type is long to write out. I added the static void and the initializer for listOfSpecificDayOfWeek so that this is easy to paste into a program for testing.
static void GetUsallyOpeningClosingHour(
Func<OpeningDay, TimeSpan> groupByRule,
Func<IEnumerable<IGrouping<TimeSpan, OpeningDay>>,
Func<IGrouping<TimeSpan, OpeningDay>, TimeSpan>,
IOrderedEnumerable<IGrouping<TimeSpan, OpeningDay>>> order)
{
IEnumerable<OpeningDay> listOfSpecificDayOfWeek = null;
var openingClosingHours = order(listOfSpecificDayOfWeek.GroupBy(groupByRule), x => x.Key);
}
You can call this function like this:
GetUsallyOpeningClosingHour(x => x.From, Enumerable.OrderByDescending);
GetUsallyOpeningClosingHour(x => x.From, Enumerable.OrderBy);
As other answers indicate, you can also just use a boolean flag to indicate ascending or descending order.
You would have to pass in a parameter as there's no way for the method to know which direction you want to sort in based only on the parameter (eg. From/To).
public [return-type] GetUsallyOpeningClosingHour(Func<OpeningDay, TimeSpan> groupByRule, bool isAscending)
{
var openingClosingHours = listOfSpecificDayOfWeek.GroupBy(groupByRule);
if (isAscending)
{
openingClosingHours = openingClosingHours.OrderBy(x => x.Key);
}
else
{
openingClosingHours = openingClosingHours.OrderByDescending(x => x.Key);
}
// Return openingClosingHours? It's not clear how you're using this variable.
}
public List<Book> Books(string orderField, bool desc, int skip, int take)
{
var propertyInfo = typeof(Book).GetProperty(orderField);
return _context.Books
.Where(...)
.OrderBy(p => !desc ? propertyInfo.GetValue(p, null) : 0)
.ThenByDescending(p => desc ? propertyInfo.GetValue(p, null) : 0)
.Skip(skip)
.Take(take)
.ToList();
}
this is my code sample:
public IQueryable<T> GetAllbySearch(
int pageNumber = 1, int pageSize = 10,
Dictionary<string, dynamic> filterParams = null,
Func<IQueryable<T>, IIncludableQueryable<T, object>> include = null,
bool allIncluded = false
, Func<IQueryable<T>, IOrderedQueryable<T>> order = null)
{
var query = _entity.AsQueryable();
if (include != null && !allIncluded)
{
query = include(query);
}
if (allIncluded && include == null)
{
foreach (var property in _context.Model.FindEntityType(typeof(T)).GetNavigations()
.Where(r => !r.IsCollection()))
query = query.Include(property.Name);
}
if (filterParams != null && filterParams.Any())
{
if (filterParams.Any(r => r.Value != null))
{
var expression = GetSearchFilter(filterParams);
if (order != null)
{
return order(query.Where(expression));
}
else
{
return query.Where(expression));
}
}
}
if (order != null)
{
return order(query);
}
else
{
return query;
}
}

How to sort List<T> based on column provided as input [duplicate]

I have the name of the "sort by property" in a string. I will need to use Lambda/Linq to sort the list of objects.
Ex:
public class Employee
{
public string FirstName {set; get;}
public string LastName {set; get;}
public DateTime DOB {set; get;}
}
public void Sort(ref List<Employee> list, string sortBy, string sortDirection)
{
//Example data:
//sortBy = "FirstName"
//sortDirection = "ASC" or "DESC"
if (sortBy == "FirstName")
{
list = list.OrderBy(x => x.FirstName).toList();
}
}
Instead of using a bunch of ifs to check the fieldname (sortBy), is there a cleaner way of doing the sorting
Is sort aware of datatype?
This can be done as
list.Sort( (emp1,emp2)=>emp1.FirstName.CompareTo(emp2.FirstName) );
The .NET framework is casting the lambda (emp1,emp2)=>int as a Comparer<Employee>.
This has the advantage of being strongly typed.
If you need the descending/reverse order invert the parameters.
list.Sort( (emp1,emp2)=>emp2.FirstName.CompareTo(emp1.FirstName) );
One thing you could do is change Sort so it makes better use of lambdas.
public enum SortDirection { Ascending, Descending }
public void Sort<TKey>(ref List<Employee> list,
Func<Employee, TKey> sorter, SortDirection direction)
{
if (direction == SortDirection.Ascending)
list = list.OrderBy(sorter);
else
list = list.OrderByDescending(sorter);
}
Now you can specify the field to sort when calling the Sort method.
Sort(ref employees, e => e.DOB, SortDirection.Descending);
You could use Reflection to get the value of the property.
list = list.OrderBy( x => TypeHelper.GetPropertyValue( x, sortBy ) )
.ToList();
Where TypeHelper has a static method like:
public static class TypeHelper
{
public static object GetPropertyValue( object obj, string name )
{
return obj == null ? null : obj.GetType()
.GetProperty( name )
.GetValue( obj, null );
}
}
You might also want to look at Dynamic LINQ from the VS2008 Samples library. You could use the IEnumerable extension to cast the List as an IQueryable and then use the Dynamic link OrderBy extension.
list = list.AsQueryable().OrderBy( sortBy + " " + sortDirection );
This is how I solved my problem:
List<User> list = GetAllUsers(); //Private Method
if (!sortAscending)
{
list = list
.OrderBy(r => r.GetType().GetProperty(sortBy).GetValue(r,null))
.ToList();
}
else
{
list = list
.OrderByDescending(r => r.GetType().GetProperty(sortBy).GetValue(r,null))
.ToList();
}
Building the order by expression can be read here
Shamelessly stolen from the page in link:
// First we define the parameter that we are going to use
// in our OrderBy clause. This is the same as "(person =>"
// in the example above.
var param = Expression.Parameter(typeof(Person), "person");
// Now we'll make our lambda function that returns the
// "DateOfBirth" property by it's name.
var mySortExpression = Expression.Lambda<Func<Person, object>>(Expression.Property(param, "DateOfBirth"), param);
// Now I can sort my people list.
Person[] sortedPeople = people.OrderBy(mySortExpression).ToArray();
You could use reflection to access the property.
public List<Employee> Sort(List<Employee> list, String sortBy, String sortDirection)
{
PropertyInfo property = list.GetType().GetGenericArguments()[0].
GetType().GetProperty(sortBy);
if (sortDirection == "ASC")
{
return list.OrderBy(e => property.GetValue(e, null));
}
if (sortDirection == "DESC")
{
return list.OrderByDescending(e => property.GetValue(e, null));
}
else
{
throw new ArgumentOutOfRangeException();
}
}
Notes
Why do you pass the list by reference?
You should use a enum for the sort direction.
You could get a much cleaner solution if you would pass a lambda expression
specifying the property to sort by instead of the property name as a string.
In my example list == null will cause a NullReferenceException, you should catch this case.
Sort uses the IComparable interface, if the type implements it.
And you can avoid the ifs by implementing a custom IComparer:
class EmpComp : IComparer<Employee>
{
string fieldName;
public EmpComp(string fieldName)
{
this.fieldName = fieldName;
}
public int Compare(Employee x, Employee y)
{
// compare x.fieldName and y.fieldName
}
}
and then
list.Sort(new EmpComp(sortBy));
Answer for 1.:
You should be able to manually build an expression tree that can be passed into OrderBy using the name as a string. Or you could use reflection as suggested in another answer, which might be less work.
Edit: Here is a working example of building an expression tree manually. (Sorting on X.Value, when only knowing the name "Value" of the property). You could (should) build a generic method for doing it.
using System;
using System.Linq;
using System.Linq.Expressions;
class Program
{
private static readonly Random rand = new Random();
static void Main(string[] args)
{
var randX = from n in Enumerable.Range(0, 100)
select new X { Value = rand.Next(1000) };
ParameterExpression pe = Expression.Parameter(typeof(X), "value");
var expression = Expression.Property(pe, "Value");
var exp = Expression.Lambda<Func<X, int>>(expression, pe).Compile();
foreach (var n in randX.OrderBy(exp))
Console.WriteLine(n.Value);
}
public class X
{
public int Value { get; set; }
}
}
Building an expression tree requires you to know the particpating types, however. That might or might not be a problem in your usage scenario. If you don't know what type you should be sorting on, it will propably be easier using reflection.
Answer for 2.:
Yes, since Comparer<T>.Default will be used for the comparison, if you do not explicitly define the comparer.
The solution provided by Rashack does not work for value types (int, enums, etc.) unfortunately.
For it to work with any type of property, this is the solution I found:
public static Expression<Func<T, object>> GetLambdaExpressionFor<T>(this string sortColumn)
{
var type = typeof(T);
var parameterExpression = Expression.Parameter(type, "x");
var body = Expression.PropertyOrField(parameterExpression, sortColumn);
var convertedBody = Expression.MakeUnary(ExpressionType.Convert, body, typeof(object));
var expression = Expression.Lambda<Func<T, object>>(convertedBody, new[] { parameterExpression });
return expression;
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;
public static class EnumerableHelper
{
static MethodInfo orderBy = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderBy" && x.GetParameters().Length == 2).First();
public static IEnumerable<TSource> OrderBy<TSource>(this IEnumerable<TSource> source, string propertyName)
{
var pi = typeof(TSource).GetProperty(propertyName, BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance);
var selectorParam = Expression.Parameter(typeof(TSource), "keySelector");
var sourceParam = Expression.Parameter(typeof(IEnumerable<TSource>), "source");
return
Expression.Lambda<Func<IEnumerable<TSource>, IOrderedEnumerable<TSource>>>
(
Expression.Call
(
orderBy.MakeGenericMethod(typeof(TSource), pi.PropertyType),
sourceParam,
Expression.Lambda
(
typeof(Func<,>).MakeGenericType(typeof(TSource), pi.PropertyType),
Expression.Property(selectorParam, pi),
selectorParam
)
),
sourceParam
)
.Compile()(source);
}
public static IEnumerable<TSource> OrderBy<TSource>(this IEnumerable<TSource> source, string propertyName, bool ascending)
{
return ascending ? source.OrderBy(propertyName) : source.OrderBy(propertyName).Reverse();
}
}
Another one, this time for any IQueryable:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
public static class IQueryableHelper
{
static MethodInfo orderBy = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderBy" && x.GetParameters().Length == 2).First();
static MethodInfo orderByDescending = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderByDescending" && x.GetParameters().Length == 2).First();
public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, params string[] sortDescriptors)
{
return sortDescriptors.Length > 0 ? source.OrderBy(sortDescriptors, 0) : source;
}
static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string[] sortDescriptors, int index)
{
if (index < sortDescriptors.Length - 1) source = source.OrderBy(sortDescriptors, index + 1);
string[] splitted = sortDescriptors[index].Split(' ');
var pi = typeof(TSource).GetProperty(splitted[0], BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.IgnoreCase);
var selectorParam = Expression.Parameter(typeof(TSource), "keySelector");
return source.Provider.CreateQuery<TSource>(Expression.Call((splitted.Length > 1 && string.Compare(splitted[1], "desc", StringComparison.Ordinal) == 0 ? orderByDescending : orderBy).MakeGenericMethod(typeof(TSource), pi.PropertyType), source.Expression, Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(TSource), pi.PropertyType), Expression.Property(selectorParam, pi), selectorParam)));
}
}
You can pass multiple sort criteria, like this:
var q = dc.Felhasznalos.OrderBy(new string[] { "Email", "FelhasznaloID desc" });
Adding to what #Samuel and #bluish did. This is much shorter as the Enum was unnecessary in this case. Plus as an added bonus when the Ascending is the desired result, you can pass only 2 parameters instead of 3 since true is the default answer to the third parameter.
public void Sort<TKey>(ref List<Person> list, Func<Person, TKey> sorter, bool isAscending = true)
{
list = isAscending ? list.OrderBy(sorter) : list.OrderByDescending(sorter);
}
If you get sort column name and sort direction as string and don't want to use switch or if\else syntax to determine column, then this example may be interesting for you:
private readonly Dictionary<string, Expression<Func<IuInternetUsers, object>>> _sortColumns =
new Dictionary<string, Expression<Func<IuInternetUsers, object>>>()
{
{ nameof(ContactSearchItem.Id), c => c.Id },
{ nameof(ContactSearchItem.FirstName), c => c.FirstName },
{ nameof(ContactSearchItem.LastName), c => c.LastName },
{ nameof(ContactSearchItem.Organization), c => c.Company.Company },
{ nameof(ContactSearchItem.CustomerCode), c => c.Company.Code },
{ nameof(ContactSearchItem.Country), c => c.CountryNavigation.Code },
{ nameof(ContactSearchItem.City), c => c.City },
{ nameof(ContactSearchItem.ModifiedDate), c => c.ModifiedDate },
};
private IQueryable<IuInternetUsers> SetUpSort(IQueryable<IuInternetUsers> contacts, string sort, string sortDir)
{
if (string.IsNullOrEmpty(sort))
{
sort = nameof(ContactSearchItem.Id);
}
_sortColumns.TryGetValue(sort, out var sortColumn);
if (sortColumn == null)
{
sortColumn = c => c.Id;
}
if (string.IsNullOrEmpty(sortDir) || sortDir == SortDirections.AscendingSort)
{
contacts = contacts.OrderBy(sortColumn);
}
else
{
contacts = contacts.OrderByDescending(sortColumn);
}
return contacts;
}
Solution based on using Dictionary that connects needed for sort column via Expression> and its key string.

Using reflection to obtain values based on a specific query condition

I have a slightly different issue to the one answered here (Using reflection to retrieve a value from a list)
While the approved answer here works fine for select, I'd like to extend it so that I can obtain data from a query based on a condition type. Currently, my adapted code looks like this
public static async Task<T> GetDataFromTable<T>(string paramName, string condition="")
{
var k = Activator.CreateInstance(typeof(T));
var mn = typeof(T).GetProperty(paramName);
var tc = typeof(T).GetProperty(condition);
if (mn == null || !ftrackData.Online)
return (T)k;
var data = GetTableData<T>();
if (!string.IsNullOrEmpty(paramName))
{
var retval = data.Select(t => mn.GetValue(t, null));
return (T)retval;
}
else
return (T)data.FirstOrDefault(t => mn.GetValue(t, null) > tc.GetType(t, null)).ToList();
}
I'd be looking to have the ">" in the final return change depending on an additional parameter passed into the arguments list. I know I can do a simple switch after the else, but is there some way to change the condition by insertion?
Your code doesn't really make any sense. The Select extension method takes a Func<T, TResult> which implies the return type should be IEnumerable<TResult> whereas you specify it as T.
In your question you want to do a Select and FirstOrDefault in the same method, but that is not possible since the result types will be different.
Select:
public static async Task<IEnumerable<TResult>> SelectData<T, TResult>(
string propertyName
)
{
if(string.IsNullOrWhiteSpace(propertyName))
{
return Enumerable.Empty<TResult>();
}
var dataTask = GetTableData<T>();
var tType = Expression.Parameter(typeof(T), "t");
var property = Expression.Property(tType, propertyName);
var selectExpression =
Expression.Lambda<Func<T, TResult>>(property, tType)
.Compile();
return (await dataTask).Select(selectExpression);
}
FirstOrDefault:
public static async Task<T> FirstOrDefaultData<T>(
string propertyName,
string conditionName,
Func<MemberExpression, MemberExpression, BinaryExpression> comparer
)
{
if(string.IsNullOrWhiteSpace(propertyName) ||
string.IsNullOrWhileSpace(conditionName) ||
comparer == null
{
return default(T);
}
var dataTask = GetTableData<T>();
var tType = Expression.Parameter(typeof(T), "t");
var property = Expression.Property(tType, propertyName);
var condition = Expression.Property(tType, conditionName);
var binaryExpression =
Expression.Lambda<Func<T, bool>>(comparer(property, condition), tType)
.Compile();
return (await dataTask).FirstOrDefault(binaryExpression);
}
Usage:
public class Foo
{
public string Bar { get; set; }
public bool Flag { get; set; }
}
var bars = SelectData<Foo, string>("Bar");
var foo = FirstOrDefaultData<Foo>("Bar",
"Flag",
(p, c) => Expression.GreaterThan(p, c));

Categories

Resources