How to specify dynamic field names in a Linq where clause?
If you create a Filter object that contains criteria for Linq that normally goes in a where clause like this:
var myFilterObject = FilterFactory.GetBlank();
myFilterObject.AddCondition("Salary", "lessThan", "40000");
var myResult = myRepository.GetEmployees(myFilterObject);
How would you match the Linq field to the Field Name without using a big case statement?
return from e in db.Employee
where e.Salary < 40000
select new IList<EmployeeViewModel> { Name= e.name, Salary= e.Salary };
I assume you need to send an object to the Repository that specifies filtering so that you only pull what records you need. I assume Linq doesn't pre-compile (unless you create 开发者_运维知识库a customized delegate and function), so you should be able to dynamically specify which fields you want to filter.
It would be nice if you could do something like e["Salary"] like some type of Expando Object.
You can build the Expression by hand, like so:
var eParam = Expression.Parameter(typeof(Employee), "e");
var comparison = Expression.Lambda(
Expression.LessThan(
Expression.Property(eParam, "Salary"),
Expression.Constant(40000)),
eParam);
return from e in db.Employee.Where(comparison)
select new EmployeeViewModel { Name = e.name, Salary = e.Salary };
I suggest you have a look at Dynamic Query in the Linq to SQL examples. You can use that to write conditions in "plain text", e.g.:
var employees = db.Employee.Where("Salary < 40000").Select(...);
To clarify: These extensions essentially build the same expression trees out of the string that you normally construct via lambdas, so this isn't the same as writing raw SQL against the database. The only disadvantage is that you can't use the query comprehension syntax (from x in y
etc.) with it.
I'm pretty late to this party, I realize - however I've written some code that I believe satisfies this without using DynamicLINQ, which I've found to be poorly supported, and dodgy to use (at best).
/// <summary>
/// A method to create an expression dynamically given a generic entity, and a propertyName, operator and value.
/// </summary>
/// <typeparam name="TEntity">
/// The class to create the expression for. Most commonly an entity framework entity that is used
/// for a DbSet.
/// </typeparam>
/// <param name="propertyName">The string value of the property.</param>
/// <param name="op">An enumeration type with all the possible operations we want to support.</param>
/// <param name="value">A string representation of the value.</param>
/// <param name="valueType">The underlying type of the value</param>
/// <returns>An expression that can be used for querying data sets</returns>
private static Expression<Func<TEntity, bool>> CreateDynamicExpression<TEntity>(string propertyName,
Operator op, string value, Type valueType)
{
Type type = typeof(TEntity);
object asType = AsType(value, valueType);
var p = Expression.Parameter(type, "x");
var property = Expression.Property(p, propertyName);
MethodInfo method;
Expression q;
switch (op)
{
case Operator.Gt:
q = Expression.GreaterThan(property, Expression.Constant(asType));
break;
case Operator.Lt:
q = Expression.LessThan(property, Expression.Constant(asType));
break;
case Operator.Eq:
q = Expression.Equal(property, Expression.Constant(asType));
break;
case Operator.Le:
q = Expression.LessThanOrEqual(property, Expression.Constant(asType));
break;
case Operator.Ge:
q = Expression.GreaterThanOrEqual(property, Expression.Constant(asType));
break;
case Operator.Ne:
q = Expression.NotEqual(property, Expression.Constant(asType));
break;
case Operator.Contains:
method = typeof(string).GetMethod("Contains", new[] {typeof(string)});
q = Expression.Call(property, method ?? throw new InvalidOperationException(),
Expression.Constant(asType, typeof(string)));
break;
case Operator.StartsWith:
method = typeof(string).GetMethod("StartsWith", new[] {typeof(string)});
q = Expression.Call(property, method ?? throw new InvalidOperationException(),
Expression.Constant(asType, typeof(string)));
break;
case Operator.EndsWith:
method = typeof(string).GetMethod("EndsWith", new[] {typeof(string)});
q = Expression.Call(property, method ?? throw new InvalidOperationException(),
Expression.Constant(asType, typeof(string)));
break;
default:
throw new ArgumentOutOfRangeException(nameof(op), op, null);
}
return Expression.Lambda<Func<TEntity, bool>>(q, p);
}
/// <summary>
/// Extract this string value as the passed in object type
/// </summary>
/// <param name="value">The value, as a string</param>
/// <param name="type">The desired type</param>
/// <returns>The value, as the specified type</returns>
private static object AsType(string value, Type type)
{
//TODO: This method needs to be expanded to include all appropriate use cases
string v = value;
if (value.StartsWith("'") && value.EndsWith("'"))
v = value.Substring(1, value.Length - 2);
if (type == typeof(string))
return v;
if (type == typeof(DateTime))
return DateTime.Parse(v);
if (type == typeof(DateTime?))
return DateTime.Parse(v);
if (type == typeof(int))
return int.Parse(v);
if (type == typeof(int?)) return int.Parse(v);
throw new ArgumentException("A filter was attempted for a field with value '" + value + "' and type '" +
type + "' however this type is not currently supported");
}
This code can be used thusly:
var whereClause = CreateDynamicExpression<MyDatabaseTable>("MyFieldName",
Operator.Contains, "some string value",typeof(string));
var results = _db.MyDatabaseTable.Where(whereClause);
if you are using a nullable field you should execute the following code otherwise you will get an error
var eParam = Expression.Parameter(typeof(Employee), "e");
var comparison = Expression.Lambda(
Expression.LessThan(
Expression.Property(eParam, "Salary"),
Expression.Convert(Expression.Constant(40000),typeof(Employee).GetProperty("Salary").PropertyType)),
eParam);
精彩评论