I have a class named HomeInfo
public class HomeInfo
{
public int ID {get;set;}
public string OwnerName {get;set;}
public string Address {get;set;}
public int EstimatedValue {get;set;}
}
I get data from server and i add that into List<HomeInfo> listHomeInfo
Now in my GUI I need to allow filtering results based on user input, so my client wants a textbox for Estimated Value and he wants to enter text there like '>30k and <50k' or '>50k', I parsed and converted these values and created object of class
public class ExpressionValue
{
public float? FirstDigit { get; set; }
/// <summary>
/// >, >=, <,<=
/// </summary>
public string FirstExpCondition { get; set; }
/// <summary>
/// OR, AND
/// </summary>
public string ConditionOperator { get; set; }
public float SecondDigit { get; set; }
public string SecondExpCondition { get; set; }
}
Using an ExpressionValue object I am able to create a proper condition string.
Now I am able to create a condition string like 'EstimatedValue > 30000 AND EstimatedValue < 60000' or 'EstimatedValue < 50000'
I don't know how can I effectively apply this condition on 'List listHomeInfo' since as far i know List<T>.Where() doesn't support string condition. I know a way around it is to convert the list to DataTable and use Select(string expression) method and then convert DataRow[] to List<HomeInfo>, but I think there may be a better way to achieve this.
[EDIT]
I created two methods to help me out but i am getting exception "The binary operator GreaterThan is not defined for the types 'System.Single' and 'System.Double'." when creating BinaryExpression.
public static Expression<Func<T, bool>> ParseExpressionCondition<T>(string expression, string fieldName)
{
try
{
string decimalNumRegex = #"\d+(\.\d{1,2})?";
List<string> matchPatterns = new List<string>() { ">=", ">", "<=", "<" };
ExpressionValue expValue = new ExpressionValue();
Dictionary<string, string> conditions = new Dictionary<string, string>();
var parameter = Expression.Parameter(typeof(T), typeof(T).ToString());
//var lhs = Expression.GreaterThan(Expression.Property(parameter, "EstimatedValue"), Expression.Constant(30000));
BinaryExpression lhs = null, rhs = null;
object objectValue = null;
string condOperator = null;
foreach (string pattern in matchPatterns)
{
Match match = Regex.Match(expression, pattern + decimalNumRegex);
if (match.Success)
{
//get digit part
double digit = double.Parse(Regex.Match(match.Value, decimalNumRegex).Value);
if (!expValue.FirstDigit.HasValue)
{
objectValue = digit;
condOperator = match.Value.Replace(digit.ToString(), "");
lhs = GetBinaryExpression(parameter, fieldName, objectValue, condOperator);
}
else
{
objectValue = digit;
condOperator = match.Value.Replace(digit.ToString(), "");
rhs = GetBinaryExpression(parameter, fieldName, objectValue, condOperator);
}
}
}
if (expression.ToLower().Contains("and"))
return Expression.Lambda<Func<T, bool>>(Expression.And(lhs, rhs), parameter);
else if (expression.ToLower().Contains("or"))
return Expression.Lambda<Func<T, bool>>(Expression.Or(lhs, rhs), parameter);
return null;
}
catch (Exception ex)
{
Logger.WriteLog(ex);
throw ex;
}
}
private static BinaryExpression GetBinaryExpression(ParameterExpression paraExp, string fieldName, object expressionValue, string conditionOperator)
{
try
{
BinaryExpression binExp = null;
MemberExpression expressionLeft = Expression.Property(paraExp, fieldName);
Expression expressionRight = Expression.Constant(expressionValue );
switch (conditionOperator)
{
case ">":
binExp = Expression.GreaterThan(expressionLeft, expressionRight);
break;
case ">=":
binExp = Expression.GreaterThanOrEqual(expressionLeft, expressionRight);
break;
case "<":
binExp = Expression.LessThan(expressionLeft, expressionRight);
break;
case "<=":
binExp = Expression.LessThanOrEqual(expressionLeft, expressionRight);
break;
}
return binExp;
}
catch (Exception ex)
{
throw ex;
}
}
Assuming you have parsing logic already implemented to some extent, I would suggest generating an expression tree (rather than using your own custom ExpressionValue class).
E.g. 'EstimatedValue > 30000 AND EstimatedValue < 60000' could become an expression tree of the form:
var parameter = Expression.Parameter(typeof(HomeInfo), "homeInfo");
var lhs = Expression.GreaterThan(Expression.Property(parameter, "EstimatedValue"), Expression.Constant(30000));
var rhs = Expression.LessThan(Expression.Property(parameter, "EstimatedValue"), Expression.Constant(60000));
var expression = Expression.Lambda<Func<HomeInfo, bool>>(Expression.AndAlso(lhs, rhs), parameter);
The list can then be queried using the generated expression tree as follows:
var results = listHomeInfo.AsQueryable().Where(expression);
Do not reinvent the wheel: NCalc does that kind of stuff already.
With a variable named EstimatedValue and a user defined expression UserExpression, in NCalc you'd do:
myList.Where(elem => new Expression(EstimatedValue.ToString() + UserExpression).Evaluate());
In your position I'd create a mini rule engine.
So
public abstract class ExpressionBase {
public float value {get;set;}
}
public class GreaterThanExpression : ExpressionBase {}
public class LessThanExpression : ExpressionBase {}
Now as you parse the entered string you can build a list of the expressions entered and then apply them to an IQueryable in the order you want to.
Write a LINQ extention method....
public static IEnumerable<HomeInfo> PassesExpression(this IEnumerable<HomeInfo> homes, ExpressionValue expression)
{
foreach(HomeInfo home in homes)
{
bool one, two;
if(expression.FirstExpCondition == '>')
one = (home.EstimatedValue > expression.FirstDigit);
else if(expression.FirstExpCondition == '>=')
one = (home.EstimatedValue >= expression.FirstDigit);
else if(expression.FirstExpCondition == '<')
one = (home.EstimatedValue < expression.FirstDigit);
else if(expression.FirstExpCondition == '<=')
one = (home.EstimatedValue <= expression.FirstDigit);
if(expression.SecondExpCondition == '>')
two = (home.EstimatedValue > expression.SecondDigit);
else if(expression.SecondExpCondition == '>=')
two = (home.EstimatedValue >= expression.SecondDigit);
else if(expression.SecondExpCondition == '<')
two = (home.EstimatedValue < expression.SecondDigit);
else if(expression.SecondExpCondition == '<=')
two = (home.EstimatedValue <= expression.SecondDigit);
if((expression.ConditionOperator == 'OR' && (one || two)) || (expression.ConditionOperator == 'AND' && (one && two)))
yield return home;
}
}
I usually have two textboxes for value ranges. One for the minimum value, one for the maximum value. They can be empty, if the limit is not required
int? min = null
int? max = null;
int i;
if (Int32.TryParse(txtMin.Text, out i) min = i;
if (Int32.TryParse(txtMax.Text, out i) max = i;
string name = txtName.Text;
With these definitions you can combine where clauses dynamically
IEnumerable<HomeInfo> result = list;
if (min.HasValue) result = result.Where(h => h.EstimatedValue >= min.Value);
if (max.HasValue) result = result.Where(h => h.EstimatedValue <= max.Value);
if (name != "")
result = result.Where(
h => h.OwnerName.StartsWith(name, StringComparison.OrdinalIgnoreCase)
);
use LinqToObjects
List<HomeInfo> homeInfos = new List<HomeInfo>();
homeInfos.Where(x => x.EstimatedValue > 1000).Where(x => x.EstimatedValue < 10000);
Related
I have a template which is stored as a string: "[u1:firstname] [u1:lastname]"
I need to convert the template to an Expression that outputs a string.
I've already written a parser to pull out the tokens but I'm not quite sure how to build an Expression for it. The code has been simplified below.
public class Account {
private Func<Account, string> template;
public User User1 { get; set; }
public User User2 { get; set; }
public string AccountName => this.template(this);
public void SetTemplate(Expression<Func<Account, string>> template) {
this.template = template.Compile();
}
}
public class User {
public string FirstName { get; set; }
public string LastName { get; set; }
}
The name of an account is defined by the template, and if the properties associated with the template change, so does the account name.
If I set the expression manually it works:
var account = new Account().SetTemplate(a => $"{a.User1.FirstName} {a.User2.LastName}");
But how do I build that Expression more dynamically?
Right now I'm doing something like this:
using TemplateExpression = Expression<Func<Account, string>>;
string templateString = "[u1:firstname] [u1:lastname]";
var expressions = new List<Expression>();
for (int i = 0; i < templateString.Length; ++i) {
var token = getToken(templateString, i);
switch (token) {
case "u1:firstname":
TemplateExpression u1FirstNameExpr = a => a.User1.FirstName;
expressions.Add(u1FirstNameExpr);
break;
case "u1:lastname":
TemplateExpression u1LastNameExpr = a => a.User1.LastName;
expressions.Add(u1LastNameExpr);
break;
// other possible tokens.
default: // constant
var constant = Expression.Constant(token);
expressions.Add(constant);
break;
}
}
But I have no idea how to combine those expressions into one expression that resembles the string interpolation above. I thought about maybe combining the expressions using string.Concat instead, but I couldn't get that to work either. Would maybe string.Format work better? But I'm still not sure how to construct that Expression.
Note that I had to rewrite the getToken to be able to test the method. I'm using Expression.Invoke because it is the easiest method to call an Expression from another Expression. In the end nearly all the code is the preparation of a string containing the format like "Hello {0} world {1}" and the array of objects that are passed in the string.Format.
public static string getToken(string templateString, ref int i, out bool isToken)
{
int j = i;
if (templateString[j] == '{')
{
isToken = true;
j++;
int k = templateString.IndexOf('}', j);
if (k == -1)
{
throw new Exception();
}
i = k + 1;
return templateString.Substring(j, k - j);
}
else
{
isToken = false;
i++;
return templateString[j].ToString();
}
}
public static Expression<Func<Account, string>> CreateTemplate(string templateString)
{
var formatObjs = new List<Expression>();
var formatString = new StringBuilder();
int parameterNumber = 0;
var accountParameter = Expression.Parameter(typeof(Account), "a");
for (int i = 0; i < templateString.Length;)
{
bool isToken;
var token = getToken(templateString, ref i, out isToken);
if (isToken)
{
Expression<Func<Account, string>> member;
switch (token)
{
case "u1:firstname":
member = a => a.User1.FirstName;
break;
case "u1:lastname":
member = a => a.User1.LastName;
break;
// other possible tokens.
default: // constant
throw new Exception();
}
formatObjs.Add(Expression.Invoke(member, accountParameter));
formatString.Append('{');
formatString.Append(parameterNumber);
formatString.Append('}');
parameterNumber++;
}
else
{
formatString.Append(token);
}
}
var formatMethod = typeof(string).GetMethod("Format", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), typeof(object[]) }, null);
var formatConstantExpression = Expression.Constant(formatString.ToString());
var formatObjsExpression = Expression.NewArrayInit(typeof(object), formatObjs);
var lambdaExpression = Expression.Lambda<Func<Account, string>>(Expression.Call(formatMethod, formatConstantExpression, formatObjsExpression), accountParameter);
return lambdaExpression;
}
Use it like:
var acc = new Account
{
User1 = new User { FirstName = "Foo", LastName = "Bar" }
};
acc.SetTemplate(Account.CreateTemplate("Hello {u1:firstname} World {u1:lastname}!!!"));
string name = acc.AccountName;
I want to select from a dynamic list where the list field name is for example 'SecName'and the SecName value is equal to for example 'xxx',
I know how to create it in sql, but I want to create and use it with entity framework.
Like Executing String in sql.
How can I make it in entity framework and linq.
It should be something like this
var lst=_efmodel.Tables.where(x=>x.fieldname=="SecName" && x.value=="xxx").tolist();
I'm looking for syntax to filter my list by passing two string, 1st string should be property Name and the Other should be that property value.
After searching I found the Answer ,Answer is:
public class SearchField
{
public string Name { get; set; }
public string #Value { get; set; }
//public string Operator { get; set; }
public SearchField(string Name, string #Value)
{
this.Name = Name;
this.#Value = #Value;
//Operator = "=";
}
}
public class FilterLinq<T>
{
public static Expression<Func<T, Boolean>> GetWherePredicate(params SearchField[] SearchFieldList)
{
//the 'IN' parameter for expression ie T=> condition
ParameterExpression pe = Expression.Parameter(typeof(T), typeof(T).Name);
//combine them with and 1=1 Like no expression
Expression combined = null;
if (SearchFieldList != null)
{
foreach (var fieldItem in SearchFieldList)
{
//Expression for accessing Fields name property
Expression columnNameProperty = Expression.Property(pe, fieldItem.Name);
//the name constant to match
Expression columnValue = Expression.Constant(fieldItem.Value);
//the first expression: PatientantLastName = ?
Expression e1 = Expression.Equal(columnNameProperty, columnValue);
if (combined == null)
{
combined = e1;
}
else
{
combined = Expression.And(combined, e1);
}
}
}
//create and return the predicate
if (combined != null) return Expression.Lambda<Func<T, Boolean>>(combined, pe);
return null;
}
}
and use it like this :
var lst = _efmodel.Count(2015).AsQueryable()
.Where(
FilterLinq<PazTedad_Result>.GetWherePredicate(
new SearchField("FieldName", "FieldValue"))).ToList();
Code must be something like this?
var lst=_efmodel.Table.Where(x => x.fieldname == <SomeValue>).ToList();
I have searched, and found similar posts pertaining to my issue, however nothing seems to solve my problem.
I am fairly new to C#, and this is my first attempt at building an expression tree. (please go easy ;-)
I am trying to create an expression tree which would, once compiled, filter values on a set of data.
Here is my expression method:
private static Expression<Func<TItem, bool>> CreateFilterExpression<TItem>(string propertyName, string expressionType, dynamic filterValue)
{
if (param == null)
{
param = Expression.Parameter(typeof(TItem), "item");
}
MemberExpression member = GetMemberExpression<TItem>(propertyName);
//When we call our method, we need to evaluate on the same type
//we convert the filter value to the type of the property we are evaluating on
dynamic convertedValue = Convert.ChangeType(filterValue, member.Type);
MethodInfo method = member.Type.GetMethod(expressionType, new[] { member.Type });
ConstantExpression constantValue = Expression.Constant(convertedValue, member.Type);
Expression containsMethodExp;
if (expressionType == "NotEqual")
{
method = member.Type.GetMethod("Equals", new[] { member.Type });
}
if (member.Type.ToString().ToLower() == "system.string")
{
//We need to compare the lower case of property and value
MethodCallExpression propertyValueToLowerCase = Expression.Call(member, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
MethodCallExpression filterValueToLowerCase = Expression.Call(constantValue, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
containsMethodExp = Expression.Call(propertyValueToLowerCase, method, filterValueToLowerCase);
}
else if (member.Type.ToString().ToLower() == "system.datetime")
{
//we need to compare only the dates
MemberExpression dateOnlyProperty = Expression.Property(member, "Date");
containsMethodExp = Expression.Call(dateOnlyProperty, method, constantValue);
}
else
{
containsMethodExp = Expression.Call(member, method, constantValue);
}
if (expressionType == "NotEqual")
{
containsMethodExp = Expression.Not(containsMethodExp);
}
return Expression.Lambda<Func<TItem, bool>>(containsMethodExp, param);
}
private static MemberExpression GetMemberExpression<TItem>(string propertyName)
{
if (param == null)
{
param = Expression.Parameter(typeof(TItem), "item");
}
MemberExpression member = null;
//Check if we have a nested property
if (propertyName.Contains('.'))
{
Expression nestedProperty = param;
string[] properies = propertyName.Split('.');
int zeroIndex = properies.Count() - 1;
for (int i = 0; i <= zeroIndex; i++)
{
if (i < zeroIndex)
{
nestedProperty = Expression.PropertyOrField(nestedProperty, properies[i]);
}
else
{
member = Expression.Property(nestedProperty, properies[i]);
}
}
}
else
{
member = Expression.Property(param, propertyName);
}
return member;
}
Example usage would be like so:
var lambda = CreateFilterExpression<T>("Some.Nested.Object", "Equals", "Some value");
var compiled = lambda.Compile();
gridData = gridData.Where(compiled);
An example of the data I trying to ultimately bind to my grid looks like this:
public class Some : BaseClass
{
public decimal NumberAvailable { get; set; }
public DateTime EffectiveDate { get; set; }
public Batch Batch { get; set; }
public decimal Price { get; set; }
public decimal Limit { get; set; }
public NestedClass Nested { get; set; }
public int? CompanyId { get; set; }
public decimal Amount { get; set; }
}
public class NestedClass : BaseClass
{
public int RequestId { get; set; }
public string Code { get; set; }
public string Company { get; set; }
public string Reference { get; set; }
}
The problem occurs when we have null value on an object, like "Some.Nested = null", and then trying to convert "Reference" to lowercase. Here:
MethodCallExpression propertyValueToLowerCase = Expression.Call(member, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Here is the result in debugger:
How can I check for null values, on nested objects, and return empty string if it is null?
I hope I explained my question well enough. Thank you in advance!
What you want to do is to generate an expression like this:
Some == null ? null : Some.Nested == null ? null : Some.Nested.Object
This unfortunately is no longer a member expression, so GetMemberExpression wouldn’t work for this. Instead you need a chain of conditional expression that accesses one more level at a time.
Once you have that, you could then do <memberExpression> ?? string.Empty to get a string which you can safely operate on.
To generate the latter expression, you can use Expression.Coalesce:
Expression.Coalesce(memberExpression, Expression.Constant(string.Empty))
For the member expression itself, you could write something like this:
Expression AccessMember(Expression obj, string propertyName)
{
string[] parts = propertyName.Split(new char[] { '.' }, 2);
Expression member = Expression.PropertyOrField(obj, parts[0]);
if (parts.Length > 1)
member = AccessMember(member, parts[1]);
return Expression.Condition(Expression.Equal(obj, Expression.Constant(null)),
Expression.Constant(null, member.Type), member);
}
This can be used like this:
string path = "Some.Nested.Object";
string[] parts = path.Split(new char[] { '.' }, 2);
ParameterExpression param = Expression.Parameter(typeof(T), parts[0]);
Expression memberAccess = AccessMember(param, parts[1]);
memberAccess would then be exactly the above chained conditional expression.
Combined into your function (simplified only for strings for now), it could look like this:
Expression<Func<TObj, bool>> BuildFilterExpression<TObj, TMember>(string propertyPath, TMember comparisonValue, TMember defaultValue)
{
string[] parts = propertyPath.Split(new char[] { '.' }, 2);
ParameterExpression param = Expression.Parameter(typeof(TObj), parts[0]);
// get member access expression
Expression memberExpression = AccessMember(param, parts[1]);
// coalesce the member with the default value
memberExpression = Expression.Coalesce(memberExpression, Expression.Constant(defaultValue));
// get the comparison value as expression
Expression comparisonExpression = Expression.Constant(comparisonValue);
// type specific logic
if (memberExpression.Type == typeof(string))
{
MethodInfo toLowerMethod = typeof(string).GetMethod("ToLower", Type.EmptyTypes);
memberExpression = Expression.Call(memberExpression, toLowerMethod);
comparisonExpression = Expression.Call(comparisonExpression, toLowerMethod);
}
// create the comparison expression
Expression filterExpression = Expression.Equal(memberExpression, comparisonExpression);
return Expression.Lambda<Func<TObj, bool>>(filterExpression, param);
}
Used like this:
BuildFilterExpression<SomeType, string>("Some.Nested.Object", "foo bar", string.Empty)
… it essentially creates the following lambda expression:
(Some) => ((Some == null ? null : Some.Nested == null ? null : Some.Nested.Object) ?? string.Empty).ToLower() == "foo bar"
Above code assumes that for a property expression Some.Nested.Object, Some is the object that is being passed to the lambda, so the first property that would be accessed is Nested. The reason is that I simply didn’t know your example object structure, so I had to come up with something.
If you want Some be the first property that is accessed for the passed object, you can easily change that though. To do that, modify the beginning of BuildFilterExpression so that the propertyPath is not split up. Pass some random name (or no name even) to Expression.Parameter, and pass the full propertyPath to AccessMember:
// don’t split up the propertyPath
// let’s call the parameter `obj`
ParameterExpression param = Expression.Parameter(typeof(TObj), "obj");
// get member access expression—for the full property path
Expression memberExpression = AccessMember(param, propertyPath);
Each item/string in my array starts with two letters followed by two or three numbers and then sometimes followed by another letter.
Examples, RS01 RS10 RS32A RS102 RS80 RS05A RS105A RS105B
I tried to sort this using the default Array.Sort but it came back with this...
RS01
RS05A
RS10
RS102
RS105A
RS105B
RS32A
RS80
But I need it like this..
RS01
RS05A
RS10
RS32A
RS80
RS102
RS105A
RS105B
Any Ideas?
Here is sorting with custom comparison delegate and regular expressions:
string[] array = { "RS01", "RS10", "RS32A", "RS102",
"RS80", "RS05A", "RS105A", "RS105B" };
Array.Sort(array, (s1, s2) =>
{
Regex regex = new Regex(#"([a-zA-Z]+)(\d+)([a-zA-Z]*)");
var match1 = regex.Match(s1);
var match2 = regex.Match(s2);
// prefix
int result = match1.Groups[1].Value.CompareTo(match2.Groups[1].Value);
if (result != 0)
return result;
// number
result = Int32.Parse(match1.Groups[2].Value)
.CompareTo(Int32.Parse(match2.Groups[2].Value));
if (result != 0)
return result;
// suffix
return match1.Groups[3].Value.CompareTo(match2.Groups[3].Value);
});
UPDATE (little refactoring, and moving all stuff to separate comparer class). Usage:
Array.Sort(array, new RSComparer());
Comparer itself:
public class RSComparer : IComparer<string>
{
private Dictionary<string, RS> entries = new Dictionary<string, RS>();
public int Compare(string x, string y)
{
if (!entries.ContainsKey(x))
entries.Add(x, new RS(x));
if (!entries.ContainsKey(y))
entries.Add(y, new RS(y));
return entries[x].CompareTo(entries[y]);
}
private class RS : IComparable
{
public RS(string value)
{
Regex regex = new Regex(#"([A-Z]+)(\d+)([A-Z]*)");
var match = regex.Match(value);
Prefix = match.Groups[1].Value;
Number = Int32.Parse(match.Groups[2].Value);
Suffix = match.Groups[3].Value;
}
public string Prefix { get; private set; }
public int Number { get; private set; }
public string Suffix { get; private set; }
public int CompareTo(object obj)
{
RS rs = (RS)obj;
int result = Prefix.CompareTo(rs.Prefix);
if (result != 0)
return result;
result = Number.CompareTo(rs.Number);
if (result != null)
return result;
return Suffix.CompareTo(rs.Suffix);
}
}
}
You can use this linq query:
var strings = new[] {
"RS01","RS05A","RS10","RS102","RS105A","RS105B","RS32A","RS80"
};
strings = strings.Select(str => new
{
str,
num = int.Parse(String.Concat(str.Skip(2).TakeWhile(Char.IsDigit))),
version = String.Concat(str.Skip(2).SkipWhile(Char.IsDigit))
})
.OrderBy(x => x.num).ThenBy(x => x.version)
.Select(x => x.str)
.ToArray();
DEMO
Result:
RS01
RS05A
RS10
RS32A
RS80
RS102
RS105A
RS105B
You'll want to write a custom comparer class implementing IComparer<string>; it's pretty straightforward to break your strings into components. When you call Array.Sort, give it an instance of your comparer and you'll get the results you want.
I am building an API around a web service call using Expressions to allow a developer to specify a query and have an ExpressionVisitor convert the Expression into the query string. The request is XML with a particular element containing a query string.
For example, I can do something like this which will retrieve all checking accounts with a bank name of Bank 1 or Bank 2:
"bankname = 'Bank 1' or bankname = 'Bank 2'"
The web service can handle significantly more complex queries but I'll just stick with this for now.
So I have a class CheckingAccount:
[IntacctObject("checkingaccount")]
public class CheckingAccount : Entity
{
[IntacctElement("bankaccountid")]
public string Id { get; set; }
[IntacctElement("bankname")]
public string BankName { get; set; }
}
And an ExpressionVisitor whose primary function is to convert an expression like this:
Expression> expression = ca => ca.BankName == "Bank 1" || ca.BankName == "Bank 2"
into the query: "bankname = 'Bank 1' or bankname = 'Bank 2'"
This isn't so tough. Where things start to break down are when I introduce local variables:
var account = new CheckingAccount { BankName = "Bank 1" };
string bankName = "Bank 2";
Expression> expression = ca => ca.BankName == account.BankName || ca.BankName == bankName;
I know how to deal with a simple local variable (ie. string bankName = "Bank 2") but dealing with a the other type (var account = new CheckingAccount { BankName = "Bank 1" }) is much more complex.
At the end of the day these are the big issues that I need to figure out how to deal with right now. I know there are much more complex scenarios but I'm not so concerned with those at the moment.
Here is my expression visitor (please note the generic constraint on method CreateFilter):
internal class IntacctWebService30ExpressionVisitor : ExpressionVisitor
{
private readonly List _Filters = new List();
private IntacctWebServicev30SimpleQueryFilter _CurrentSimpleFilter;
private IntacctWebServicev30ComplexQueryFilter _CurrentComplexFilter;
private MemberExpression _CurrentMemberExpression;
public string CreateFilter(Expression> expression) where TEntity : Entity
{
Visit(expression);
string filter = string.Join(string.Empty, _Filters.Select(f => f.ToString()).ToArray());
return filter;
}
protected override Expression VisitBinary(BinaryExpression node)
{
switch (node.NodeType)
{
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
_CurrentComplexFilter = new IntacctWebServicev30ComplexQueryFilter { ExpressionType = node.NodeType };
break;
case ExpressionType.Equal:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.NotEqual:
_CurrentSimpleFilter = new IntacctWebServicev30SimpleQueryFilter { ExpressionType = node.NodeType };
break;
}
return base.VisitBinary(node);
}
protected override Expression VisitMember(MemberExpression node)
{
var attr = node.Member.GetAttribute();
if (attr != null)
_CurrentSimpleFilter.FieldName = attr.FieldName;
else
_CurrentMemberExpression = node;
return base.VisitMember(node);
}
protected override Expression VisitConstant(ConstantExpression node)
{
object value = Expression.Lambda>(node).Compile().Invoke();
string fieldValue = extraxtFieldValue(value, node);
_CurrentSimpleFilter.FieldValue = fieldValue;
if (_CurrentComplexFilter != null)
{
if (_CurrentComplexFilter.Left == null)
{
_CurrentComplexFilter.Left = _CurrentSimpleFilter;
}
else if (_CurrentComplexFilter.Right == null)
{
_CurrentComplexFilter.Right = _CurrentSimpleFilter;
_Filters.Add(_CurrentComplexFilter);
}
else
throw new InvalidOperationException();
}
else
{
_Filters.Add(_CurrentSimpleFilter);
}
return base.VisitConstant(node);
}
private string extraxtFieldValue(object value)
{
string fieldValue;
if (value is DateTime)
fieldValue = ((DateTime)value).ToString("yyyy-MM-dd");
else if (value is string)
fieldValue = value.ToString();
else if (value.GetType().IsEnum)
{
throw new NotImplementedException();
}
else
{
// Not sure if this is the best way to do this or not but can't figure out how
// else to get a variable value.
// If we are here then we are dealing with a property, field, or variable.
// This means we must extract the value from the object.
// In order to do this we will rely on _CurrentMemberExpression
if (_CurrentMemberExpression.Member.MemberType == MemberTypes.Property)
{
fieldValue = value.GetType().GetProperty(_CurrentMemberExpression.Member.Name).GetValue(value, null).ToString();
}
else if (_CurrentMemberExpression.Member.MemberType == MemberTypes.Field)
{
fieldValue = value.GetType().GetFields().First().GetValue(value).ToString();
}
else
{
throw new InvalidOperationException();
}
}
return fieldValue;
}
}
Please let me know if you'd like any more detail....
Thanks
Have a look at an article about this very issue from Matt Warren. He provides source for a class that does exactly this, with an explanation how does it do it. It's also included in his IQToolkit.
If you are interested in using a open source third party library to do this for you, you can check out Expression Tree Serialization. I believe it does what you are looking for.