Resolving generic lambda expression type - c#

I have a method that calls Entity Framework functions. It is responsible for sending the "select clause", "order by clause" and possible includes (considering that I'm using lazy loading). The method looks like this:
public IEnumerable<TReturn> GetAll<TReturn, TOrderKey>(
Expression<Func<TEntity, TReturn>> selectExp,
Expression<Func<TEntity, TOrderKey>> orderbyExp,
Boolean descending,
params Expression<Func<TEntity, Object>>[] includeExps)
{
var query = DbSet.AsQueryable();
query = !descending ? query.OrderBy(orderByExp) : query.OrderByDescending(orderByExp);
if (includeExps != null)
query = includeExps.Aggregate(query, (current, exp) => current.Include(exp));
return query.Select(selectExp).ToList();
}
When I call:
_service.GetAll(i => new { i.Name}, i => i.Name, false, null);
It works fine! That is the generated SQL is the exactly as I wanted.
However, considering a real scenario (in my case I'm using asp.net mvc), I have an Action method that gets the order parameter from the client.
That is the method:
public JsonResult GetAllUsers(string sortColumn, bool sortDescending)
{
//sortColumn string must be translated in a Expression
var users = _service.GetAll(i => new { i.Name, i.Email }, i => i.Name, sortDescending, null);
//
//
}
My first attempt was to create an expression for every column, like this:
public JsonResult GetAllUsers(string sortColumn, bool sortDescending)
{
//I don't what is the Type that I should put here
//It can be anything, like: Expression<Func<User, String>>,
//Expression<Func<User, Guid>>, Expression<Func<User, Int>>
?Type? orderExp;
switch(sortColumn)
{
case "UserId":
//Expression<Func<User, Guid>>
orderExp = i => i.UserId;
break;
case "Name":
//Expression<Func<User, String>>
orderExp = i => i.Email;
break;
}
//sortColumn string must be translated in a Expression
var users = _service.GetAll(i => new { i.Name, i.Email }, orderExp, sortDescending, null);
//
//
}
I want to create an expression based on sortProperty, there's a lot of information about it over the stackoverflow, however (see the action method) the variable must be typed before the process. The GetAll method can't be called inside every "case" because it returns an anonymous type.
I can't convert all columns to Expression<Func<User, Object>> because entity framework does not support it.
Linq.Dynamic should help, but I don't want to use string parameters.

You could overload your GetAll method as follows:
public IEnumerable<TReturn> GetAll<TReturn>(
Expression<Func<TEntity, TReturn>> selectExp,
string orderColumnName,
Boolean descending,
params Expression<Func<TEntity, Object>>[] includeExps)
{
var entityType = typeof(TEntity);
var prop = entityType.GetProperty(orderColumnName);
var param = Expression.Parameter(entityType, "i");
var orderExp = Expression.Lambda(
Expression.MakeMemberAccess(param, prop), param);
// get the original GetAll method overload
var method = this.GetType().GetMethods().Where(m => m.Name == "GetAll" && m.GetGenericArguments().Length == 2);
var actualMethod = method.First().MakeGenericMethod(typeof(TReturn), prop.PropertyType);
return (IEnumerable<TReturn>)actualMethod.Invoke(this, new object[] { selectExp, orderExp, descending, includeExps });
}
Just add a few more checks for null values, otherwise invalid sortColumn names will come back haunting you.
Use it similar to your current approach:
_service.GetAll(i => new { i.Name, i.Email }, sortColumn, sortDescending, null);

Related

.Net Core 5 Rest Api Entity Framework Linq Expression Translation Error While Getting Record List From Controller

I am working on a project and I got an error while getting queried list. I will tell on code block below.
//AT REPOSITORY SIDE THIS FUNCTION GETS FILTERED LIST OF RECORDS
public List<TEntity> GetList(Expression<Func<TEntity, bool>> filter)
{
using (var context=new MyContext()){
return context.Set<TEntity>().Where(filter).ToList();
}
}
//AT ENTITY SIDE THIS FUNCTION CHECKS IF ALL PROPERTIES ARE EQUAL WITH THE GIVEN OBJECT
public bool PublicPropertiesEqual(object which)
{
var type = this.GetType();
var whichType = which.GetType();
var whichProperties = whichType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if(whichProperties.Any(x => x.Name == pi.Name)){
object selfValue = type.GetProperty(pi.Name).GetValue(this, null);
object whichValue = type.GetProperty(pi.Name).GetValue(which,null);
if (selfValue != whichValue && (selfValue == null || !selfValue.Equals(whichValue)))
{
return false;
}
}
}
return true;
}
//AT CONTROLLER SIDE ONLY CALLED REPO GETLIST FUNCTION WITH GIVEN FILTER
[HttpGet]
public IActionResult GetList([FromQuery] TEntity query)
{
List<TEntity> res = _repo.GetList(x = x.PublicPropertiesEqual(query));
return Ok(res);
}
PROBLEM
When I execute the code I get an error like that
InvalidOperationException: The LINQ expression 'DbSet()
.Where(c => c.PublicPropertiesEqual(__query_0))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
So I cant get records from database. I tried to write custom expression tree showed below at the controller side and it also doesn't work.
[HttpGet]
public IActionResult GetList([FromQuery] TEntity query)
{
var param = Expression.Parameter(typeof(TEntity));
var lambda = Expression.Lambda<Func<TEntity,bool>>(
Expression.Call(
param,
_entityType.GetMethod("PublicPropertiesEqual"),
Expression.Constant(query)
),
param
);
List<TEntity> res = _repo.GetList(lambda);
return Ok(res);
}
And the error after executed this code
InvalidOperationException: The LINQ expression 'DbSet()
.Where(c => c.PublicPropertiesEqual(Customer))' could not be translated. REST OF THE ERROR IS SAME ABOVE...
As a conclusion, how can I filter the query with using PublicPropertiesEqual(object) function ?
It is easy to write such function, but you have to filter out navigation properties by yourself:
public static Expression<Func<T, bool>> PublicPropertiesEqual<T>(T entity)
{
var props = typeof(T).GetProperties();
var entityParam = Expression.Parameter(typeof(T), "e");
var entityExpr = Expression.Constant(entity);
var equality = props.Select(p => Expression.Equal(
Expression.MakeMemberAccess(entityParam, p),
Expression.MakeMemberAccess(entityExpr, p)
));
var predicate = equality.Aggregate(Expression.AndAlso);
return Expression.Lambda<Func<T, bool>>(predicate, entityParam);
}
Usage is simple:
List<TEntity> res = _repo.GetList(PublicPropertiesEqual(query));
Anyway if you have access to DbContext better to pass it to the function and reuse IModel information.
public static Expression<Func<T, bool>> PublicPropertiesEqual<T>(DbContext ctx, T entity)
{
var et = ctx.Model.FindEntityType(typeof(T));
if (et == null)
throw new InvalidOperationException();
var props = et.GetProperties();
var entityParam = Expression.Parameter(typeof(T), "e");
var entityExpr = Expression.Constant(entity);
var equality = props
.Where(p => !p.IsForeignKey() && !p.IsIndexerProperty())
.Select(p => Expression.Equal(
Expression.MakeMemberAccess(entityParam, p.PropertyInfo),
Expression.MakeMemberAccess(entityExpr, p.PropertyInfo)
));
var predicate = equality.Aggregate(Expression.AndAlso);
return Expression.Lambda<Func<T, bool>>(predicate, entityParam);
}

How do I create a lambda expression using return value from another lambda expression? [duplicate]

This question already has an answer here:
Convert Linq expression "obj => obj.Prop" into "parent => parent.obj.Prop"
(1 answer)
Closed 4 years ago.
Neither the question nor the answer is the same as the question "Convert Linq expression “obj => obj.Prop” into “parent => parent.obj.Prop”". The only duplicate I've found on here lately is the number of duplicate mods with control issues.
I'm attempting to make a extension method for Entity Framework that will add a "Contains" call on a field if the given value is not null or whitespace. It's out of pure laziness of not wanting all the if statement checks for null or whitespace.
I want to be able to use it like this:
var qry = MyDb.Redacteds.OrderBy(a=>a.RedactedDate);
qry = qry.WhereContains(a => a.RedactedName, txtRedactedName.Text);
I've come up with this, but obviously the Invoke causes an issue with EF. What's the trick to using the result of a lambda when building an expression?
// create
// ent => exp(ent).Contains(s)
public static IQueryable<T> WhereContains<T>(this IQueryable<T> qry, Expression<Func<T, string>> exp, string s)
{
if (!string.IsNullOrWhiteSpace(s))
{
s = s.Trim();
var param = Expression.Parameter(typeof(T), "ent");;
var call = Expression.Invoke(exp, param); // <-= HERE
var body = Expression.Call(call, typeof(string).GetMethod("Contains", new[] { typeof(string) }), Expression.Constant(s));
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
qry = qry.Where(lambda);
}
return qry;
}
If it was simply a delegate instead of a lambda, the return could simple be:
ent => exp(ent).Contains(s)
That's what I'm looking to do.
You want to grab the parameter off the lambda and use the body as the expression. You aren't using it as a lambda at all - you are picking the expression tree and parameters out of the lambda.
// create
// ent => exp(ent).Contains(s)
public static IQueryable<T> WhereContains<T>(this IQueryable<T> qry, Expression<Func<T, string>> exp, string s)
{
if (!string.IsNullOrWhiteSpace(s))
{
s = s.Trim();
//HERE GRAB THE PARAMETER
var param = exp.Parameters[0];
//HERE JUST USE EXP.BODY
var body = Expression.Call(exp.Body, typeof(string).GetMethod("Contains", new[] { typeof(string) }), Expression.Constant(s));
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
qry = qry.Where(lambda);
}
return qry;
}

Execution-Deferred IQueryable<T> from Dynamic Linq?

I am using Dynamic Linq to perform some queries (sorry but it's my only option). As a result, I am getting an IQueryable instead of an IQueryable<T>. In my case, I want an IQueryable<Thing> where Thing is a concrete type.
My query is as such:
public IQueryable<Thing> Foo(MyContext db)
{
var rootQuery = db.People.Where(x => x.City != null && x.State != null);
var groupedQuery = rootQuery.GroupBy("new ( it.City, it.State )", "it", new []{"City", "State"});
var finalLogicalQuery = groupedQuery.Select("new ( Count() as TotalNumber, Key.City as City, Key.State as State )");
var executionDeferredResults = finalLogicalQuery.Take(10); // IQueryable
IQueryable<Thing> executionDeferredTypedThings = ??; // <--- Help here!!!!
return executionDeferredTypedThings;
}
My Thing.cs:
public class Thing
{
public int TotalNumber { get; set; }
public string City { get; set; }
public string State { get; set; }
}
Yes, I know the exact above thing can be done without Dynamic Linq but I have some variableness going on that I've simplified out of here. I can get it to work with my variableness if my return type is simply IQueryable but I can't figure out how to convert to IQueryable<Thing> while keeping it execution-deferred and while also keeping Entity Framework happy. I do have the dynamic Select always returning something (with the correct data) that looks like a Thing. But I simply can't figure how to return the IQueryable<Thing> and could use some help there. Thanks!!
Failed Attempt 1
Based on Rex M's suggestion, I am now trying to use AutoMapper to solve this problem (although I am not committed to this approach and am willing to try other approaches). For the AutoMapper approach, I am doing it as such:
IQueryable<Thing> executionDeferredTypedThings = executionDeferredResults.ProjectTo<Thing>(); // <--- Help here!!!!
But this results in an InvalidOperationException:
Missing map from DynamicClass2 to Thing. Create using Mapper.CreateMap.
The thing is, while I have defined Thing, I have not defined DynamicClass2 and as such, I cannot map it.
Failed Attempt 2
IQueryable<Thing> executionDeferredTypedThings = db.People.Provider.CreateQuery<Thing>(executionDeferredResults.Expression);
This gives an InvalidCastException and seems to be the same underlying problem that the above AutoMapper fail hits:
Unable to cast object of type 'System.Data.Entity.Infrastructure.DbQuery'1[DynamicClass2]' to type 'System.Linq.IQueryable'1[MyDtos.Thing]'.
You can use AutoMapper's Queryable Extensions to produce an IQueryable which wraps the underlying IQueryable, thus preserving the original IQueryable's IQueryProvider and the deferred execution, but adds in a mapping/translating component to the pipeline to convert from one type to another.
There's also AutoMapper's UseAsDataSource which makes some common query extension scenarios easier.
If I understand correctly, the following extension method should do the job for you
public static class DynamicQueryableEx
{
public static IQueryable<TResult> Select<TResult>(this IQueryable source, string selector, params object[] values)
{
if (source == null) throw new ArgumentNullException("source");
if (selector == null) throw new ArgumentNullException("selector");
var dynamicLambda = System.Linq.Dynamic.DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
var memberInit = dynamicLambda.Body as MemberInitExpression;
if (memberInit == null) throw new NotSupportedException();
var resultType = typeof(TResult);
var bindings = memberInit.Bindings.Cast<MemberAssignment>()
.Select(mb => Expression.Bind(
(MemberInfo)resultType.GetProperty(mb.Member.Name) ?? resultType.GetField(mb.Member.Name),
mb.Expression));
var body = Expression.MemberInit(Expression.New(resultType), bindings);
var lambda = Expression.Lambda(body, dynamicLambda.Parameters);
return source.Provider.CreateQuery<TResult>(
Expression.Call(
typeof(Queryable), "Select",
new Type[] { source.ElementType, lambda.Body.Type },
source.Expression, Expression.Quote(lambda)));
}
}
(Side note: Frankly I have no idea what values argument is for, but added it to match the corresponding DynamicQueryable.Select method signature.)
So your example will become something like this
public IQueryable<Thing> Foo(MyContext db)
{
var rootQuery = db.People.Where(x => x.City != null && x.State != null);
var groupedQuery = rootQuery.GroupBy("new ( it.City, it.State )", "it", new []{"City", "State"});
var finalLogicalQuery = groupedQuery.Select<Thing>("new ( Count() as TotalNumber, Key.City as City, Key.State as State )"); // IQueryable<Thing>
var executionDeferredTypedThings = finalLogicalQuery.Take(10);
return executionDeferredTypedThings;
}
How it works
The idea is quite simple.
The Select method implementation inside the DynamicQueryable looks something like this
public static IQueryable Select(this IQueryable source, string selector, params object[] values)
{
if (source == null) throw new ArgumentNullException("source");
if (selector == null) throw new ArgumentNullException("selector");
LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
return source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "Select",
new Type[] { source.ElementType, lambda.Body.Type },
source.Expression, Expression.Quote(lambda)));
}
What it does is to dynamically create a selector expression and bind it to the source Select method. We take exactly the same approach, but after modifying the selector expression created by the DynamicExpression.ParseLambda call.
The only requirement is that the projection is using "new (...)" syntax and the names and types of the projected properties match, which I think fits in your use case.
The returned expression is something like this
(source) => new TargetClass
{
TargetProperty1 = Expression1(source),
TargetProperty2 = Expression2(source),
...
}
where TargetClass is a dynamically generated class.
All we want is to keep the source part and just replace that target class/properties with the desired class/properties.
As for the implementation, first the property assignments are converted with
var bindings = memberInit.Bindings.Cast<MemberAssignment>()
.Select(mb => Expression.Bind(
(MemberInfo)resultType.GetProperty(mb.Member.Name) ?? resultType.GetField(mb.Member.Name),
mb.Expression));
and then the new DynamicClassXXX { ... } is replaced with with
var body = Expression.MemberInit(Expression.New(resultType), bindings);
Would something like this be of benefit to you?
public static IQueryable<TEntity> GetQuery<TEntity>(this DbContext db, bool includeReferences = false) where TEntity : class
{
try
{
if (db == null)
{
return null;
}
var key = typeof(TEntity).Name;
var metaWorkspace = db.ToObjectContext().MetadataWorkspace;
var workspaceItems = metaWorkspace.GetItems<EntityType>(DataSpace.OSpace);
var workspaceItem = workspaceItems.First(f => f.FullName.Contains(key));
var navProperties = workspaceItem.NavigationProperties;
return !includeReferences
? db.Set<TEntity>()
: navProperties.Aggregate((IQueryable<TEntity>)db.Set<TEntity>(), (current, navProperty) => current.Include(navProperty.Name));
}
catch (Exception ex)
{
throw new ArgumentException("Invalid Entity Type supplied for Lookup", ex);
}
}
You may want to take a look into the Generic Search project on Github located here:
https://github.com/danielpalme/GenericSearch
There is no need for Dynamic Linq on this one.
var groupedQuery = from p in db.People
where p.City != null && p.State != null
group p by new {p.City, p.State}
into gp
select new Thing {
TotalNumber = gp.Count(),
City = gp.Key.City,
State = gp.Key.State
};
IQueryable<Thing> retQuery = groupedQuery.AsQueryable();
retQuery= retQuery.Take(10);
return retQuery;

How can I rewrite this LINQ query with reflection

So I had written this LINQ query using reflection, and later found out it isn't supported. What would be the best way to get the same functionality from this code?
List<Profile> profilesFromUUID = await MobileService.GetTable<Profile>().Where(p => typeof(Profile)
.GetProperty(handler.Name + "UUID").GetValue(p) == obj.uuid).ToListAsync();
Use the reflection to create the query, not in the query. Consider:
public static IQueryable<Profile> Filter(
this IQueryable<Profile> source, string name, Guid uuid)
{
// .<name>UUID
var property = typeof(Profile).GetProperty(name + "UUID");
// p
var parExp = Expression.Parameter(typeof(Profile));
// p.<name>UUID
var methodExp = Expression.Property(parExp, property);
// uuid
var constExp = Expression.Constant(uuid, typeof(Guid));
// p.<name>UUID == uuid
var binExp = Expression.Equal(methodExp, constExp);
// p => p.<name>UUID == uuid
var lambda = Expression.Lambda<Func<Profile, bool>>(binExp, parExp);
// source.Where(p => p.<name>UUID == uuid)
return source.Where(lambda);
}
This builds up the expression first (so if name was "Test" it would create the expression corresponding with p => p.TestUUID == uuid and then uses that in the call to Where.
Because this step is done first, rather than within the expression itself, there's no need for the query engine to try to translate typeof or GetProperty() into SQL (which it of course, couldn't do).
So:
var filtered = MobileService.GetTable<Profile>().Filter(handler.Name, obj.uuid);
Returns an IQueryable<Profile> with the appropriate Where attached. And so:
var profilesFromUUID = await MobileService.GetTable<Profile>().Filter(handler.Name, obj.uuid).ToListAsync();
Will as a whole first use reflection to build the query, then apply the query, then produce a list from it asynchrously and then wait for its results.
It's worth noting that since Filter() will accept any IQueryable<Profile> they can be either chained or unioned. So:
MobileService.GetTable<Profile>().Filter("A", uuid0).Filter("B", uuid1);
Is equivalent to:
from p in MobileService.GetTable<Profile>() where p.AUUID = uuid0 && p.BUUID == uuid1
And:
MobileService.GetTable<Profile>().Filter("A", uuid0).Union(
MobileService.GetTable<Profile>().Filter("B", uuid1))
Is equivalent to:
from p in MobileService.GetTable<Profile>() where p.AUUID = uuid0 || p.BUUID == uuid1
A more generalised version would be:
public static IQueryable<TSource> FilterByNamedProperty<TSource, TValue>(this IQueryable<TSource> source, string propertyName, TValue value)
{
var property = typeof(TSource).GetProperty(propertyName);
var parExp = Expression.Parameter(typeof(TSource));
var methodExp = Expression.Property(parExp, property);
var constExp = Expression.Constant(value, typeof(TValue));
var binExp = Expression.Equal(methodExp, constExp);
var lambda = Expression.Lambda<Func<TSource, bool>>(binExp, parExp);
return source.Where(lambda);
}
Then while you have to do the + "UUID" in the calling code, you can use this to do analogous queries with any IQueryable<> of any element type.
How about just compare all property name? By definition UUID would not have collision anyway. Since Profile is just a data class, the # of the property for UUID is fixed.
List<Profile> profilesFromUUID = await MobileService.GetTable<Profile>()
.Where(p =>
p.A_UUID == obj.uuid ||
p.B_UUID == obj.uuid ||
p.C_UUID == obj.uuid)
.ToListAsync();
Or add a method (extension method) for Profile like:
public static Guid GetUUIDByTableName(this Profile value, string tableName)
{
switch (tableName)
{
case "A_": return value.A_UUID;
case "B_": return value.B_UUID;
default: return Guid.Empty;
}
}
List<Profile> profilesFromUUID = await MobileService.GetTable<Profile>()
.Where(p => p.GetUUIDByTableName(handler.Name) == obj.uuid)
.ToListAsync();

Search through Where clause in LINQ, using dynamic properties

I'm basically trying to construct a query, and I don't know why microsoft made this so difficult in Entity Framework and LINQ. I have various parameter STRINGS. So if you see a variable, assume it's a string passed in from somewhere.
users = this.entities.tableUsers
.Where(searchfield+" LIKE %#0%", search)
.OrderBy(x => x.GetType().GetProperty(order_by).GetValue(x, null).ToString())
.Skip(Convert.ToInt32(limit_begin))
.Take(Convert.ToInt32(limit_end))
.ToList();
My question is what to put inside "Where()" function in LINQ.
I want to search a field with string "searchfield", for the value .contains() "search".
Not sure why Visual Studio won't let me do this easily.
I've tried this as well, no luck:
.Where(x => x.GetType().GetProperty(searchfield).GetValue(x, null).ToList().Contains(search))
Note: I don't want to install any new libraries, this should be incredibly easy and simple for a modern language. I don't mind if the query returns all the rows and I search through it AFTER with .Contains().
This is not trivial, but I believe it can be done. The following has not been tested. The code is borrowed from here.
Create a helper method somewhere like
public static Expression<Func<T, bool>> GetContainsExpression<T>(string propertyName, string containsValue)
{
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);
}
public static Expression<Func<T, TKey>> GetPropertyExpression<T, TKey>(string propertyName)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var exp = Expression.Property(parameterExp, propertyName);
return Expression.Lambda<Func<T, TKey>>(exp, parameterExp);
}
Use it like
users = this.entities.tableUsers
.Where(GetContainsExpression<User>(searchfield, search))
.OrderBy(GetPropertyExpression<User, string>(searchfield))
...
UPDATE
As an alternative, you could create extension methods to provide a cleaner syntax. Create the following methods in a static class somewhere:
public static IQueryable<T> WhereStringContains<T>(this IQueryable<T> query, string propertyName, string contains)
{
var parameter = Expression.Parameter(typeof(T), "type");
var propertyExpression = Expression.Property(parameter, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(contains, typeof(string));
var containsExpression = Expression.Call(propertyExpression, method, someValue);
return query.Where(Expression.Lambda<Func<T, bool>>(containsExpression, parameter));
}
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName)
{
var propertyType = typeof(T).GetProperty(propertyName).PropertyType;
var parameter = Expression.Parameter(typeof(T), "type");
var propertyExpression = Expression.Property(parameter, propertyName);
var lambda = Expression.Lambda(propertyExpression, new[] { parameter });
return typeof(Queryable).GetMethods()
.Where(m => m.Name == "OrderBy" && m.GetParameters().Length == 2)
.Single()
.MakeGenericMethod(new[] { typeof(T), propertyType })
.Invoke(null, new object[] { query, lambda }) as IOrderedQueryable<T>;
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName)
{
var propertyType = typeof(T).GetProperty(propertyName).PropertyType;
var parameter = Expression.Parameter(typeof(T), "type");
var propertyExpression = Expression.Property(parameter, propertyName);
var lambda = Expression.Lambda(propertyExpression, new[] { parameter });
return typeof(Queryable).GetMethods()
.Where(m => m.Name == "OrderByDescending" && m.GetParameters().Length == 2)
.Single()
.MakeGenericMethod(new[] { typeof(T), propertyType })
.Invoke(null, new object[] { query, lambda }) as IOrderedQueryable<T>;
}
Then you can call them like:
var users = this.entities.tableUsers.WhereStringContains(searchField, search)
.OrderBy(searchField);
this should be incredibly easy and simple for a modern language
No, it should not if it goes against that language paradigm. LINQ and Entity Framework (as well as any other decent ORM out there) are made precisely to avoid what you're trying to accomplish: non-typed and non-compiler-verifiable queries. So basically you're forcing square peg into round hole.
You can still take a look at Dynamic LINQ.
You'll have to build an expression tree to pass to the Where method. Here's a loose adaptation of some code I have lying about:
string searchfield, value; // Your inputs
var param = Expression.Parameter(typeof(User), "user");
return Expression.Lambda<Func<T, bool>>(
Expression.Call(
Expression.Property(
param,
typeof(User).GetProperty(searchfield)),
typeof(string).GetMethod("Contains"),
Expression.Constant(value)),
param);
That will generate an appropriate expression to use as the parameter to Where.
EDIT: FYI, the resultant expression will look something like user => user.Foo.Contains(bar).
EDIT: To sort, something like this (ripped from my DynamicOrderList class):
private IQueryable<T> OrderQuery<T>(IQueryable<T> query, OrderParameter orderBy)
{
string orderMethodName = orderBy.Direction == SortDirection.Ascending ? "OrderBy" : "OrderByDescending";
Type t = typeof(T);
var param = Expression.Parameter(t, "user");
var property = t.GetProperty(orderBy.Attribute);
return query.Provider.CreateQuery<T>(
Expression.Call(
typeof(Queryable),
orderMethodName,
new Type[] { t, typeof(string) },
query.Expression,
Expression.Quote(
Expression.Lambda(
Expression.Property(param, property),
param))
));
}
My answer to your other question about this:
When creating dynamic linq sorting and searching order statements in Entity Framework

Categories